React Redux - quick flash of previous state before dispatch issue - javascript

I'm building a React + Redux app with a Node.js backend, and one of the features is that a user can view profiles of other users. To do this, I have a section in the Redux state called users that looks like:
{
...,
users: {
user: {},
isLoading: true
}
}
Every time the /users/:id route is rendered, a getUser(id) action is dispatched and fills the state with the received information.
The main issue is when a user views user1's profile page (therefore redux state is now filled with user1's profile) and then views user2's profile page, the dispatch getUser(2) action is called right after the page is rendered. Since user1's info is still in the state, it will flash their info for a very short time, and then show the loading spinner until the new user is loaded.
I read about dispatching a resetUser(id) action on every unmount of the page, but I'm not sure if this is the right way to go. Also, one of the features is if a user is viewing their own page, they have an edit button which redirects them to /edit-profile. If I reset the state on every unmount, I'll have to fetch their profile again (in the edit page), even though I just did that when they viewed their page.. And that doesn't seem like it makes sense.
Any ideas how to solve this? Thanks!

The render phase runs after mounting. And you stated that previous data is being shown before new data. It seems that you have asynchronous mounting:
async componentDidMount() {}
It will continue rendering even before mounting phase is completed. To avoid issue, you may use synchronous nature of mounting:
componentDidMount(){}
Where you'll call api data.
Now, when you reach to rendering phase it will have new data available before rendering and won't flash you old data.
You now may be wondering how to call api asynchronously. You can create a asynchronous function and call that function inside the synchronous componentDidMount.
componentDidMount() {
yourAsyncFunc()
}
async yourAsyncFunc() {} // api call
How can I do this with React hooks?
While using useEffect, don't implement async:
useEffect(async () =>
Implement it simply:
useEffect(() => {
// you can still use asynchronous function here
async function callApi() {}
callApi()
}, []) // [] to run in similar to componentDidMount
If you miss second parameter to useEffect then you are not ensuring it to run on mounting phase. It will run before, after, and in the rendering phase depending on case.

Implementing something like resetUser(id) seems to be the right way here. I use this approach in my current project and this does solve the problem. The other approach of removing async keyword from useEffect callback as mentioned in another answer didn't work for me (I use hooks, redux-toolkit, Typescript).
After dispatching this action, your state should look something like
{
...,
users: {
user: null,
isLoading: false,
}
}
If you are using hooks, you can dispatch the action this way:
useEffect(() => {
const ac = new AbortController();
...
return () => {
dispatch(resetUser(null));
ac.abort();
};
}, []);
Action could look something like this:
resetListingDetails(state, action) {
// Immer-like mutable update
state.users = {
...state.users,
user: null,
};
}

Related

Abort previous running async componentDidMount

We have a component, connected to the redux store, that has this componentDidMount definition:
async componentDidMount() {
const page = await fetchPage(this.props.page);
const section = await fetchSection(this.props.section);
this.props.saveDataToStore({ page, section });
}
So, every time the component is mounted, it'll run this code.
Now let's imagine that we go to another section of our app, so the component gets unmounted, and then we come back before the previous componentDidMount has finished its execution, mounting the component again and starting fetching new data. Every time the data is fetched, we save it to the redux store.
I think that there is no way to abort previous componentDidMount execution to prevent setting old data from previous calls. So, my question is, what's the best approach to follow to prevent old data being set?
I've been able to solve it having a function that does all of these calls (like fetchData) that handles an AbortController object, but I found it a bit verbose, so do you know what's the recommended approach to follow in this situation?
Using the AbortController or a library like axios that supports cancellable requests is a good solution, but you could also keep an instance variable that you change to false when the component unmounts and check against his before you use setState after the requests have finished.
Example
class App extends React.Component {
_isMounted = true;
async componentDidMount() {
const page = await fetchPage(this.props.page);
const section = await fetchSection(this.props.section);
if (this._isMounted) {
this.setState({ page, section });
}
}
componentWillUnmount() {
this._isMounted = false;
}
render() {
// ...
}
}

when I map my redux state with component state I am getting an error

when you click advanced sports search button I need to display drawer with my api values.
but right now when I map my redux state with component state I am getting an error.
Actions must be plain objects. Use custom middleware for async actions.
can you tell me how to map my state.
so that in future I can fix all my redux issues by myself.
providing code snippet and sandbox below.
all my map state is done in tab-demo.js
https://codesandbox.io/s/rlpv50q8qo
getSportsPlayerHistory = values => {
this.props.fetchHistorySportsDatafromURL();
};
toggleDrawer = (side, open) => () => {
if (open === true) {
this.getSportsPlayerHistory();
}
this.setState({
[side]: open
});
};
const mapDispatchToProps = dispatch => {
return {
onDeleteAllSPORTS: () => {
// console.log("called");
dispatch(deleteAllPosts());
},
addFavoriteSPORTSs: data => {
dispatch(addFavoriteSPORTSs(data));
},
fetchHistorySportsDatafromURL: () => {
dispatch(fetchHistorySportsDatafromURL());
}
};
};
Actions need to return plain objects, your fetchHistorySportsDatafromURL action returns a function. If you make your history reducer function async then you can make an async function to make your API call there and return the result to state.
API call in reducer
This works, but isn't ideal as you want your reducers to be pure functions, as-in, no side-effects, same input always produces the same output
You can also make the API request in the component's callback handler asynchronously and pass the result to the dispatched action.
API call in component then dispatched in action
This is a good solution and works great for small projects, but couples network business logic into your UI display components, which also isn't as ideal since it reduces code re-usability.
If you still want to keep your API logic separate from your component (which is a good thing), redux-thunk is a way to create asynchronous action creators, which is very similar to the pattern of your original code.
API call in action using redux-thunk
This is the most ideal as it completely de-couples business logic from your UI, meaning you can change back-end requests without touching front-end UI, and other components can now also use the same action. Good DRY principal.
Not really sure what you wanted to do with the new state, but this should get you to a good spot to handle that in your mapStateToProps function.

Memory leak in my React app because of firebase and how to avoid it?

The app I'm working on displays a user dashboard on login with a sidebar for navigation. It uses firebase. I do most of my data fetch from firebase in my async componentDidMount() and store the data in my component state. It takes a couple of seconds to finish all fetches. But if the user decides to navigate to another screen before the fetch is complete, I get the
Can't call setState on unmounted component
warning (as expected). So I do some digging and find that
if(this.isMounted()) this.setState({updates:updates})
makes the warning go away, but then I also find that using isMounted is an antipattern.
The official documentation on this issue suggests tracking the mounted state ourselves by setting _isMounted=true in componentDidMount and then set it to false in the componentWillUnmount. The only way I see to achieve this would be through a variable in component state. Turns out, setState doesn't work in componentWillUnmount. [Issue 1] (I tried calling an external function from componentWillUnmount which in turn sets the state variable. Didn't work.)
The documentation suggests another way, to use cancellable promises. But I'm clueless about how to achieve that with await firebase calls. I also couldn't find any way to stop firebase calls mid-track. [Issue 2]
So now I'm stuck with the warning and data leaks.
a. How do I resolve this problem?
b. Is this something I need to take seriously?
It's good practice to check if the component is still mounted when a request completes, if there is a risk of the component unmounting.
You don't need to put _isMounted in your component state since it will not be used for rendering. You can put it directly on the component instance instead.
Example
class MyComponent extends React.Component {
state = { data: [] };
componentDidMount() {
this._isMounted = true;
fetch("/example")
.then(res => res.json())
.then(res => {
if (this._isMounted) {
this.setState({ data: res.data });
}
});
}
componentWillUnmount() {
this._isMounted = false;
}
render() {
// ...
}
}

Callbacks using redux-thunk / redux-observable with redux

I am learning how redux works but its a lot of code to do simple things. For example, I want to load some data from the server before displaying. For editing reasons, I can't simply just use incoming props but I have to copy props data into the local state.
As far as I've learned, I have to send a Fetch_request action. If successful, a fetch_success action will update the store with new item. Then updated item will cause my component's render function to update.
In component
componentWillMount() {
this.props.FETCH_REQUEST(this.props.match.params.id);
}
...
In actions
export function FETCH_REQUEST(id) {
api.get(...)
.then(d => FETCH_SUCCESS(d))
.catch(e => FETCH_FAILURE(e));
}
...
In reducer
export function FETCH_REDUCER(state = {}, action ={}) {
switch (action.type) {
case 'FETCH_SUCCESS':
return { ...state, [action.payload.id]: ...action.payload }
...
}
Back in component
this.props.FETCH_REDUCER
// extra code for state, getting desired item from...
Instead, can I call a react-thunk function and pass some callback functions? The react-thunk can update the store and callbacks can change the component's local state.
In component
componentWillMount() {
this.props.FETCH_REQUEST(this.props.match.params.id, this.cbSuccess, this.cbFailure);
}
cbSuccess(data) {
// do something
}
cbFailure(error) {
// do something
}
...
In action
export function FETCH_REQUEST(id, cbSuccess, cbFailure) {
api.get(...)
.then(d => {
cbSuccess(d);
FETCH_SUCCESS(d);
}).catch(e => {
cbFailure(d);
FETCH_FAILURE(e);
});
}
...
Is this improper? Can I do the same thing with redux-observable?
UPDATE 1
I moved nearly everything to the redux store, even for edits (ie replaced this.setState with this.props.setState). It eases state management. However, every time any input's onChange fires, a new state is popping up. Can someone confirm whether this is okay? I'm worried about the app's memory management due to redux saving a ref to each state.
First of all, you should call your API in componentDidMount instead of componentWillMount. More on this at : what is right way to do API call in react js?
When you use a redux store, your components subscribe to state changes using the mapStateToProps function and they change state using the actions added a props through the mapDispatchToProps function (assuming you are using these functions in your connect call).
So you already are subscribing to state changes using your props. Using a callback would be similar to having the callback tell you of a change which your component already knows about because of a change in its props. And the change in props would trigger a re-render of the component to show the new state.
UPDATE:
The case you refer to, of an input field firing an onChange event at the change of every character, can cause a lot of updates to the store. As mentioned in my comments, you can use an api like _.debounce to throttle the updates to the store to reduce the number of state changes in such cases. More on handling this at Perform debounce in React.js.
The issue of memory management is a real issue in real world applications when using Redux. The way to reduce the effect of repeated updates to the state is to
Normalize the shape of state : http://redux.js.org/docs/recipes/reducers/NormalizingStateShape.html
Create memoized selectors using Reselect (https://github.com/reactjs/reselect)
Follow the advice provided in the articles regarding performance in Redux github pages (https://github.com/reactjs/redux/blob/master/docs/faq/Performance.md)
Also remember that although the whole state should be copied to prevent mutating, only the slice of state that changes needs to be updated. For example, if your state holds 10 objects and only one of them changes, you need to update the reference of the new object in the state, but the remaining 9 unchanged objects still point to the old references and the total number of objects in your memory is 11 and not 20 (excluding the encompassing state object.)

Call componentDidMount when API responds

In my project I have a call to an action that makes a webservice call and in turn dispatch actions to the result of the ws, these actions edit the store.
My problem is in :
ComponentDidUpdate () {
If (this.props.messages.length) {
Const items = this.props.messages.filter (this.isDisplayable);
This.timer = setInterval (() => {
If (items.length> 0) {
This.props.popItem (items);
} Else {
ClearInterval (this.timer);
}
}, This.props.interval);
}
}
In fact it is launched several times and I have warnings of
Warning: flattenChildren (...): Encountered two children with the same
key, 1. Child keys must be unique; When two children share a key,
only the first child will be used.
I used the componentDidMount but it launches it before api responds.
my question is:
Is that there is a way to update the component only at the response of my action, or alternatively to pass the warnings ?
try this :
componentWillReceiveProps(nextProps) {
if (this.props.messages === nextProps.messages) return;
i had some probleme and i resolve it by force update
forceUpdate () {
If (this.props.messages.length) {
...
}
}
In my project I have a call to an action that makes a webservice call and in turn dispatch actions to the result of the ws, these actions edit the store.
None of the methods componentDidMount and componentDidUpdate are good.
Observe the Store in Redux and update your component accordingly when the correct action TYPE is found.
Since you are using the Redux architecture, the state for all your components is in a single place — in the Store.
yes i know, but the problem is that componentDidUpdate is called several times which gives me the index error.
This is quite normal in React. Check this lifecycle.
What you should do is the govern the Redux architecture.
I will try today to provide some diagrams for you.
In general, anything you do will be from the global Store.
You may forget the React.Component state, and props you had in the non-Redux applications.
You typically need to use the Wrapper as a context provider around your app, where the context is the property of React.Component.
The context will be passed to all children and grandchildren so this will be the global Store organization.
Then you will need to read the Store from the context, and call the two typical methods: dispatch and subscribe.

Categories

Resources