Currently, React only batches updates in functions that are called by the React itself. like event handlers and lifecycles. And as far as I know, React simply calls these functions like they are wrapped in unstable_batchedUpdates.
Lets assume it's something like the following:
unstable_batchedUpdates(() => {
instance.componentDidMount();
});
So right now, we have to manually wrap functions that are called by someone other than the React inside of the unstable_batchedUpdates.
I'm also aware that in the concurrent mode, we have the updates train concept and we batch all of the state updates together.
But I have two questions,
First, how does this update train works? What mechanism decides which updates should be on the train? Is it only the time of occurrence?
And second, Why it has to be this complicated? why we couldn't do something similar to what Vue does and wrap all of the setState calls inside of a nextTick function? I know it's almost similar to what we will have in the concurrent mode, but what kind of bottlenecks and problems do we have for adding it right now, without the concurrent mode enabled?
I'm thinking of starvation problem. But I'm not sure.
Thanks in advance.
Related
I am triggering a function in saga with Dispatch. Can Saga send me information inside the component after it's done all its work? Actually, I will explain what I want to do with a simple example, don't laugh, please, I am sure this won't work.
dispatch({
type: 'triggerApiCallInSaga'
}).then(res => doSomething(res.payload));
I want to make changes in the component as a result of the query I have made. I actually want to maintain this with redux, but I'm worried about my state expanding. Also it seems silly to me to check via redux to close the modal.
In short I want to receive signal from saga and see it in component. Can I make a listener for it? Or does saga have a method for this? Although I searched, I could not find the right results. redux-sagas callback (aka sagas and setState) I encountered a similar question to my question, but here I encountered a negative answer that I did not understand.
Thank you.
The answer you linked is right, the expected way for a saga to communicate back to component is through updating redux store. Adding promises/callbacks is an antipattern, because each action can be handled by 0-n sagas - of course you might know in your case it is just one saga, but the framework doesn't guarantee that in any way and so it can lead to some unexpected issues later on.
That said, even though it is an antipattern doesn't mean you can't do it.
The easier way is to add an callback to the action, so e.g.
// component
const callback = data => console.log(data)
dispatch({type: MY_ACTION, callback})
// saga
yield takeEvery(MY_ACTION, function*(action) {
action.callback({foo: 'bar'});
})
If you would prefer promises, that is something that can't be done easily with just the saga library, but there are packages that can add this functionality like:
https://github.com/adobe/redux-saga-promise
Ideal solution to this problem would be to run a saga on top of a useReducer hook, so that instead of adding component specific state to redux store you would just work with a component state. There are some attempts out there that try to do this, but unfortunately react is still missing some functionality that would allow us to reliably do this without introducing other issues.
As far as I know a mutation in Vuex should be synchronous. Vuex.nextTick ends up being asynchronous if I understand it correctly. Does it make it agains the convention to use it in the mutation then? Could it break something?
I have tried doing it this way and it seemed to work fine, but I haven't tried going back to previous state with Dev Tools for example.
Just to let You know: I use nextTick to postpone some sorting to avoid doing it unnecessarily multiple times. Maybe You have some other solution to that issue? I know I could use an action istead of a mutation, but I wanted to change some actions I already have into mutations as it seems to be a better option for me, but they use this pice of code.
Using nextTick in a mutation effectively moves the code outside the mutation, so you'll get errors in strict mode and the Devtools can't track the changes.
Also, nextTick won't run until after the next round of rendering, so you're going to be rendering multiple times.
If possible use a getter for the sorted list instead.
So we have a redux (with thunk middleware) / react environment.
We have the following piece of code:
onMyClick = () => {
this.props.mySynchronousActionWhichWillCreateNewReducerState();
this.setState(...my state change which wants to see the new reducer state...);
}
It will not work in this form since the code is synchronous. Which means that the react lifecycle will never get to update itself with the new props.
However if we change it like this:
onMyClick = () => {
Promise.resolve(this.props.myActionWichWillCreateNewReducerState())
.then(
() => this.setState(...my state change which want to see the new reducer state...)
);
}
It now works as "expected" (The dispatch triggers the new reducer state, the component updates, then the setState runs). First I thought this solution is error prone and works because we 'win' just a bit of time with asynchronicity to allow the update to kick in before the setState. But it never fails, (You can make the code slow in the action, or in the reducer, in the middleware, in the component update, where ever you want, it still works).
Why?
Some explanation might be warranted why this is so hard to wrap my head around. And more in the sense of "Why does it work the way it does" instead of "How does it work"
So first and foremost lets look at the two pieces of code like plain javascript.
In this case - for me at least - the first should work, and the second should not. Or at least the second should be fuzzy.
First:
I make synchronous call (dispatch->action creation->store change), then I make an other, and yet the second cannot expect the changes made by the first. I have to know how redux and react operates quite intimately to know how, and why. And btw you can even mutate the redux store (big no no) instead of returning a new object from the reducer to retain the reference and it still doesn't work. Which is mind boggling, you synchronously mutate an object, then cannot access the change afterwards...
Second:
In this case (just like Jaromanda X commented) what I "seemingly" tell the code 'Hey run these two pieces of code in parallel'. And now it works, and works all the time. Wut. Adding my (superficial or so it seems) understanding of react life cycles to the mix makes it even more paradoxical. Since it means that even more logic - react lifecycle update - will have to 'outrun' the setState call for it to work.
If this wouldn't be redux/react environment with all the support and intelligence behind it, I would say this code behavior smells like all hell and it smells like black-magic and go-to :).
When you wrap a piece of code inside a promise, you are essentially delaying its execution by a minimum of 1 tick. For your code, that time was sufficient enough for reducer dispatch to complete its update. Hence when the code inside then was executed it got the updated value, as this.state is an object and even within a closure it always points to a memory reference which will be updated.
That said neither reducer update in redux or setState in React returns a promise. Your code is equivalent to :
Promise.resolve(console.log("dummy")).then(() => console.log("second"));
console.log("first")
first will always be printed before second as the promisified snippet is executed in the next tick of the event queue.
Your code is not error prone at the moment because React decided 1 tick was sufficient to update the state. But don't rely on that as for some other piece of code or in later versions of React, the time required for updates might change.
I recently read through a large portion of the React and ReactDOM codebase in order to get an understanding of what happens when a call to setState occurs. At this point, I would say that I understand the sequence of events at a high level. A call to setState results in a message being added to a queue of updates that need to be processed. The updates are processed and changes to the DOM are only made if necessary.
Here's where I am lost. There are hundreds of blog posts discussing the asynchronous behavior of setState. While I don't doubt that setState is asynchronous, I cannot find a line of code in the ReactDOM codebase that would introduce asynchronous behavior. Does anyone know exactly where this happens?
First of all setState may be execute in async way, but it is not allwys executed as such. Ben Nadel list some of his findings in setState() State Mutation Operation May Be Synchronous In ReactJS
To summarize setStates seems to gets batched in situations where react can intercept originating event, like onClick handlers. Since react creates actual DOM from virtual react DOM (and since it is aware of semantics of attributes) it can wrap any of onClick handlers that you provide into something like this
wrappedHandler = () => {
turnBatchingOn()
originalHanlder()
executeBatched()
}
In this case you get async behavior, all setState calls get enqueued, and they get executed only after your original onClick handler has finished executing.
Note this is not actual react code it is just my speculation how it is async effect achieved. I understand it is not actual line of code that does it, but I think it could help you find it.
Best article explaining setState implementation that I found is on React Kung Fu
i think setState isn't async, but there is an optimization of multiple setState calls and in some cases sync canot be guaranteed.
I'm working on building an step sequencer in Redux to help me learning. What I need is some universal clock that functions as "tick" to provide musical timing.
So I planed to use the Tone.js library, which builds on top of the Web Audio API.
There's the following function:
Tone.Transport.scheduleRepeat(function(time){
//do something with the time
}, "8n");
You provide a callback function which gets called everytime the transport reaches a certain position.
My naive approach was to let the callback dispatch an action which increments a tick count in my store.
This doesn't work because actions have to be plain objects.
What are the possibilities to get this working?
I'm still working on getting the right understanding of the basic underlying principles of Redux so I'm unsure about that but could I somehow
catch the callback using middleware and just let it through when it is actually invoked?
Would this be the right direction? What are some libraries, examples or concepts I could look into to get some idea how this could be done?
Is this even the right direction or should I approach this differently? If so, how?
Or do you maybe have any idea what's the best way to provide global timing for different components in a Redux app?
I went into a lot more detail in https://www.html5rocks.com/en/tutorials/audio/scheduling/, but in short - you shouldn't use Javascript callbacks for musical timing. It's not accurate enough. That's why we have web audio scheduling.
Very interesting question, that's a pet project I've been also wanting to tackle but haven't written a single LOC yet. :)
For the timing part, you could use a middleware for that, or even a <Clock /> component that launches the scheduler itself and dispatches an action on every tick (probably with the time as payload).
The tricky part however is the overall design of your application. Having researched Tone.js a little bit, it was clear to me that you'd have to separate the audio part from the visuals. Which means your Redux state should only be concerned about representing your step sequencer (I'm visualizing something like a list of lanes (channels/instruments) and your audio logic should be kept outside of it.
I would keep an array of lanes, each of which is itself an array of "steps" that define whether they're "active" or not. Again this is only UI related. Clicking on a step to activate it should modify your state via action creator and also setup anything you'll later need to play with Tone.js.
When playing back your song, you'll need to dispatch that clock tick to advance the current active "step" so you can highlight it in the UI.
Here's a mouth-watering Codepen emulating a Roland TR-808 to grab ideas:
http://codepen.io/pixelass/details/adyLPR
And here's the relevant section on the Tone.js wiki on sync'ing audio and UI:
https://github.com/Tonejs/Tone.js/wiki/Performance#syncing-visuals
Sorry I can't help you further, perhaps you're ahead of me and already have some working code you could share.
Basically cwilso responded correctly. If you want to be scheduling JS functions for musical timing, you should not be using callbacks.
If there is Tone.js functionality that you want to implement based on this timing, avoid Redux and call these Tone.js functions directly, either within the callback functions or Tone.Transport.schedule function.
If you are building a sequencer I would recommend looping the Tone.Transport based on the length you want and scheduling the notes to be hit at certain points on the timeline (if this is what you are looking for). Check out the loopStart and loopEnd in the docs for help (http://tonejs.github.io/docs/#Transport).
If this functionality is necessary for visual references, which may be why you want a Redux callback, I can provide an example of how you might do that below:
function incrementTick() {
return { type: 'INCREMENT_TICK' }
}
// inside your component once the increment function has been connected
Tone.Transport.scheduleRepeat((time) => {
this.props.incrementTick()
}, "8n");
Hope this helps.
I'm working on an audio related application and have run into the issue of managing the web audio API part along with redux as well.
The way I've solved it is only storing representation of the audio state in the redux store (plain JS objects; what you would store in the database and use to initialize the app). This stored information is used to render the UI.
I have a service 'engine' class which listens to all changes in the store, this is where all the web audio stuff is created and stored. It contains basically a copy of the reducers from the redux store but applies the changes to the web audio nodes.
For example I dispatch an action:
{type:"set-gain", payload:{trackid:3, value:0.7} }
The redux store will simply update a plain JS track object to the new gain value, the engine will find the associated gain node (or create with add etc.) and set the value on it.
In your case you would dispatch an action to set the timing, in the redux store save it as plain JS object, in the engine part you use web audio scheduling to set it.