metal.js icon indicating copy to clipboard operation
metal.js copied to clipboard

Clarification on SYNC_UPDATES

Open bryceosterhaus opened this issue 8 years ago • 14 comments

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.

bryceosterhaus avatar Jun 23 '16 17:06 bryceosterhaus

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.

mairatma avatar Jun 23 '16 17:06 mairatma

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;

bryceosterhaus avatar Jun 23 '16 18:06 bryceosterhaus

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.

mthadley avatar Jun 23 '16 18:06 mthadley

It sets the state synchronously, and calls the callback after the batched event is triggered, which would be after the component has been rerendered.

mairatma avatar Jun 23 '16 19:06 mairatma

This issue was moved to metal/metal#4

mairatma avatar Jul 05 '16 17:07 mairatma

This issue was moved back.

mairatma avatar Jul 21 '16 20:07 mairatma

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 avatar Dec 13 '16 18:12 bryceosterhaus

@bryceosterhaus Why is it needed? There’s nothing wrong with that same component in async (default) mode.

yuchi avatar Dec 13 '16 18:12 yuchi

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

bryceosterhaus avatar Dec 13 '16 18:12 bryceosterhaus

What’s the behaviour you’re having without synchronous updates?

yuchi avatar Dec 13 '16 18:12 yuchi

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

bryceosterhaus avatar Dec 13 '16 18:12 bryceosterhaus

Mhhh very bad indeed.

yuchi avatar Dec 13 '16 18:12 yuchi

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.

mairatma avatar Dec 14 '16 00:12 mairatma

Please add SYNC_UPDATES documentation to Metaljs.com

blzaugg avatar Jan 25 '18 22:01 blzaugg