I'm messing around practicing concepts using react, react-router6, & redux-toolkit. Here is a quick YT short showing the problem: https://www.youtube.com/shorts/jwidQfibVEo
Here is the Post.js file that contains the logic for getting a post
import React, { useEffect } from 'react'
import { useNavigate, useParams } from 'react-router-dom'
import jwtDecode from 'jwt-decode';
import { useSelector, useDispatch } from 'react-redux';
import { getPostAsync, postsSelector, deletePostAsync } from '../redux/slices/postSlice';
const Post = () => {
const { post } = useSelector(postsSelector);
const { postId } = useParams();
const navigate = useNavigate();
const dispatch = useDispatch();
useEffect(() => {
dispatch(getPostAsync(postId));
**where the console.logs in the video come from**
console.log(post);
}, [dispatch, postId]);
const handleDelete = () => {
dispatch(deletePostAsync(postId));
navigate('/posts')
}
const handleUpdate = () => {
navigate(`/posts/${postId}/update-post`)
}
//decode the token to get the username property off it to assign as a post's author
const token = localStorage.getItem('user');
const decode = jwtDecode(token);
return (
<div>
<h1>{post.title}</h1>
<h2>{post.body}</h2>
<p>Author: {post.author}</p>
{decode._id === post.user_id &&
<div className='button-group'>
<button onClick={handleDelete}>Delete</button>
<button onClick={handleUpdate}>Update</button>
</div>}
</div>
)
}
export default Post
Here is the initial state portion of the redux file
export const postSlice = createSlice({
name: 'posts',
initialState: {
posts: [],
post: {},
loading: false,
error: false
}
Here is the logic in redux setting the payload to the state for a post
getPost: (state, action) => {
state.post = action.payload;
}
Here is the logic for making a GET request for a api/posts/:postId route that sets the data as the payload for the state using getPost()
export const getPostAsync = (postId) => async (dispatch) => {
try {
const response = await axiosWithAuth().get(`api/posts/${postId}`);
dispatch(getPost(response.data));
} catch (error) {
dispatch(setError(error));
}
}
The main issue was still that when I was on say Post 1 & then go back to posts & then click the link for Post 3, it will show Post 1's data for a quick millisecond before showing the data for Post 3.
This is because the current post data is still in the redux store until you fetch a new post and replace it. You can clear out the post
state when the Post
component unmounts when navigating back to the main "/posts"
page.
Example:
useEffect(() => {
dispatch(getPostAsync(postId));
return () => {
dispatch(getPost(null)); // <-- wipe out the post state when unmounting
};
}, [dispatch, postId]);
To help with momentarily rendering blank content you can conditionally render a loading indicator while the data is fetched.
if (!post) {
return <h1>Loading Post...</h1>;
}
return (
<div>
<h1>{post.title}</h1>
<h2>{post.body}</h2>
<p>Author: {post.author}</p>
{decode._id === post.user_id && (
<div className="button-group">
<button onClick={handleDelete}>Delete</button>
<button onClick={handleUpdate}>Update</button>
</div>
)}
</div>
);
To address the navbar and the login/logout link you'll want to store the token in the redux store (persisted to/initialized from localStorage).
auth.slice.js
import { createSlice } from "@reduxjs/toolkit";
const authSlice = createSlice({
name: "auth",
initialState: {
token: JSON.parse(localStorage.getItem("user"))
},
reducers: {
login: (state, action) => {
state.token = action.payload;
localStorage.setItem("user", JSON.stringify(action.payload));
},
logout: (state) => {
state.token = null;
localStorage.removeItem("user");
}
}
});
export const actions = {
...authSlice.actions
};
export default authSlice.reducer;
store
import { configureStore } from "@reduxjs/toolkit";
import postSlice from "./slices/postSlice";
import authSlice from './slices/auth.slice';
export const store = configureStore({
reducer: {
auth: authSlice,
posts: postSlice
}
});
Navbar
const NavBar = () => {
const token = useSelector(state => state.auth.token);
const dispatch = useDispatch();
return (
<div>
<NavLink to="/" className="navlinks">
Home
</NavLink>
<NavLink to="/posts" className="navlinks">
Posts
</NavLink>
{token && (
<div>
<NavLink
to="/"
className="navlinks"
onClick={() => {
dispatch(resetPostsAsync());
dispatch(resetPostAsync());
dispatch(authActions.logout());
}}
>
Logout
</NavLink>
</div>
)}
{!token && (
<div>
<NavLink to="/register" className="navlinks">
Register
</NavLink>
<NavLink to="/login" className="navlinks">
Login
</NavLink>
</div>
)}
</div>
);
};
Login
const Login = () => {
const dispatch = useDispatch();
...
const handleSubmit = (e) => {
e.preventDefault();
axiosWithAuth()
.post("api/auth/login", user)
.then((res) => {
dispatch(authActions.login(res.data.token));
navigate("/posts");
})
.catch((err) => console.log(err));
};
return (
...
);
};