I have a couple set states on providers that trigger a bunch of effects throughout my react app. They happen back to back in a hook. The second set state has some effects that it triggers that need make sure that the first hook fully propagated through the app. Currently the first one has not made all the required changes so some functionality triggered by the second causing weird behavior. How do you ensure that the second one only happens after the first is fully propagated?
setSomeStateValue(x);
setValueToTriggersEffectsThatRelyOnUpdatesFromTheOther(y);
Here are some thing I have done that work but have their issues:
1.) settimeout(...,0)
setSomeStateValue(x);
setTimeout(otherSetState,0);
This pushes the second one to a subsequent batch. I like this one because it doesn't involve adding extra code to watch other state variables that the code maybe shouldn't be concerned with, but it does seem a little black magicy and could possibly cause hard to debug issues.
2.) monitor the stuff i need set before calling the second set state
This one seems a little more readable but involves importing and watching things that might not make sense that they belong in the related code. Basically adding a useEffect that watches everything I need set before the second call happens. Also if something changes in regards to what is needed to have the second call ready then this code will have to change as well where as the first solution should not require an update.
Both of these work but have their fallbacks. I would like to refactor the second call to account for these issues but that would be too large of a refactor at this point too make it a feasible option. Is there a native way to ensure this or another strategy here that I am missing? And if not, which one of the above solutions is better?
In such situations where a setState is dependent on a previous setState or a specific state of the component, you can potentially do two things. First,
use the second argument of setState. Here, callback is only invoked once setState is done updating the state. Hence you can get the desired synchronous behaviour.
setState(updater, [callback])
Use componentDidUpdate lifecycle method ( For Hooks, it would be just another useEffect ). If you don't want to have complex setStates in your component, simply have an effect with a dependency on the needed state and do your operations there
React.useEffect(() => {
if( desired_state){
secondSetState()
},
[ desired_state ]
}
Related
I have a component, which has an inner compnent, that holds an array in the data section.
when some value is pushed to the array, another div is created and the component re-renders.
<div class="engine" v-for="(dialect, index) in entitiesArray" :key="index" style="height: 36px;">
I want to fire an event when the ui finished. completely to re-render.
I tried the 'updated' method
from the documentation:
https://v2.vuejs.org/v2/api/#updated:~:text=also%3A%20Lifecycle%20Diagram-,updated,-Type%3A%20Function
The component’s DOM will have been updated when this hook is called, so you can perform DOM-dependent operations here. However, in most cases you should avoid changing state inside the hook. To react to state changes, it’s usually better to use a computed property or watcher instead.
Note that updated does not guarantee that all child components have also been re-rendered.
That does not work properly, and as stated in the documentation, even after adding 'await Vue.nextTick()', the element does not completely renders.
Additionally, I used a watcher for the array, which did not solve my issue.
the solutions I found are:
'await flushPromises()'
setTimeout() with 0 ms, so the code will be pushed to the end of the queue.
What would be considered as best practice to solve this issue?
In general, what is the best approach to wait ( or listen to some event) so that all UI changes and components re-rendering had been done entirely.
Thanks!
I've been search for quite a while, but i wasn't able to find a good solution to this.
I have to trigger a function/event, as soon as the DOM of a component finished rendering.
I need to access the height of the component, so ngAfterViewInit won't work, as that is called before the DOM is rendered.
I tried ngAfterViewChecked, but this gets called multiple times. As the function that gets executed is part of a wrapper-app, i have no access to that either, and i absolutely need to make sure it only gets fired once. I was thinking about using ngAfterViewChecked with some sort of debounce, but this feels (and probably is) wrong.
The alternative would be to trigger a function from the template, when the ngFor loop reaches the last item. But again, that just feels wrong.
Any input is appreciated
I got around this issue by using ngAfterViewChecked. On every check, i get the scrollHeight of the components body. Before it renders, it will be 0, so checking if there's been a change on the scrollHeight attribute, works for my use case.
Thanks for all the comments, that actually made me think of this solution
I have been working on putting together a module to allow a user to invite their friends to use an app. Code works without major issue, but since I have over 100 contacts in my phone, the speed is rather slow. Scrolling isn't a problem, and I can add a loader as the phone pulls the data. But I when I choose an element for highlight it takes a few seconds for the item to get checked. I'm curious if anyone has any tips for how to optimize?
My snack is below:
https://snack.expo.io/#fauslyfox110/testingreferrals
Main file: inviteScreenTwo.js
React will update the elements on screen whenever you make it change to your state. In your case, I suspect that the delay is due to React going through all the contact records your showing and updating them when you change the highlighting.
One way of dealing with this is to make sure that contacts that are off-screen aren't actually in the DOM. You would need to update your render method to place only contact records in the list that are actually visible. That way, React won't need to update as many elements. Please refer to the React docs to read more about this optimization.
Another way would be to override the lifecycle method shouldComponentUpdate for your record components, making sure that the only rerender when their highlighted status changes. This is called reconciliation avoidance. The method has the following signature:
shouldComponentUpdate(nextProps, nextState) {
}
By default, this method always returns true. You could change it to compare nextProps with this.props, checking that the highlighting has changed, and return true or false as appropriate. Read more about this optimization in the React docs.
Sometimes setState doesnt working all by itself and i am adding small timeouts to do it.
My first question is;
1-) Why setState sometimes doesnt working without a delay?
2-) Is it correct to add setTimeout? - And its small amount of delay like 10 ms, its nothing, but it makes my code to work and state to update.
When i was coding in Angular1, we were using $timeout sometimes for scope to apply this changes. But it was okay to do so, in React i am not sure about this.
Any help would be appreciated, thanks.
It sounds like you're attempting to call setState in a render. This is bad because render occurs immediately following state updates. It causes a render loop. You should not use a setTimeout to do this either. That causes React to stop warning you because it can no longer detect the issue. This will cause large random bugs in your app.
Your symptoms are a sign that you need to push the state up into container components.
The reason why there is a delay in setting the state is that 3 lifecycle method of ReactJS are called while setting the state.
setTimeout is a way to get the current value of the state which has just been set, However another alternative would be that you can create a callback function inside the setState to get the current value.
Use of setTimeout must be always be avoided until and unless there is no other alternative.
example:
this.setState({show:true},()=>{console.log(this.state.show)})
I have a React controller-view that subscribes to two different stores, and initiates calls that will cause each of them to emit:
componentWillMount() {
UserStore.addChangeListener(this._onChange);
EventStore.addChangeListener(this._onChange);
}
componentDidMount() {
UserService.getUser(this.props.params.slug)
EventService.fetchEventsForUser(this.props.params.slug);
}
For the UserStore listener, this works fine. The user is fetched from the API, which comes back to the UserStore, which emits a change. If I put console.log statements in, I can see all of this flow in exactly the way I'd expect, ending in the _onChange function of my controller-view.
But. For some inexplicable reason, the change listener added to the EventStore isn't firing. The events are correctly fetched from the API, and which come back to the EventStore method that I expect, which correctly updates EventStore's internal state, and it even fires emitChange() just fine (I've verified with copious console.log statements). But _onChange in the controller-view is never called!
I've been trying to troubleshoot this for close to double-digit hours now. I don't feel any closer to an answer than when I started. I don't even know where to look.
Other notes
I tried removing the UserStore listener from my controller-view, just setting static content in UserStore. I thought maybe there could be a race condition between the two updating. Nothing changed.
I tried splitting my controller-view's _onChange into two separate functions, again with the suspicion that they weren't playing nicely together. But the console.logs showed that _onUserChange was called as expected, but _onEventChange was never called at all.
This used to work! In the master branch, which the pull request I've been linking to branches from, it works! Everything's structured a bit differently, there—the controller-view I've been linking to only listens to changes in UserStore, while its child component listens for changes in EventStore. If I duplicate this structure, though, it still doesn't work. In my new branch, whatever component is listening to changes in EventStore seems to be completely deaf to such changes.
Also on master, EventStore did much more computation than it does now. I very much doubt that it being slow would make its listeners more likely to hear it, though. UserStore is equivalently simple, and its listeners have no problem hearing it.
Sometimes it does work. About 1 page load in 100, it works. The component updates its state, and the server-loaded content actually displays. I haven't been able to reproduce this or determine what causes it.
Another component in this app also listens for EventStore changes, and also rarely hears them.
You can see it failing to work at http://life.chadoh.com/#/chadoh. If you then visit http://life.chadoh.com/#/chadoh/week/0, you'll see that an event has correctly been loaded from the server. But for some reason the listeners were not alerted. If you refresh the page on http://life.chadoh.com/#/chadoh/week/0, the sidebar will never be updated with events (the difference is whether it's initially rendered before or after EventStore has already been loaded with data).
Changing the get events() method in EventStore to a regular events() has no effect.
Putting the EventService.fetchEvents call inside a setTimeout does not yield better results.
Putting the listen calls inside the constructor does not yield better results.
I see EventsStore extends BaseStore which extends events.EventEmitter. Is that the node.js standard library event emitter?
If so, you may have a naming conflict on _events.
Your store uses _events to hold data: https://github.com/chadoh/life/blob/no-dates/src/stores/EventStore.js#L10
But EventEmitter also uses _events to store event handlers: https://github.com/joyent/node/blob/d13d7f74d794340ac5e126cfb4ce507fe0f803d5/lib/events.js#L140-L186
As a result, event data may overwrite the collection of event handlers, causing handlers to never fire (because they aren't there!).
You could try using a different key to hold data (eg _eventData) and see if that solves your problem!