0

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));
    }
}
Drew Reese
  • 165,259
  • 14
  • 153
  • 181
  • 1
    `console.log(post);` is going to log the current post, not the one you just dispatched an action to fetch. Look at what is rendered on the screen and it's the correct post text that was fetched. If you have the redux dev tools/extension you can also inspect the store and I'm sure you'll find the state there, and if you are using redux-persist or otherwise persisting the store to localStorage you'll retain the state after a page reload (*assuming you rehydrate the store*). – Drew Reese Nov 03 '22 at 22:57
  • Does this answer your question? [The useState set method is not reflecting a change immediately](https://stackoverflow.com/questions/54069253/the-usestate-set-method-is-not-reflecting-a-change-immediately) – Konrad Nov 03 '22 at 23:00
  • 1
    @KonradLinkowski Same principle but different issue, this is redux store, not React state. – Drew Reese Nov 03 '22 at 23:01
  • @DrewReese alright so I put the console.log in the redux file for getPostAsync & then it worked correctly displaying the data for each post even w/ a refresh. 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. I'm still not sure how to fix it so it just shows the current Post's data w/out having to overwrite a previous Post's data really quickly – Gerald Thomas Nov 03 '22 at 23:06

1 Answers1

0

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

Drew Reese
  • 165,259
  • 14
  • 153
  • 181
  • I gave this a try but it still flickers. The only way it doesn't flicker is if I was on Post 1, went back or clicked a different link, then clicked post 1 again. Not sure if that information helps. I do think the solution is about clearing the state it's just figuring the code out to get it to do it lol – Gerald Thomas Nov 03 '22 at 23:26
  • @GeraldThomas I see. Think you could create a *running* [codesandbox](https://codesandbox.io/) demo that reproduces the issue that we could inspect live? – Drew Reese Nov 03 '22 at 23:27
  • I could give that a try. Let me start one & paste the code over & install the deps – Gerald Thomas Nov 03 '22 at 23:29
  • Sorry about the wait. I was using a server on my own localhost5000 as well so I just made a herkouapp api to use https://codesandbox.io/s/silly-night-gzdvti?file=/src – Gerald Thomas Nov 03 '22 at 23:59
  • I also noticed another issue in that I was trying to show the Register & Login in my NavBar.js if there wasn't a token in localStorage but if there was a token to show a Logout link instead. In order to get it to work you have to refresh after logging in or registering or else it will show the Login & Register in the nav when it should show the Logout only. I feel like I coded for 2 days straight & still have all these issues – Gerald Thomas Nov 04 '22 at 00:02
  • @GeraldThomas Is there a way you can mock the authentication and posts apis so we don't need to actually authenticate? We just need to test/inspect the `"/posts"` and `"/posts/:postId"` routes and the interactions between them. Or do you have test credentials we could use? – Drew Reese Nov 04 '22 at 00:06
  • The register should be working as far as I remember. But if not, I was just using username Batman or Bowser both with a password of 1234 – Gerald Thomas Nov 04 '22 at 00:10
  • You'll notice when logging in the bug that the navbar still has login & register links in it but if you refresh it will switch them to a logout link. I'm not sure how to fix that bug without forcing a refresh using window.location.reload() on it. I wanted to use a ternary in my NavBar.js file where if there's a token you see logout else you see register & login but it was saying my token wasn't specified in the console log so I set them up differently using the && operator for each – Gerald Thomas Nov 04 '22 at 00:14
  • @GeraldThomas When I implement my solution above to your code I don't see the "flicker" or any previous state, it got cleared out as I would expect. Here's a [fork](https://codesandbox.io/s/my-state-seems-to-disappear-if-i-refresh-the-page-or-if-i-move-to-a-different-li-mpmvjg) of your sandbox. – Drew Reese Nov 04 '22 at 00:21
  • I don't notice the flicker either but if you refresh the page after logging in (you'll know if you see register & login switch to logout in the navbar) then when going through posts I see the flicker happen. maybe this issue was due to the navbar issue I'm having with the token – Gerald Thomas Nov 04 '22 at 00:29
  • @GeraldThomas I still don't see any "flicker". Are you just referring to the page loading and then a moment later you see the fetched post data get rendered when the store is updated? – Drew Reese Nov 04 '22 at 00:33
  • By flicker I mean if you were on the post before & click it again there doesn't seem to be much change but if you go back or to the posts link & then try to click a different post it looks like there's a quick flash moreso than going from same post to same post. it's definitely subtle maybe I'm just nitpicking it because I want to make a portfolio piece off this foundation that I'd add content & styling to obviously – Gerald Thomas Nov 04 '22 at 00:37
  • @GeraldThomas I see. Yeah.... there's going to be *some* delay between navigating to the page and it renders and dispatches the action to fetch data, and then updating the store and triggering a rerender to display the fetched data. It's common to conditionally render a loading indicator while fetching data. The new `react-router-dom@6.4` Data APIs sort of try to close this gap, but then you're still waiting *somewhere* while the data is fetched. It's not really anything you can avoid, but you can make the wait as pleasant as possible for users. – Drew Reese Nov 04 '22 at 00:43
  • @GeraldThomas As for the navbar you basically need to store some auth state in the app so a React rerender can be triggered when it updates. I can add some basic functionality to the sandbox and update my answer. I'll update my answer when I've a bit more time. – Drew Reese Nov 04 '22 at 00:44
  • Thanks a lot for all your help by the way. Definitely one of the most helpful people I've come across on SO – Gerald Thomas Nov 04 '22 at 00:45
  • @GeraldThomas I've updated answer and linked sandbox. Please take a look when you can. – Drew Reese Nov 04 '22 at 02:04