useNavigate won't work after removing JWT from localstorage - reactjs

The goal is to basically "logout", which means removing the JWT that's currently stored in local storage.
Ideally, what I want is that the logout button is clicked, the logout function is dispatched which is essentially just removing the JWT from local storage, the reset function is dispatched which means that the state is reverted back to the initial state, and finally, it will navigate onto the login page via the useNavigate hook.
The problem is, it can't seem to work. Oddly enough, I tried testing out if it's reading anything on the selector
Here is the code for the dashboard, which checks if there's an existing user in local storage, and if null, redirects to the login page:
const navigate = useNavigate();
const dispatch = useDispatch();
const { user } = useSelector((state) => state.auth);
const { posts, isLoading, isError, message } = useSelector(
(state) => state.post
);
useEffect(() => {
if (isError) {
console.log(message);
}
if (!user) {
navigate("/login");
}
dispatch(getPosts());
return () => {
dispatch(reset());
};
}, [user, message, isError, navigate, dispatch]);
if (isLoading) {
return <Spinner />;
}
here's the logout function from the slice as well as the slice containing the reset:
export const logout = createAsyncThunk("auth/logout", async () => {
await authService.logout();
});
export const authSlice = createSlice({
name: "auth",
initialState,
reducers: {
reset: (state) => {
state.isLoading = false;
state.isSuccess = false;
state.isError = false;
state.message = "";
},
},
extraReducers: (builder) => {
builder
.addCase(register.pending, (state) => {
state.isLoading = true;
})
.addCase(register.fulfilled, (state, action) => {
state.isLoading = false;
state.isSuccess = true;
state.user = action.payload;
})
.addCase(register.rejected, (state, action) => {
state.isLoading = false;
state.isError = true;
state.message = action.payload;
state.user = null;
})
.addCase(login.pending, (state) => {
state.isLoading = true;
})
.addCase(login.fulfilled, (state, action) => {
state.isLoading = false;
state.isSuccess = true;
state.user = action.payload;
})
.addCase(login.rejected, (state, action) => {
state.isLoading = false;
state.isError = true;
state.message = action.payload;
state.user = null;
})
.addCase(logout.fulfilled, (state) => {
state.user = null;
});
},
});
Here's the function that handles the logout button click event:
const handleLogout = () => {
dispatch(logout());
dispatch(reset());
navigate("/");
};
Edit:
The returned callback function in the first set of codes is basically the same all throughout, it just reverts the state into the default state (null values).

The logout action is asynchronous, and the logout.fulfilled action is what clears the state.user back to null. The handleLogout function doesn't wait for the Promise to resolve though, it just dispatches the two actions and immediately navigates to "/". It's likely the case that the logout logic hasn't completed and reset the user state when the UI is checking the user state on the dashboard.
It seems that handleLogout should wait for the logout action to resolve, then issue the other action dispatch and navigate.
export const logout = createAsyncThunk("auth/logout", async () => {
return await authService.logout(); // <-- return Promise
});
const handleLogout = async () => {
await dispatch(logout()); // <-- await Promise to resolve
dispatch(reset());
navigate("/");
};

Related

Update Comments state in Redux Toolkit

I'm creating a CRUD project with React and Redux toolkit and the problem is I can't update Comments after creating a new one.
I structured Comment Slice like that:
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
import commentsService from "./commentService";
const initialState = {
comments: [],
isError: false,
isSuccess: false,
isLoading: false,
message: "",
};
export const createComment = createAsyncThunk(
"api/newcomment",
async (commentData, thunkAPI) => {
try {
const token = thunkAPI.getState().auth.user.token;
return await commentsService.createComment(commentData, token);
} catch (error) {
console.log(error);
}
}
);
export const getAllComments = createAsyncThunk(
"api/comments",
async (PostId, thunkAPI) => {
const token = thunkAPI.getState().auth.user.token;
return await commentsService.getAllComments(PostId, token);
}
);
export const deleteComment = createAsyncThunk(
"api/:id",
async (id, thunkAPI) => {
try {
const token = thunkAPI.getState().auth.user.token;
return await commentsService.deleteComment(id, token);
} catch (error) {
console.log(error);
}
}
);
export const commentsSlice = createSlice({
name: "comment",
initialState,
reducers: {
reset: (state) => initialState,
},
extraReducers: (builder) => {
builder
.addCase(createComment.pending, (state) => {
state.isLoading = true;
})
.addCase(createComment.fulfilled, (state, action) => {
state.isLoading = false;
state.isSuccess = true;
state.comments = state.comments.push(action.payload);
})
.addCase(createComment.rejected, (state, action) => {
state.isLoading = false;
state.isError = true;
state.message = action.payload;
})
.addCase(getAllComments.pending, (state) => {
state.isLoading = true;
})
.addCase(getAllComments.fulfilled, (state, action) => {
state.isLoading = false;
state.isSuccess = true;
const comments = state.comments.filter(
(comment) => comment.PostId === action.payload.PostId
);
if (comments.length === 0) {
state.comments.push(action.payload);
}
})
.addCase(getAllComments.rejected, (state, action) => {
state.isLoading = false;
state.isError = true;
state.message = action.payload;
})
.addCase(deleteComment.pending, (state) => {
state.isLoading = true;
})
.addCase(deleteComment.fulfilled, (state, action) => {
state.isLoading = false;
state.isSuccess = true;
state.comments = state.comments.filter(
(comment) => comment.id !== action.payload.id
);
})
.addCase(deleteComment.rejected, (state, action) => {
state.isLoading = false;
state.isError = true;
state.message = action.payload;
});
},
});
And CardPost where I dispatch comments:
const CardPost = ({ post }) => {
const [show, setShow] = useState(false);
const dispatch = useDispatch();
const { user } = useSelector((state) => state.auth);
const PostId = post.id;
const { comments, isLoading, isError, message } = useSelector(
(state) => state.comments
);
useEffect(() => {
if (isError) {
console.log(message);
}
dispatch(getAllComments(PostId));
}, [dispatch, PostId, isError, message]);
//// Show Comments
<div>
<div>
<FormComment PostId={PostId} />
</div>
<div>
{comments.length > 0 ? (
<div className="comments">
{comments.map((id) => (
<div key={id}>
<CardComment post={post} />
</div>
))}
</div>
) : (
<Typography>No Comment..</Typography>
)}
</div>
</div>
When there are several comments on a Post that are displayed and I create a new one, it shows me the message "No comment.." but all the messages appear well when the page is refreshed.
Where did I make it wrong?
Is it the .addCase that I built wrong and the state is not updated or is it the useEffect dispatch?

rejectWithValue createAsyncThunk

I want to create createAsyncThunk function for register user but there is a problem when rejected action, it is throw an error
the code of authSlice:
`
export const authSlice = createSlice({
name: "auth",
initialState,
reducers: {
reset: (state) => {
state.isLoading = false;
state.isSuccess = false;
state.isError = false;
state.message = "";
},
},
extraReducers: (builder) => {
builder
.addCase(register.pending, (state) => {
state.isLoading = true;
})
.addCase(register.fulfilled, (state, action) => {
state.isLoading = false;
state.isSuccess = true;
state.user = action.payload;
})
.addCase(register.rejected, (state, action) => {
console.log({ action });
state.isLoading = false;
state.isError = true;
state.message = action.payload;
state.user = null;
})
},
});
the code of function register user
// Register user
export const register = createAsyncThunk(
"auth/register",
async (user, thunkAPI) => {
const { rejectWithValue } = thunkAPI;
try {
return await authService.register(user);
} catch (error) {
const message = error.message;
return rejectWithValue(message);
}
}
);
import axios from "axios";
const API_URL = "http://localhost:5000/auth/condidate";
const register = async (userData) => {
const response = await axios.post(API_URL, userData);
if (response.data) {
localStorage.setItem("user", JSON.stringify(response.data));
}
return response.data;
};
const authService = {
register,
};
export default authService;
`
there is no problem with register fulfilled but rejected regisiter throw an error
enter image description here
try to register user with react and redux toolkit but there is a problem with reject the action of register

How to prevent duplicate execution of redux toolkit dispatch in useEffect in react?

I'm using the rudux toolkit in react. After rendering the first page, useEffect is executed and the farmloadPost action is executed.
By the way, armloadPost.pending is executed three times. How can I make it run only once?
i tried disabled stricmode, but it same
this is my code
useEffect( () => {
dispatch(farmloadPost());
}, [dispatch]);
export const farmloadPost = createAsyncThunk(
"farm/farmloadPost",
async (data, { rejectWithValue }) => {
try {
const response = await axios.get(api);
return response.data;
} catch (error: any) {
console.log("error:",error);
return rejectWithValue(error.response.data);
}
}
);
const postSlice = createSlice({
name: "post",
initialState,
reducers: {},
extraReducers: (builder) =>
builder
// loadPost
.addCase(farmloadPost.pending, (state) => {
state.farmLoading = true;
state.farmDone = false;
state.farmError = null;
})
.addCase(farmloadPost.fulfilled, (state, action) => {
// console.log("action.payload:", action.payload);
state.farmLoading = false;
state.farmDone = true;
state.farm = action.payload;
})
.addCase(farmloadPost.rejected, (state, action) => {
state.farmLoading = false;
// state.farmDone = action.error.message;
})
.addDefaultCase((state) => state),
});

Redux Error: TypeError: state.favorites.concat is not a function

I am using redux toolkit and ran into an issue where I cannot push the results into an array which I declared. This is my initial state:
const initialState = {
favorites: [],
isError: false,
isSuccess: false,
isLoading: false,
message: "",
};
In the dashboard of my application, I want to display the favorites that are in state in which I use a useEffect as such:
useEffect(() => {
if (isError) {
console.log(message);
}
if (!user) {
navigate("/login");
}
//dispatch(getFavorites());
// return () => {
// dispatch(reset());
// };
}, [user, navigate, isError, message, dispatch]);
The problem occurs when I uncomment the dispatch to get favorites, here is the getFavorites in my service:
//Get user favorites
const getFavorites = async (token) => {
const config = {
headers: {
Authorization: `Bearer ${token}`,
},
};
const response = await axios.get(API_URL, config);
return response.data;
};
Here is how I create a favorite:
const createFavorite = async (favoriteData, token) => {
const config = {
headers: {
Authorization: `Bearer ${token}`,
},
};
var qs = require("qs");
var data = qs.stringify({
address: favoriteData.location["address1"],
phone: favoriteData.display_phone,
rating: favoriteData.rating.toString(),
price: favoriteData.price,
});
console.log(favoriteData);
const response = await axios.post(API_URL, data, config);
return response.data;
};
This is where the error occurs in the slice file where my extra-reducers are:
export const favoriteSlice = createSlice({
name: "favorite",
initialState,
reducers: {
reset: (state) => initialState,
},
extraReducers: (builder) => {
builder
.addCase(createFavorite.pending, (state) => {
state.isLoading = true;
})
.addCase(createFavorite.fulfilled, (state, action) => {
state.isLoading = false;
state.isSuccess = true;
state.favorites.concat(action.payload);
})
.addCase(createFavorite.rejected, (state, action) => {
state.isLoading = false;
state.isError = true;
state.message = action.payload;
})
.addCase(getFavorites.pending, (state) => {
state.isLoading = true;
})
.addCase(getFavorites.fulfilled, (state, action) => {
state.isLoading = false;
state.isSuccess = true;
state.favorites = action.payload;
})
.addCase(getFavorites.rejected, (state, action) => {
state.isLoading = false;
state.isError = true;
state.message = action.payload;
});
},
});
The errors occurs on the line:
state.favorites.concat(action.payload)
Am I returning the data in a wrong format? I am new to redux and am following a tutorial so I am not sure how to fix this. Any help would be appreciated.
.concat() doesn't mutate it returns a copy so you have to also have to reset your state.favorites to it here:
.addCase(createFavorite.fulfilled, (state, action) => {
state.isLoading = false;
state.isSuccess = true;
state.favorites = state.favorites.concat(action.payload);
})
As for the error, it seems your state.favorites either goes undefined or its set to a non-array/string value. .concat() only works on arrays or strings

Can't set redux state with data from local storage

I have redux-toolkit setup for managing app state. There is an 'authenticated' state variable that is false/true depending if the user is logged in. In the loginSuccessful reducer we set 'is_auth' in local storage and pull it from local storage in 'initialState'. In the login component we retrieve this variable from state to try and redirect based on the login being successful or not, but I can't seem to read the state variable after it is changed from the reducer and a redirect never happens...
here is the logic in login component
//app state
const authenticated = useSelector((state) => state.user.authenticated);
const error = useSelector((state) => state.user.error);
//form state
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const dispatch = useDispatch();
const handleLogin = (e) => {
e.preventDefault();
login({ email, password }, dispatch);
};
if (authenticated) return <Navigate to='/admin' />;
here is the reducer code:
const userSlice = createSlice({
name: 'user',
initialState: {
currentUser: null,
isLoading: false,
error: false,
authenticated: null || localStorage.getItem('is_auth'),
},
reducers: {
loginStart: (state) => {
state.isLoading = true;
state.error = false;
// state.authenticated = false;
},
loginSuccess: (state, action) => {
state.isLoading = false;
state.currentUser = action.payload;
localStorage.setItem('is_auth', true);
// state.authenticated = true;
},
loginFailure: (state) => {
state.isLoading = false;
state.error = true;
// state.authenticated = false;
},
logout: (state) => {
state.currentUser = null;
},
},
});
here is the login API request:
export const login = async (creds, dispatch) => {
dispatch(loginStart());
try {
const res = await apiRequest.post('auth/login', creds);
dispatch(loginSuccess(res.data));
} catch (err) {
dispatch(loginFailure());
}
};

Resources