Search code examples
reactjsreact-reduxreact-routerreact-router-domredux-toolkit

My state seems to disappear if I refresh the page or if I move to a different link a console log shows the state from the previous link


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));
    }
}

Solution

  • 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 (
        ...
      );
    };
    

    Edit my-state-seems-to-disappear-if-i-refresh-the-page-or-if-i-move-to-a-different-li