metal.js
metal.js copied to clipboard
Clarification on SYNC_UPDATES
So as far as I can tell from both the metal site and the code, this is what I know about SYNC_UPDATES
* Flag indicating if component updates will happen synchronously. Updates are
* done asynchronously by default, which allows changes to be batched and
* applied together.
I am writing a component right now that handles uploading multiple files at once. For my state I have an array of files and then for each upload, I am listening for progress and updating the files
array on my state object. However, I am noticing that when SYNC_UPDATES = false;
the files array is inconsistent with the files I chose to upload. When SYNC_UPDATES = true;
everything behaves as I would expect and the files array is properly updated.
So this brought me to the question of what SYNC_UPDATES is actually doing, and why in this case would I use it and not in every case?
Also, if I have a tree of components, C1, C2, C3 and state is passed from C1 -> C2 -> C3. If C3 requires SYNC_UPDATES = true
does that mean that C1 and C2 also require it to be true since they pass the state to C3? Assuming C3 is also passed an onChange function.
The only thing SYNC_UPDATES
does is to have updates to state properties cause the component to rerender immediately after the update. The default behavior in Metal.js is to wait until the next tick to rerender, which helps batch all state updates that may happen one after another into a single rerender. The state property's value is always immediately set, regardless of SYNC_UPDATES
, the only difference being when the component will rerender.
The only use case I've found so far to use SYNC_UPDATES
was when using react's draft.js editor, because it would emit multiple onChange
events synchronously, but our default behavior would only rerender its wrapper component once, with the last value given by onChange
, which would cause Draft.js's undo/redo feature not to work properly, as it expected to receive all intermediary values as well. So basically, you should use SYNC_UPDATES
if you care about all intermediary values your state property may have, to guarantee you'll pass all of them down to a child component, since the batching behavior would prevent that. That should be a rare use case though.
As for the tree of components you've mentioned, it really depends on the application. You can certainly mix them up, as long as the place where you need the behavior to be different is covered. In the metal-draft repo I've linked to above I have a SimpleExample
class that uses SYNC_UPDATES
, but it could be normally used inside another component that doesn't use it.
Not sure why you'd need it for the multiple uploader component though. If you think something is weird with that after my explanation above, you can send me the code you're using so I can try to investigate what's going on.
here is a simplified example showing the issue
import Component from 'metal-jsx';
class Child extends Component {
created() {
this.incrementCount = this.incrementCount.bind(this);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
setTimeout(this.incrementCount, 0);
setTimeout(this.incrementCount, 0);
}
incrementCount() {
const {count} = this;
this.onChange(count + 1);
}
render() {
return (
<div>
<button onClick={this.handleClick}>Click Me</button>
{this.count}
</div>
);
}
}
Child.STATE = {
count: {},
onChange: {}
}
class Parent extends Component {
created() {
this.handleChange = this.handleChange.bind(this);
}
handleChange(value) {
this.count = value;
}
render() {
return (
<div>
<Child count={this.count} onChange={this.handleChange} />
</div>
);
}
}
Parent.STATE = {
count: {
value: 0
}
}
// Parent.SYNC_UPDATES = true;
export default Parent;
While were clarifying things, is setState
always async? ~~Looks to be that way since it both takes a callback, and also listens for stateChanged
in metal-state
.~~ Nevermind, looks like it calls set
before setting up the callback for stateChanged
.
It sets the state synchronously, and calls the callback after the batched event is triggered, which would be after the component has been rerendered.
This issue was moved to metal/metal#4
This issue was moved back.
Here is an example of a use case that SYNC_UPDATES is needed
https://jsfiddle.net/defzoaop/1/
class ControlledInput extends JSXComponent {
handleChange(event) {
this.state.inputValue = event.target.value
}
render() {
return <input onKeyDown={this.handleChange.bind(this)} value={this.state.inputValue}/>;
}
}
ControlledInput.STATE = {
inputValue: {
value: ''
}
}
// ControlledInput.SYNC_UPDATES = true;
JSXComponent.render(<ControlledInput message="Hello MetalJS!" />);
@bryceosterhaus Why is it needed? There’s nothing wrong with that same component in async (default) mode.
@yuchi I believe its because the batch updating is not registering the intermediary state changes. So that is why when turning SYNC_UPDATES on, it will work as expected.
What’s the behaviour you’re having without synchronous updates?
If you try typing very fast, the input doesn't register all of the keys.
Here is a gif of me typing "asdf" over and over https://gyazo.com/f321436e6e28faf5ea2522701a041085
Mhhh very bad indeed.
Yes, you're right, that's another use case. We could probably use input
instead of keydown
there, which would be better anyway, but I can see how there could be other use cases where the intermediary values for this kind of thing would be essential, with no other way around it than using SYNC_UPDATES.
Please add SYNC_UPDATES documentation to Metaljs.com