JS executing out of order when dispatching redux actions - javascript

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

Related

How to change a variable state with react? [duplicate]

I have just found that in react this.setState() function in any component is asynchronous or is called after the completion of the function that it was called in.
Now I searched and found this blog (setState() State Mutation Operation May Be Synchronous In ReactJS)
Here he found that setState is async(called when stack is empty) or sync(called as soon as called) depending on how the change of state was triggered.
Now these two things are hard to digest
In the blog the setState function is called inside a function updateState, but what triggered the updateState function is not something that a called function would know about.
Why would they make setState async as JS is single threaded language and this setState is not a WebAPI or server call so has to be done on JS's thread only. Are they doing this so that Re-Rendering does not stop all the event listeners and stuff, or there is some other design issue.
You can call a function after the state value has updated:
this.setState({foo: 'bar'}, () => {
// Do something here.
});
Also, if you have lots of states to update at once, group them all within the same setState:
Instead of:
this.setState({foo: "one"}, () => {
this.setState({bar: "two"});
});
Just do this:
this.setState({
foo: "one",
bar: "two"
});
1) setState actions are asynchronous and are batched for performance gains. This is explained in the documentation of setState.
setState() does not immediately mutate this.state but creates a pending state transition. Accessing this.state after calling this method can potentially return the existing value.
There is no guarantee of synchronous operation of calls to setState and calls may be batched for performance gains.
2) Why would they make setState async as JS is a single threaded language and this setState is not a WebAPI or server call?
This is because setState alters the state and causes rerendering. This can be an expensive operation and making it synchronous might leave the browser unresponsive.
Thus the setState calls are asynchronous as well as batched for better UI experience and performance.
I know this question is old, but it has been causing a lot of confusion for many reactjs users for a long time, including me.
Recently Dan Abramov (from the react team) just wrote up a great explanation as to why the nature of setState is async:
https://github.com/facebook/react/issues/11527#issuecomment-360199710
setState is meant to be asynchronous, and there are a few really good reasons for that in the linked explanation by Dan Abramov. This doesn't mean it will always be asynchronous - it mainly means that you just can't depend on it being synchronous. ReactJS takes into consideration many variables in the scenario that you're changing the state in, to decide when the state should actually be updated and your component rerendered.
A simple example to demonstrate this, is that if you call setState as a reaction to a user action, then the state will probably be updated immediately (although, again, you can't count on it), so the user won't feel any delay, but if you call setState in reaction to an ajax call response or some other event that isn't triggered by the user, then the state might be updated with a slight delay, since the user won't really feel this delay, and it will improve performance by waiting to batch multiple state updates together and rerender the DOM fewer times.
Good article here https://github.com/vasanthk/react-bits/blob/master/patterns/27.passing-function-to-setState.md
// assuming this.state.count === 0
this.setState({count: this.state.count + 1});
this.setState({count: this.state.count + 1});
this.setState({count: this.state.count + 1});
// this.state.count === 1, not 3
Solution
this.setState((prevState, props) => ({
count: prevState.count + props.increment
}));
or pass callback this.setState ({.....},callback)
https://medium.com/javascript-scene/setstate-gate-abc10a9b2d82
https://medium.freecodecamp.org/functional-setstate-is-the-future-of-react-374f30401b6b
You can use the following wrap to make sync call
this.setState((state =>{
return{
something
}
})
Yes, setState() is asynchronous.
From the link: https://reactjs.org/docs/react-component.html#setstate
React does not guarantee that the state changes are applied immediately.
setState() does not always immediately update the component.
Think of setState() as a request rather than an immediate command to update the component.
Because they think
From the link: https://github.com/facebook/react/issues/11527#issuecomment-360199710
... we agree that setState() re-rendering synchronously would be inefficient in many cases
Asynchronous setState() makes life very difficult for those getting started and even experienced unfortunately:
- unexpected rendering issues: delayed rendering or no rendering (based on program logic)
- passing parameters is a big deal
among other issues.
Below example helped:
// call doMyTask1 - here we set state
// then after state is updated...
// call to doMyTask2 to proceed further in program
constructor(props) {
// ..
// This binding is necessary to make `this` work in the callback
this.doMyTask1 = this.doMyTask1.bind(this);
this.doMyTask2 = this.doMyTask2.bind(this);
}
function doMyTask1(myparam1) {
// ..
this.setState(
{
mystate1: 'myvalue1',
mystate2: 'myvalue2'
// ...
},
() => {
this.doMyTask2(myparam1);
}
);
}
function doMyTask2(myparam2) {
// ..
}
Hope that helps.
Imagine incrementing a counter in some component:
class SomeComponent extends Component{
state = {
updatedByDiv: '',
updatedByBtn: '',
counter: 0
}
divCountHandler = () => {
this.setState({
updatedByDiv: 'Div',
counter: this.state.counter + 1
});
console.log('divCountHandler executed');
}
btnCountHandler = () => {
this.setState({
updatedByBtn: 'Button',
counter: this.state.counter + 1
});
console.log('btnCountHandler executed');
}
...
...
render(){
return (
...
// a parent div
<div onClick={this.divCountHandler}>
// a child button
<button onClick={this.btnCountHandler}>Increment Count</button>
</div>
...
)
}
}
There is a count handler attached to both the parent and the child components. This is done purposely so we can execute the setState() twice within the same click event bubbling context, but from within 2 different handlers.
As we would imagine, a single click event on the button would now trigger both these handlers since the event bubbles from target to the outermost container during the bubbling phase.
Therefore the btnCountHandler() executes first, expected to increment the count to 1 and then the divCountHandler() executes, expected to increment the count to 2.
However the count only increments to 1 as you can inspect in React Developer tools.
This proves that react
queues all the setState calls
comes back to this queue after executing the last method in the context(the divCountHandler in this case)
merges all the object mutations happening within multiple setState calls in the same context(all method calls within a single event phase is same context for e.g.) into one single object mutation syntax (merging makes sense because this is why we can update the state properties independently in setState() in the first place)
and passes it into one single setState() to prevent re-rendering due to multiple setState() calls (this is a very primitive description of batching).
Resultant code run by react:
this.setState({
updatedByDiv: 'Div',
updatedByBtn: 'Button',
counter: this.state.counter + 1
})
To stop this behaviour, instead of passing objects as arguments to the setState method, callbacks are passed.
divCountHandler = () => {
this.setState((prevState, props) => {
return {
updatedByDiv: 'Div',
counter: prevState.counter + 1
};
});
console.log('divCountHandler executed');
}
btnCountHandler = () => {
this.setState((prevState, props) => {
return {
updatedByBtn: 'Button',
counter: prevState.counter + 1
};
});
console.log('btnCountHandler executed');
}
After the last method finishes execution and when react returns to process the setState queue, it simply calls the callback for each setState queued, passing in the previous component state.
This way react ensures that the last callback in the queue gets to update the state that all of its previous counterparts have laid hands on.
setState is asynchronous. You can see in this documentation by Reactjs
https://reactjs.org/docs/faq-state.html#why-is-setstate-giving-me-the-wrong-valuejs
https://reactjs.org/docs/faq-state.html#when-is-setstate-asynchronous
React intentionally “waits” until all components call setState() in their event handlers before starting to re-render. This boosts performance by avoiding unnecessary re-renders.
However, you might still be wondering why React doesn’t just update this.state immediately without re-rendering.
The reason is this would break the consistency between props and state, causing issues that are very hard to debug.
You can still perform functions if it is dependent on the change of the state value:
Option 1:
Using callback function with setState
this.setState({
value: newValue
},()=>{
// It is an callback function.
// Here you can access the update value
console.log(this.state.value)
})
Option 2: using componentDidUpdate
This function will be called whenever the state of that particular class changes.
componentDidUpdate(prevProps, prevState){
//Here you can check if value of your desired variable is same or not.
if(this.state.value !== prevState.value){
// this part will execute if your desired variable updates
}
}

How can we implement componentWillUnmount using react hooks?

The method componentWillUnmount() is invoked immediately before a component is unmounted and destroyed. If we use useEffect with an empty array ([]) as the second argument and put our function in return statement it will be executed after the component is unmounted and even after another component will be mounted. This is done for performance reasons as far as I understand. In order not to delay rendering.
So the question is - how can we call some function using hooks before a component gets unmounted?
What I am trying to do is an application which saves user's input as he types (without submitting form). I use setInterval to save updated text every N seconds. And I need to force save updates before the component will unmount. I don't want to use prompt by react router before navigating. This is an electron application. I appreciate any thoughts or advice on how to implement such functionality.
Update
Unfortunately, Effects with Cleanup run after letting the browser paint. More details can be found here: So What About Cleanup?. It basically means that cleanup is run after a component is unmounted and it is not the same as executing code in componentWillUnmount(). I can clearly see the sequence of calls if I put console.log statements in the cleanup code and in another component. The question is whether we can execute some code before a component is unmounted using hooks.
Update2
As I can see I should better describe my use case. Let's imagine a theoretical app which holds its data in a Redux store. And we have two components with some forms. For simplicity, we don't have any backend or any async logic. We use only Redux store as data storage.
We don't want to update Redux store on every keystroke. So we keep actual values in the local component's state which we initialize with values from the store when a component mounts. We also create an effect which sets up a setInterval for 1s.
We have the following process. A User types something. Updates are stored in the local component state until our setInterval callback is called. The callback just puts data in the store (dispatches action). We put our callback in the useEffect return statement to force save to store when the component gets unmounted because we want to save data to store in this case as soon as possible.
The problem comes when a user types something in the first component and immediately goes to the second component (faster than 1s). Since the cleanup in our first component will be called after re-rendering, our store won't be updated before the second component gets mounted. And because of that, the second component will get outdated values to its local state.
If we put our callback in componentWillUnmount() it will be called before unmounting and the store will be updated before the next component mounts. So can we implement this using hooks?
componentWillUnmount can be simulated by returning a function inside the useEffect hook. The returned function will be called just before every rerendering of the component. Strictly speaking, this is the same thing but you should be able to simulate any behaviour you want using this.
useEffect(() => {
const unsubscribe = api.createSubscription()
return () => unsubscribe()
})
Update
The above will run every time there is a rerender. However, to simulate the behaviour only on mounting and unmounting (i.e. componentDidMount and componentWillUnmount). useEffect takes a second argument which needs to be an empty array.
useEffect(() => {
const unsubscribe = api.createSubscription()
return () => unsubscribe()
}, [])
See a more detailed explanation of the same question here.
Since the introduction of the useLayoutEffect hook, you can now do
useLayoutEffect(() => () => {
// Your code here.
}, [])
to simulate componentWillUnmount. This runs during unmount, but before the element has actually left the page.
The question here is how do you run code with hooks BEFORE unmount? The return function with hooks runs AFTER unmount and whilst that doesn’t make a difference for most use cases, their are some where it is a critical difference.
Having done a bit of investigation on this, I have come to the conclusion that currently hooks simply does not provide a direct alternative to componentWillUnmount. So if you have a use case that needs it, which is mainly for me at least, the integration of non-React libs, you just have to do it the old way and use a component.
Update: see the answer below about UseLayoutEffect() which looks like it may solve this issue.
I agree with Frank, but the code needs to look like this otherwise it will run only on the first render:
useLayoutEffect(() => {
return () => {
// Your code here.
}
}, [])
This is equivalent to ComponentWillUnmount
Similar to #pritam's answer, but with an abstracted code example. The whole idea of useRef is to allow you to keep track of the changes to the callback and not have a stale closure at the time of execution. Hence, the useEffect at the bottom can have an empty dependency array to ensure it only runs when the component unmounts. See the code demo.
Reusable hook:
type Noop = () => void;
const useComponentWillUnmount = (callback: Noop) => {
const mem = useRef<Noop>();
useEffect(() => {
mem.current = callback;
}, [callback]);
useEffect(() => {
return () => {
const func = mem.current as Noop;
func();
};
}, []);
};
After a bit of research, found that - you could still accomplish this. Bit tricky but should work.
You can make use of useRef and store the props to be used within a closure such as render useEffect return callback method
function Home(props) {
const val = React.useRef();
React.useEffect(
() => {
val.current = props;
},
[props]
);
React.useEffect(() => {
return () => {
console.log(props, val.current);
};
}, []);
return <div>Home</div>;
}
DEMO
However a better way is to pass on the second argument to useEffect so that the cleanup and initialisation happens on any change of desired props
React.useEffect(() => {
return () => {
console.log(props.current);
};
}, [props.current]);
I got in a unique situation where the useEffect(() => () => { ... }, []); answers did not work for me. This is because my component never got rendered — I was throwing an exception before I could register the useEffect hook.
function Component() {
useEffect(() => () => { console.log("Cleanup!"); }, []);
if (promise) throw promise;
if (error) throw error;
return <h1>Got value: {value}</h1>;
}
In the above example, by throwing a Promise<T> that tells react to suspend until the promise is resolved. However, once the promise is resolved, an error is thrown. Since the component never gets rendered and goes straight to an ErrorBoundary, the useEffect() hook is never registered!
If you're in a similar situation as myself, this little code may help:
To solve this, I modified my ErrorBoundary code to run a list of teardowns once it was recovered
export default class ErrorBoundary extends Component {
// ...
recover() {
runTeardowns();
// ...
}
// ...
}
Then, I created a useTeardown hook which would add teardowns that needed to be ran, or make use of useEffect if possible. You'll most likely need to modify it if you have nesting of error boundaries, but for my simple usecase, it worked wonderfully.
import React, { useEffect, useMemo } from "react";
const isDebugMode = import.meta.env.NODE_ENV === "development";
const teardowns: (() => void)[] = [];
export function runTeardowns() {
const wiped = teardowns.splice(0, teardowns.length);
for (const teardown of wiped) {
teardown();
}
}
type Teardown = { registered?: boolean; called?: boolean; pushed?: boolean } & (() => unknown);
/**
* Guarantees a function to run on teardown, even when errors occur.
*
* This is necessary because `useEffect` only runs when the component doesn't throw an error.
* If the component throws an error before anything renders, then `useEffect` won't register a
* cleanup handler to run. This hook **guarantees** that a function is called when the component ends.
*
* This works by telling `ErrorBoundary` that we have a function we would like to call on teardown.
* However, if we register a `useEffect` hook, then we don't tell `ErrorBoundary` that.
*/
export default function useTeardown(onTeardown: () => Teardown, deps: React.DependencyList) {
// We have state we need to maintain about our teardown that we need to persist
// to other layers of the application. To do that, we store state on the callback
// itself - but to do that, we need to guarantee that the callback is stable. We
// achieve this by memoizing the teardown function.
const teardown = useMemo(onTeardown, deps);
// Here, we register a `useEffect` hook to run. This will be the "happy path" for
// our teardown function, as if the component renders, we can let React guarantee
// us for the cleanup function to be ran.
useEffect(() => {
// If the effect gets called, that means we can rely on React to run our cleanup
// handler.
teardown.registered = true;
return () => {
if (isDebugMode) {
// We want to ensure that this impossible state is never reached. When the
// `runTeardowns` function is called, it should only be ran for teardowns
// that have not been able to be hook into `useEffect`.
if (teardown.called) throw new Error("teardown already called, but unregistering in useEffect");
}
teardown();
if (isDebugMode) {
// Because `teardown.registered` will already cover the case where the effect
// handler is in charge of running the teardown, this isn't necessary. However,
// this helps us prevent impossible states.
teardown.called = true;
}
};
}, deps);
// Here, we register the "sad path". If there is an exception immediately thrown,
// then the `useEffect` cleanup handler will never be ran.
//
// We rely on the behavior that our custom `ErrorBoundary` component will always
// be rendered in the event of errors. Thus, we expect that component to call
// `runTeardowns` whenever it deems it appropriate to run our teardowns.
// Because `useTeardown` will get called multiple times, we want to ensure we only
// register the teardown once.
if (!teardown.pushed) {
teardown.pushed = true;
teardowns.push(() => {
const useEffectWillCleanUpTeardown = teardown.registered;
if (!useEffectWillCleanUpTeardown) {
if (isDebugMode) {
// If the useEffect handler was already called, there should be no way to
// re-run this teardown. The only way this impossible state can be reached
// is if a teardown is called multiple times, which should not happen during
// normal execution.
const teardownAlreadyCalled = teardown.called;
if (teardownAlreadyCalled) throw new Error("teardown already called yet running it in runTeardowns");
}
teardown();
if (isDebugMode) {
// Notify that this teardown has been called - useful for ensuring that we
// cannot reach any impossible states.
teardown.called = true;
}
}
});
}
}
It does not matter wether the returned function from useEffect gets called before or after the component unmounted: You still have access to the states valuey through the closure:
const [input, setInput] = useState(() => Store.retrieveInput());
useEffect(() => {
return () => Store.storeInput(input); // < you can access "input" here, even if the component unmounted already
}, []);
If you don't manage the input in the components state, your whole structure is broken and should be changed to manage state at the right place. In your case, you should lift the shared input state of the components to the parent.
ReactJS docs on hooks specify this:
Effects may also optionally specify how to “clean up” after them by
returning a function.
So any function you return in your useEffect hook, will be executed when the component unmounts, as well as before re-running the effect due to a subsequent render.

React-redux : listening to state changes to trigger action

I have my redux state like this:
{
parks: [
{
_id:"ad1esdad",
fullName : "Some Name"
},
{
_id:"ad1es3s",
fullName : "Some Name2"
}
],
parkInfo: {
id : "search Id",
start_time : "Some Time",
end_time : "Some Time"
}
}
I have a parkSelector component from which a user selects parkId and start_time and end_time
import React, { Component } from 'react';
import { changeParkInfo } from '../../Actions';
class ParkSelector extends Component {
constructor(props) {
super(props);
this.handleApply = this.handleApply.bind(this);
this.rederOptions = this.rederOptions.bind(this);
this.state = {
startDate: moment().subtract(1, 'days'),
endDate: moment(),
parkId : this.props.parks[0]
};
}
handleApply(event) {
this.setState({
parkId : event.target.parkId.value
startDate: event.target.start_time.value,
endDate: event.target.end_time.value,
});
this.props.changeParkInfo(this.state.parkId,this.state.startDate,this.state.endDate);
}
rederOptions(){
return _.map(this.props.parks,(park,index)=>{
return(
<option value={park._id} key={park._id}>{park.profile.fullName}</option>
);
});
}
render() {
return (
<div className="row">
<div className="pb-4 col-sm-3">
<form onSubmit={this.handleApply}>
<select name="parkId" value={this.state.parkId} className="form-control input-sm">
{this.rederOptions()}
</select>
<input name="start_time" type="date" />
<input name="end_time" type="date" />
<button type="submit">Apply</button>
</form>
</div>
</div>
)
}
}
function mapStateToProps(state){
return {
parks : state.parks
};
}
export default connect(mapStateToProps,{ changeParkInfo })(ParkSelector);
I have another component 'stats' which needs to displays information related with parkInfo which will be loaded my api request.
import React, { Component } from 'react';
import StatsCard from '../../components/StatsCard';
import { getDashboardStats } from '../../Actions';
class Dashboard extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="animated fadeIn">
<div className="row">
<StatsCard text="Revenue Collected" value={9999} cardStyle="card-success" />
<StatsCard text="Total Checkins" value={39} cardStyle="card-info" />
<StatsCard text="Total Checkouts" value={29} cardStyle="card-danger" />
<StatsCard text="Passes Issued" value={119} cardStyle="card-warning" />
</div>
</div>
)
}
}
function mapStateToProps(state){
return {
parkInfo : state.parkInfo,
dashboardStats : state.dashboardStats
};
}
export default connect(mapStateToProps,{ getDashboardStats })(Dashboard);
I need to call getDashboardStats action (which makes api call and stores in results in dashboardStats of the redux state) whenever the redux state of parkInfo changes.
What is the best way to call this action, I have tried componentWillUpdate but it keeps on updating infinitely. What is best practice for this scenario ?
I had a similar problem but found a suitable approach. I believe the problem has to do with how the responsibilities of reducers and subscribers in a Redux app are often interpreted. My project did not use React, it was Redux-only, but the underlying problem was the same. To put emphasis on answering the underlying problem, I approach the subject from a general perspective without directly referring to your React-specific code.
Problem re-cap: In the app, multiple actions P,Q,R can cause a state change C. You want this state change C to trigger an asynchronous action X regardless of the action that originally caused the state change C. In other words, the async action X is coupled to the state change but intentionally decoupled from the wide range of actions (P,Q,R) that could cause the change C. Such situation does not happen in simple hello-todo examples but does happen in real-world applications.
Naïve answer 1: You cannot trigger another action, it is going to cause infinite loop.
Naïve answer 2: You cannot trigger another action, reducer must not trigger actions or cause any side effects.
Although both naïve answers are true, their base assumptions are wrong. The first wrongly assumes the action X is triggered synchronously and without any stopping condition. The second wrongly assumes the action X is triggered in a reducer.
Answer:
Trigger the action X in a subscriber (aka renderer) and in asynchronous manner. It might sound weird at first but it is not. Even the simplest Redux applications do it. They listen state changes and act based on the change. Let me explain.
Subscribers, in addition to rendering HTML elements, define how actions are triggered in a response to user behaviour. As well as dealing with user behaviour, they can define how actions are triggered in a response to any other change in the world. There is little difference between a user clicking a button after a five seconds and a setTimeout triggering an action after five seconds. As well as we let subscribers to bind an action to a click event or, say, found GPS location, we can let them bind an action to a timeout event. After init, these bindings are allowed to be modified at each state change just like how we can re-render a button or the whole page at a state change.
An action triggered by setTimeout will cause a loop-like structure. Timeout triggers an action, reducers update the state, redux calls subscribers, subscribers set a new timeout, timeout triggers action etc. But again, there is little difference to the loop-like structure caused by normal rendering and binding of events with user behaviour. They are both asynchronous and intended cyclic processes that allow the app to communicate with the world and behave as we like.
Therefore, detect your state change of interest in a subscriber and freely trigger the action if the change happened. Use of setTimeout can be recommended, even with the delay of zero, just to keep the event loop execution order clear to you.
A busy loop is of course a problem and must be avoided. If the action X itself causes such state change that will immediately trigger it again, then we have a busy loop and the app will stop to respond or become sluggish. Thus, make sure the triggered action does not cause such a state change.
If you like to implement a repeating refresh mechanism, for example to update a timer each second, it could be done in same manner. However, such simple repetition does not need to listen state changes. Therefore in those cases it is better to use redux-thunk or write an asynchronous action creator otherwise. That way the intention of the code becomes easier to understand.
If my understanding is correct, you need to make the API call to get the dashboardStats in your Dashboard component, whenever the parkInfo changes.
The correct life-cycle hook in this scenario would be the componentWillReceiveProps
componentWillReceiveProps(nextProps){
// this check makes sure that the getDashboardStats action is not getting called for other prop changes
if(this.props.parkInfo !== nextProps.parkInfo){
this.props.getDashboardStats()
}
}
Also note that, componentWillReceiveProps will not be called for the first time, so you may have to call the this.props.getDashboardStats() in componentDidMount too.
Goal: A change in parkInfo redux-state should prompt Dashboard to dispatch getDashboardInfo and re-render. (This behavior will also be similar in other components).
I use babel transform-class-properties, syntax is slightly different.
example:
// SomeLayout.js
import ParkSelector from 'containers/ParkSelector'
import Dashboard from 'containers/Dashboard'
const SomeLayout = () => {
return (
<div>
<ParkSelector />
<Dashboard />
</div>
)
}
export default SomeLayout
-
// Dashboard.js
// connect maps redux-state to *props* not state, so a new park selection
// will not trigger this component to re-render, so no infinite loop there
#connect((store) => ({ currentParkId: store.parkInfo.id }, //decorator syntax
{ getDashboardStats })
)
class Dashboard extends Component {
state = {
currentId: this.props.currentParkID,
parkInfoFoo: '',
parkInfoBar: ''
}
// using null for when no park has been selected, in which case nothing runs
// here.
componentWillReceiveProps(nextProps) {
// when Dashboard receives new id via props make API call
// assumes you are setting initial state of id to null in your reducer
if (nextProps.currentParkId !== null) {
getDashboardStats(`someurl/info/${nextProps.id}`).then((data) => {
// update state of Dashboard, triggering a re-render
this.setState({
currentId: nextProps.id
parkInfoFoo: data.foo,
parkInfoBar: data.bar
})
})
}
}
render() {
const { currentId, parkInfoFoo } = this.state
if (currentId !== null) {
return <span>{parkInfoFoo}</span>
}
return null
}
}
export default Dashboard
I think it should be this way:
Your component ParkSelector changes, you trigger the action via dispatch to changeParkInfo action.
This action does the AJAX and on it's success
1) you update the store state of parkInfo via another action.
2) you send getDashboardStats.
Now when point (2) is success, it will update the store state dashboardStats.
Next in your Dashboard you should not connect with parkInfo, reason: you are not using parkInfo in the dashboard.
Inside the dashboard component you should call action getDashboardStats in componentDidMount() for loading up the dashboardStats for first time when component loads up.
The idea in nutshell is when data changes action should call the facade of actions and make the state change.
What you trying is trigger an action which changes state that goes into component via props that triggers another action so on.. thus there is a infinite loop as you have written the component action in componentWillUpdate.
Hope this clarifies your question.

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-redux store not updating within onClick function

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.

Categories

Resources