react-redux store not updating within onClick function - javascript

I'm experiencing this weird issue where my react-redux store is updating, but is not updating within the function that calls the actions.
this.props.active is undefined, then I set it to an integer with this.props.actions.activeSet(activeProc), but it remains undefined and enters the next if condition.
I know my app is working because everything else works with this.props.active having the correct value.
Is this supposed to happen?
edit:
After doing some testing, it appears that the state remains the same inside the onClick function.
All calls to console.log(this.props) made within the onClick function show no change to the state, but adding setTimeout(() => {console.log(this.props)}, 1) at the end to test shows that the state is being updated.
Other parts of the app are working as intended, with state changes applied immediately.
But I still don't understand what is going on.
Component function code
() => {
console.log(this.props.active); // undefined
if (this.props.active === undefined && this.props.readyQueue.length > 0) {
let activeProc = this.props.readyQueue[0];
this.props.actions.readyPop();
this.props.actions.activeSet(activeProc); // set to an integer
this.props.actions.execStateSet("Running");
}
console.log(this.props.active); // still remains undefined
if (this.props.active === undefined) {
this.props.actions.execStateSet("Idle");
}
}
function mapStateToProps(state, props) {
return {
active: state.ProcessReducer.active,
};
}
Action code
export const activeSet = (procId) => {
return {
type: 'ACTIVE_SET',
procId
}
}
Reducer code
case 'ACTIVE_SET':
return Object.assign({}, state, {
active: action.procId
});

Your Redux state updates synchronously with the dispatch of your action. Your reducer has executed by the time the dispatch call returns.
However, React isn't Redux. Redux tells React-Redux's wrapper component that the state has changed. This also happens before dispatch returns.
React-Redux then tells React that the component needs to be rerendered by calling forceUpdate. React then waits until it feels it's a good time to take care of that. I haven't looked, but it probably uses setImmediate or equivalent but it's async. This allows React to batch updates and maybe there are other reasons.
In any case, the React-Redux wrapper component will get rendered by React when the time comes and it'll use your mapStateToProps to distill theprops out of the state and then passes them to React as props for your actual component. Then, when React feels it's an okay time, it calls your render method or function. It may do all kinds of things in before that, such as calling componentWillReceiveProps or rendering some other component that also needs rendering. In any case it's none of our business. React does its thing. But when your Render function is called, your props will now reflect the new state.
You shouldn't rely on new state in an onClick handler. The onClick should only call the bound action creator, which I guess is now more aptly called an action dispatcher. If something needs to be done with the new state, you should use Redux-Thunk middleware and create a thunked action creator. These have access to getState and if they don't perform any internal async stuff, then the entire action can actually be just as synchronous as a simple dispatch (not that you'd need that in a simple onClick handler).
Finally, React is very asynchronous in nature. Think of it as telling React what you want (component + props) and letting React take it from there. If React needs to know how to turn a component into DOM elements, it'll call your component's render function. How or when React does is thing is an implementation detail that doesn't concern us.

Related

JS executing out of order when dispatching redux actions

I have a more complex version of the following pseudo-code. It's a React component that, in the render method, tries to get a piece of data it needs to render from a client-side read-through cache layer. If the data is present, it uses it. Otherwise, the caching layer fetches it over an API call and updates the Redux state by firing several actions (which theoretically eventually cause the component to rerender with the new data).
The problem is that for some reason it seems like after dispatching action 1, control flow moves to the top of the render function again (starting a new execution) and only way later continues to dispatch action 2. Then I again go to the top of the render, and after a while I get action 3 dispatched.
I want all the actions to fire before redux handles the rerender of the component. I would have thought dispatching an action updated the store but only forced components to update after the equivalent of a setTimeout (so at the end of the event loop), no? Is it instead the case that when you dispatch an action the component is updated synchronously immediately, before the rest of the function where the dispatch happens is executed?
class MyComponent {
render() {
const someDataINeed = CachingProvider.get(someId);
return (
<div>{someDataINeed == null ? "Loading" : someDataINeed }</div>
);
}
}
class CachingProvider {
get(id) {
if(reduxStoreFieldHasId(id)) {
return storeField[id];
}
store.dispatch(setLoadingStateForId(id));
Api.fetch().then(() => {
store.dispatch(action1);
store.dispatch(action2);
store.dispatch(action3);
});
return null;
}
}
In addition to #TrinTragula's very important answer:
This is React behaviour. Things that trigger rerenders that are invoked synchronously from an effect/lifecycle or event handler are batched, but stuff that is invoked asnychronously (see the .then in your code) will trigger a full rerender without any batching on each of those actions.
The same behaviour would apply if you would call this.setState three times in a row.
You can optimize that part by adding batch which is exported from react-redux:
Api.fetch().then(() => {
batch(() => {
store.dispatch(action1);
store.dispatch(action2);
store.dispatch(action3);
})
});
You should never invoke heavy operations inside of a render function, since it's going to be triggered way more than you would like to, slowing down your app.
You could for example try to use the useEffect hook, so that your function will be executed only when your id changes.
Example code:
function MyComponent {
useEffect(() => {
// call your method and get the result in your state
}, [someId]);
return (
<div>{someDataINeed == null ? "Loading" : someDataINeed }</div>
);
}

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.)

Can I call APIs in componentWillMount in React?

I'm working on react for last 1 year. The convention which we follow is make an API call in componentDidMount, fetch the data and setState after the data has come. This will ensure that the component has mounted and setting state will cause a re-render the component but I want to know why we can't setState in componentWillMount or constructor
The official documentation says that :
componentWillMount() is invoked immediately before mounting occurs. It
is called before render(), therefore setting state in this method will
not trigger a re-rendering. Avoid introducing any side-effects or
subscriptions in this method.
it says setting state in this method will not trigger a re-rendering, which I don't want while making an API call. If I'm able to get the data and able to set in the state (assuming API calls are really fast) in componentWillMount or in constructor and data is present in the first render, why would I want a re-render at all?
and if the API call is slow, then setState will be async and componentWillMount has already returned then I'll be able to setState and a re-render should occur.
As a whole, I'm pretty much confused why we shouldn't make API calls in constructor or componentWillMount. Can somebody really help me understand how react works in such case?
1. componentWillMount and re-rendering
Compare this two componentWillMount methods.
One causes additional re-render, one does not
componentWillMount () {
// This will not cause additional re-render
this.setState({ name: 'Andrej '});
}
componentWillMount () {
fetch('http://whatever/profile').then(() => {
// This in the other hand will cause additional rerender,
// since fetch is async and state is set after request completes.
this.setState({ name: 'Andrej '});
})
}
.
.
.
2. Where to invoke API calls?
componentWillMount () {
// Is triggered on server and on client as well.
// Server won't wait for completion though, nor will be able to trigger re-render
// for client.
fetch('...')
}
componentDidMount () {
// Is triggered on client, but never on server.
// This is a good place to invoke API calls.
fetch('...')
}
If you are rendering on server and your component does need data for rendering, you should fetch (and wait for completion) outside of component and pass data thru props and render component to string afterwards.
ComponentWillMount
Now that the props and state are set, we finally enter the realm of Life Cycle methods
That means React expects state to be available as render function will be called next and code can break if any mentioned state variable is missing which may occur in case of ajax.
Constructor
This is the place where you define.
So Calling an ajax will not update the values of any state as ajax is async and constructor will not wait for response. Ideally, you should use constructor to set default/initial values.
Ideally these functions should be pure function, only depending on parameters. Bringing ajax brings side effect to function.
Yes, functions depend on state and using this.setState can bring you such issues (You have set value in state but value is missing in state in next called function).
This makes code fragile. If your API is really fast, you can pass this value as an argument and in your component, check if this arg is available. If yes, initialise you state with it. If not, set it to default. Also, in success function of ajax, you can check for ref of this component. If it exist, component is rendered and you can call its state using setState or any setter(preferred) function.
Also remember, when you say API calls are really fast, your server and processing may be at optimum speed, but you can never be sure with network.
If you need just data only at first run and if you are ok with that. You can setState synchronously via calling a callback.
for eg:
componentWillMount(){
this.setState({
sessionId: sessionId,
}, () => {
if (this.state.hasMoreItems = true) {
this.loadItems() // do what you like here synchronously
}
});
}

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.

React - updating state during render produces errors

I'm new to React and am trying to update the state of a parent component from the child everytime an onChange action happens. The onchange action comes from an input box that when letters are typed it updates the state of searchInputVal with the value of what has been typed. I have a parent <App/> component with the following properties and states here:
updateSampleFilteredState(filteredSamples) {
this.setState({
samples: filteredSamples
});
},
getInitialState () {
return {
samples:allSamples,
searchInputVal:""
}}
I pass the properties and states down to a child component here:
updateNewSampleState(filteredSamples){
return (
this.props.updateSampleFilteredState(filteredSamples)
)
}
render() {
const filteredSamples = this.props.samples.filter(sample => {
return sample.sampleFamily.toLowerCase().indexOf(this.props.searchInputVal.toLowerCase()) !== -1;
});
this.updateNewSampleState(filteredSamples);
return <div className="samples-container-inner-styling">
{
filteredSamples.map((sample) => {
return (...
Before I added the line this.updateNewSampleState(filteredSamples); the child component would render out the filtering just fine but obviously not update the state of sample with the new filtered state. When I the line this.updateNewSampleState(filteredSamples); to execute the function in the component to set the new state I get a list of re-occuring errors that eventually make my app crash. The errors say something about an anti pattern. I'm not sure how else to update the state?
You should't be updating the state from the render function, and you are facing the reason why that's a bad way to do things. Every time you call the setState the component re-renders, so if you call it inside the render function it will be called again and so on... You should ask yourself why are you calling that function there. I guess you could just do it in the onChange function you are using for the input.
As already mentioned by #César, setting the state in the renderer doesn't make sense, since setting the state triggers a rerender of the component, so you basically get something like an infinite render loop.
Given that you are computing filteredSamples only from the props, you could compute that state in the constructor:
The constructor is the right place to initialize state.
However, note the following when deriving state from props in the constructor:
It's okay to initialize state based on props if you know what you're doing. [...]
Beware of this pattern, as it effectively "forks" the props and can lead to bugs. Instead of syncing props to state, you often want to lift the state up.
If you "fork" props by using them for state, you might also want to implement componentWillReceiveProps(nextProps) to keep the state up-to-date with them. But lifting state up is often easier and less bug-prone.

Categories

Resources