I have a component in which I am getting users from the database and then checking which users are not admin and displaying them in a list so that they can be approved or rejected. I am using redux toolkit as a state management library. As you can see in the code I am dispatching an action called getAllUsers()
in the useEffect
hook. The getAllUsers()
actions fetches all the users from the database and returns and array which I destructure from users using useSelector
hook.
If I pass users in the dependency array of useEffect I get infinite loop which is of course expected behaviour because the reference of the array changes.
In my user array which I get from getAllUsers
action, I have user objects which contains multiple attributes like name, email etc. among those attributes I have an attribute called isAdmin
. As you can see I am dispatching an action called approveUser
, it simply just marks that isAdmin
value to true
.
How can I make it such that as I approve the user gets approved and hence gets removed from the component.
Approve User Component:
import React, { useCallback, useEffect, useMemo, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { getAllUsers, approveUser } from "../../features/users/userSlice";
import { useStyles } from "../../hooks/useStyles";
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
import CancelIcon from '@mui/icons-material/Cancel';
import {
TableContainer,
Table,
TableHead,
TableBody,
TableRow,
TableCell,
Paper,
CircularProgress,
Button,
Typography
} from "@material-ui/core";
import Checkbox from '@mui/material/Checkbox';
const Approval = () => {
const { users, isLoading } = useSelector((state) => state.user);
const dispatch = useDispatch();
const { loaderContainer } = useStyles()
useEffect(() => {
dispatch(getAllUsers());
console.log(users)
}, [])
return (
<TableContainer component={Paper}>
<Table aria-label="simple table">
<TableHead>
<TableRow>
<TableCell><Typography variant="subtitle1">Name</Typography></TableCell>
<TableCell><Typography variant="subtitle1">Email</Typography></TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
</TableRow>
</TableHead>
<TableBody>
{ users && users.data.map((user) => {
if (!user.isAdmin) {
return (
<TableRow key={user.name} sx={{ '&:last-child td, &:last-child th':{border: 0} }}>
<TableCell><Typography variant="subtitle1">{user.name}</Typography></TableCell>
<TableCell><Typography variant="subtitle1">{user.email}</Typography></TableCell>
<TableCell><Button endIcon={<CheckCircleIcon />} style={{ backgroundColor: '#FF7B00', color: 'white' }} onClick={() => {
dispatch(approveUser({ _id: user._id, isAdmin: true, isSuperUser: false }))
}} variant="contained">Approve</Button></TableCell>
<TableCell><Button endIcon={<CancelIcon />} style={{ backgroundColor: '#FF7B00', color: 'white' }} onClick={() => {
}} variant="contained">Reject</Button></TableCell>
</TableRow>
);
}
})}
</TableBody>
</Table>
</TableContainer>
);
};
export default Approval;
User Slice:
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import axios from "axios";
const users = 'http://localhost:5000/api/users';
const loginUrl = 'http://localhost:5000/api/login';
const signupUrl = 'http://localhost:5000/api/signup';
const approve = 'http://localhost:5000/api/approve';
export const loginUser = createAsyncThunk('user/loginUser', async
(data) => {
const response = await axios.post(loginUrl, data);
return response;
})
export const signupUser = createAsyncThunk('user/signupUser', async
(data) => {
const response = await axios.post(signupUrl, data);
return response;
})
export const getAllUsers = createAsyncThunk('user/getAllUsers',
async () => {
const response = await axios.get(users);
return response;
})
export const approveUser = createAsyncThunk('user/approveUser',
async (data) => {
// console.log(data)
const response = await axios.put(approve, data);
console.log(response)
return response;
})
const initialState = {
user: {},
users: [],
isLoggedIn: false,
isLoading: true
}
const userSlice = createSlice({
name: 'user',
initialState,
reducers: {
logOutUser: (state) => {
state.isLoggedIn = false;
state.user = {};
state.isLoading = false;
},
getPassword: (state, action) => {
const password = action.payload
console.log(password)
}
},
extraReducers: {
[loginUser.pending]: (state) => {
state.isLoading = false
},
[loginUser.fulfilled]: (state, action) => {
state.isLoading = false
state.user = action.payload.data
state.isLoggedIn = action.payload.data.isLoggedIn
},
[loginUser.rejected]: (state) => {
state.isLoading = false
},
[signupUser.pending]: (state) => {
state.isLoading = false
},
[signupUser.fulfilled]: (state, action) => {
state.isLoading = false
state.user = action.payload.data
},
[signupUser.rejected]: (state) => {
state.isLoading = false
},
[getAllUsers.pending]: (state) => {
state.isLoading = false
},
[getAllUsers.fulfilled]: (state, action) => {
state.isLoading = false
state.users = action.payload.data
},
[getAllUsers.rejected]: (state) => {
state.isLoading = false
},
[approveUser.pending]: (state) => {
state.isLoading = false
},
[approveUser.fulfilled]: (state, action) => {
state.isLoading = false
// state.users = action.payload.data
},
[approveUser.rejected]: (state) => {
state.isLoading = false
},
}
})
export const { getPassword, logOutUser } = userSlice.actions
export default userSlice.reducer;
RTK's createReducer
and createSlice
use Immer internally to let you write simpler immutable update logic using "mutating" syntax.
Take a look at Immer update patterns under // update by id
.
You can get the id
of the approved user from action.meta.arg._id
. With the id, you can find the index in state.users
.
Then you can update the isAdmin
property of that user with state.users[id].isAdmin = true
[approveUser.fulfilled]: (state, action) => {
state.isLoading = false;
const id = action.meta.arg._id;
const foundId = state.users.findIndex((user) => user._id === id);
if (foundId !== -1) state.users[foundId].isAdmin = true;
},
This is an optimistic update because we can assume to update the user in state.users without needing to dispatch getAllUsers
again.