I've just started to do some experiments with redux
and react-redux
.
Note: I'm using Hooks, so I'm using useSelector()
and useDispatch()
custom hooks from react-redux
.
Imagine I have some piece of state to get a blogPost
from DB based on a slug
.
My state.blogPost:
{
loading: false, // TRUE while fetching
error: null, // TO keep network errors
blogPost: null // TO keep the blogPost
}
BlogPostContainer.js
I'm dispatching the actions and the async call inside a useEffect
.
Note1: I'm doing the async job manually, cause I'm not using redux-thunk
yet.
Note2: I'm using react-router-dom
so the slugs come from props.match.params.slug
.
Example: myApp.net/blog/some-blog-post-slug
// ...
const { loading, error, blogPost } = useSelector(state => state.blogPost);
const dispatch = useDispatch();
useEffect(() => {
dispatch({ type: "GET_BLOGPOST_START", payload: props.match.params.slug }); // START ACTION
mockAPI(props.match.params.slug)
.then((result) => {
dispatch({ type: "GET_BLOGPOST_SUCCESS", payload: result }); // SUCCESS ACTION
})
.catch((err) => {
dispatch({ type: "GET_BLOGPOST_FAIL", payload: err }); // FAIL ACTION
})
}, [dispatch,props.match.params.slug]);
// RETURN STATEMENT
return (
loading ?
<div>Loading...</div>
: error ?
<ErrorPage/>
: <BlogPostPage blogPost={blogPost}/>
);
QUESTION
This is working fine for the first loaded blogPost (because the state is completely empty at that point).
Now imagine that I go back to the home page an click on another blogPost.
From the second time on, when my BlogPostContainer
renders, there's already something present in my state (either a blogPost
or an error
that was loaded last time for the previous blogPost slug).
And as a result of this, I see a flicker of that information displayed on screen right before my useEffect
runs. When it runs, loading
will be set to true by the GET_BLOGPOST_START
action, and I'll see the loading.
I would like to see the loading page immediately. Without any flickering with old state data.
How do people usually handle this while using redux?
Do I need a local loading
state?
Should I dispatch an action to turn loading
to true
when my component unmounts? So it will be already true
when my component mounts again for the next slug
?
Should I use useLayoutEffect
so the effect will run before rendering and I won't see the flickering? (it works, but it feels wrong).
What is the best solution for this kind of situation?
EXTRA
My reducer:
const initialState = {
loading: false,
error: null,
blogPost: null
};
function getBlogPostReducer(state = initialState, action) {
switch (action.type) {
case "GET_BLOGPOST_START": // SET LOADING TO TRUE
return {
...initialState,
loading: true
};
case "GET_BLOGPOST_SUCCESS": // SET BLOGPOST and LOADING TO FALSE
return {
...initialState,
loading: false,
blogPost: action.payload
};
case "GET_BLOGPOST_FAIL": // SET ERROR and LOADING TO FALSE
return {
...initialState,
loading: false,
error: action.payload
};
default:
return state;
}
}
export default getBlogPostReducer;
In the useEffect
return a cleanup function that will dispatch a "clear blog post" action when the component unmounts:
useEffect(() => {
dispatch({ type: "GET_BLOGPOST_START", payload: props.match.params.slug }); // START ACTION
mockAPI(props.match.params.slug)
.then((result) => {
dispatch({ type: "GET_BLOGPOST_SUCCESS", payload: result }); // SUCCESS ACTION
})
.catch((err) => {
dispatch({ type: "GET_BLOGPOST_FAIL", payload: err }); // FAIL ACTION
})
return () => dispatch({ type: "CLEAR_BLOGPOST" });
}, [dispatch,props.match.params.slug]);
And the reducer handler should remove both error
and blogpost
entries from the state:
case "CLEAR_BLOGPOST":
return initialState;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With