DEV Community

Omar Elwakeel
Omar Elwakeel

Posted on

How to make a self-controlled button in react

Button image

Controlling how the buttons in your app can be sometimes redundant and you find yourself writing the same code to do the same stuff many times, what if you can have your own component that can decide for itself, how to render and how to behave.

In this tutorial I will show you how to have a generic button that can take care of itself according to the variables around it, like the current authenticated user and upon it decides how will it look and whether it will be enabled or disabled.

For this tutorial and for the sake of having a good insight on how helpful its, I'm going to build a slightly complicated app, I will use redux for the local state management, you will know why shortly.

If you want to follow along, please be my guest. But if you want to see the code you can go to the github repo.

let's create an app with create-react-app and name it as you like, after that we need to install some packages like react-redux @reduxjs/toolkit and optionally you can install @mui/material @mui/styles @emotion/react @emotion/styled

Inside the src directory we can have 2 folders one for the components and the other for the redux store

inside src/store/authSlice.js

import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';

export const login = createAsyncThunk(
    'app/auth/login',
    async (credentials) => {
        return credentials;
    }
);

export const logout = createAsyncThunk(
    'app/auth/logout',
    async () => {
        return null;
    }
);

const authSlice = createSlice({
    name: 'app/auth',
    initialState: null,
    extraReducers:{
        [login.fulfilled] : (state, action) => action.payload,
        [logout.fulfilled] : (state, action) => action.payload
    }
});

export default authSlice.reducer;

Enter fullscreen mode Exit fullscreen mode

inside src/store/index.js

import { combineReducers, configureStore } from '@reduxjs/toolkit';
import auth from './authSlice';

const reducer = combineReducers({
    auth
})

const store = configureStore({
    reducer,
    middleware: getDefaultMiddleware =>
        getDefaultMiddleware({
            immutableCheck: false,
            serializableCheck: false
        }),
    devTools: process.env.NODE_ENV === 'development'
});

export default store;
Enter fullscreen mode Exit fullscreen mode

these couple of files to configure the store for local state management, specifically the auth and current user signed in. we will wrap the application with the redux provider component and pass the store as props.

inside src/App.js

import { useState } from "react";
import { Provider } from 'react-redux';

import store from './store/index'

import Toolbar from "./components/Toolbar";
import AuthButton from "./components/AuthButton";

function App() {
  const [auth, setAuth] = useState('');

  return (
    <Provider store={store}>
      <div className="App">
        <Toolbar setAuth={setAuth}/>
        <AuthButton auth={auth}/>
      </div>
    </Provider>
  );
}

export default App;

Enter fullscreen mode Exit fullscreen mode

for the Toolbar and the AuthButton components

inside src/components/Toolbar.js

import { Button, TextField } from "@mui/material";
import { makeStyles } from "@mui/styles";
import React, { useState } from "react";
import { useDispatch } from "react-redux";
import { login, logout } from "../store/authSlice";

const useStyles = makeStyles(() => ({
    container:{
        display:'flex',
        justifyContent:'center',
        alignItems:'center'
    }
}))

const employees = [
    {username:'marie', role:'ceo'}, 
    {username:'jack', role:'manager'}, 
    {username:'laila', role:'employee'}, 
    {username:'sam', role:'guest'}
]

export default function Toolbar({ setAuth }){
    const classes = useStyles();
    const dispatch = useDispatch();

    const [username, setUsername] = useState('')

    const handleLogin = () => {
        const credentials = employees.find(employee => employee.username === username)

        if(!credentials) return;

        dispatch(login(credentials))
    }

    const handleLogout = () => {
        dispatch(logout())
    }

    return (
        <div className={classes.container}>
            <TextField 
                onChange={(e) => setUsername(e.target.value)}
            />
            <Button onClick={(e) => handleLogin()}>Login</Button>
            <Button onClick={(e) => handleLogout()}>Logout</Button>
        </div>
    )
}
Enter fullscreen mode Exit fullscreen mode

Now whenever we logout or login the button will be notified with the current authenticated user, and will fetch the role of the user and update itself, whether the color, disabled, etc.

inside src/components/AuthButton.js

import React from "react";
import { makeStyles } from "@mui/styles";
import { blue, green, orange, red } from "@mui/material/colors";
import { useSelector } from "react-redux";

const useStyles = makeStyles(() => {
    return {
        btn: {
            backgroundColor:({auth}) => {
                if(auth){
                    switch (auth) {
                        case "ceo":
                            return green[700]
                        case "manager":
                            return orange[700]
                        case "employee":
                            return blue[700]
                        default:
                            return red[700]
                    }
                }

                return red[700]
            },
            width:'20em',
            height:'5em',
            borderRadius:'5px',
            color:'white'
        }
    }
})

export default function AuthButton({ unauthorizedRoles }){
    const auth = useSelector(({auth}) => auth);
    const classes = useStyles({auth: auth?.role || ''});

    const isDisabled = unauthorizedRoles.includes(auth?.role)

    const renderMessage = () => {
        if(auth){
            if(!isDisabled){
                return `Hi, ${auth?.username} Want to do something cool`
            }
            else{
                return `Hi, ${auth?.username}, Sorry but you are not authorized for the cool stuff`
            }
        }

        return 'Why dont you login'
    }

    return (
        <button 
            variant="contained" 
            disabled={isDisabled} 
            className={classes.btn}
            >
            {renderMessage()}
        </button>    
    )
}

AuthButton.defaultProps = {
    unauthorizedRoles : ["guest"]
}
Enter fullscreen mode Exit fullscreen mode

once you login or logout, the button will adjust itself accordingly.

Result

Top comments (0)