yahoo / jafar

🌟!(Just another form application renderer)

Home Page:https://yahoo.github.io/jafar

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Value or State change is not sync, so update data in react component is wrong in some scenes

WangLarry opened this issue · comments

In react-form of jafar, update value or state through onValueChange or onStateChange function, then react pass props to component, reflect this change.

But this progress is not sync, so in some scenes the update logic is wrong.
for example, Validators.js in react-editor :

export default withTheme(({ value = [], state = {}, onValueChange, onStateChange }) => {
    ....
    const onValidatorStateChange = (validatorState, index) => {
        ....
        onStateChange(newState);
     };
}

If there are two validators, when the second child call onStateChange function, the state prop has not updated in this time. So the 'state' in Validators will miss the update of first child.

temporary solve method:

export default withTheme(({
  value: _value = [],
  state: _state = {},
  onValueChange: _onValueChange, 
  onStateChange: _onStateChange,
}) => {
  let value = _value;
  const onValueChange = (newValue) => {
    value = newValue;
    _onValueChange(newValue);
  };

  let state = _state;
  const onStateChange = (newState) => {
    state = newState;
    _onStateChange(newState);
  };
  ......

Every component has this same problem.

@WangLarry 10x for sharing :)
I did not understand exactly the case. Can you please write down the steps you did to see the issue, and what you expect to see?
If possible - supply a unit test that fails to demonstrate on 'packages/form/test/Form.spec.js'

in react-editor page, then FormList > FormEditor > FieldEditor, edit the 'benefits' field of employee, add another VALIDATORS, then save. You will find previous validator dispear.

@WangLarry fixed and deployed :)

I think there is still a problem. For example:
update state in component(1) => onStateChange => Form onStateChange => Jafar change state => Form context => component props => return to (1) , assume this progress take 300ms.

If in this progress, position (1) have updated state twice, the second one will use old state value, because the first update process has not complete, not return updated state by first one.

@WangLarry
Correct - that happened (In the Validators.jsx) because we updated the state of the "Validators" field - many times from different underline components. Ideally we should have plan the component code to prepare the correct params for all underline code and update only one time from upper component and not parallel from sub components.

The thing is that Jafar maintain process queue - meaning in you call 3 times "onStateChange" - only after one action is done processing - the next one will start - https://yahoo.github.io/jafar/docs/pending-actions.html.
onStateChange receive an entire NEW state object and replace it completely for the field (not merge with current state object - replace) - thats y when the underline components of "Validators.jsx" tried to call all together to "onStateChange" of the validators field - we got one running over the other.

We could thing about maybe add 'merge' option to 'onStateChange' but im not sure this approach wont cause potential bugs to programmers and confusing.. We tried to do it the same way react update its private component states (before hooks) - just update it outside of the component. If we kept the Validators.jsx state inside the component (using the react internal state and not Jafar field external state) and we would have called react's 'setState' (https://reactjs.org/docs/react-component.html#setstate) multiple times at the same time with entire different new state object - i guess same bug would happen here as well.
So my suggestion is to plan the jafar field component to update its state from a single place and not lets parallel task do it together like from underline init components.

WDYT?

maybe we can add the 'updater' option like react 'setState' did - to be able to update state according to currently existing state
https://reactjs.org/docs/react-component.html#setstate
something like you can update in 2 ways:

  1. onStateChange(entireNewStateObject) // currently supported
  2. onStateChange((currentState) => { return { ...currentState, newData: 12345 } }) // suggestion to add ? will that be useful ?

@galhavivi
Sorry for my poor English. :)

Yes, we need accumulate updated state, then submit it to Jafar engine. In onStateChange function, we should not use state passed through props of component, should use the 'accumulated' state value.

Maybe implement it in FormView component? Like this:


  // change FormView to function component
  ... 
  const [accState, setAccState] = React.useState(state);

  React.useEffect(() => {
    setAccState(state);  
  }, [state]);  // passed through props of FormView

  React.useEffect(() => {
    onHandleStateChange(accState);  // submit it to Jafar engine
  }, [accState]);

  // callback function of Field component
  // in field component, must call it like this:  onStateChange((prevState) => ....
  onStateChange = setAccState;

@WangLarry
Its ok my English is not 100% as well, good thing we understand each other lol ;)

We want to keep all the "logic / brain" in Jafar core - javascript form (package "form" - pure javascript)- inorder for it not to be tight to any UI library / framework such as react, so the solution will start form there - and be available to all ui libraries that uses it (currently we have only react form), and the react-form package will only pass the data to the engine and back.

changeState - i guess will probably still be able to get a new object for state - since most of the cases it will be fine and work ok. only for rare cases where you must update state according to a prev one (for example if you are updating the same field's state from different places at the same time like this bug had) you will be able to pass a function instead of object (again - same approach of react setState).

i opened a new issue for the solution - #56
you can keep track of it

You are right.
changeState - most of the cases will be fine and work ok.
Keeping all the logic in Jafar engine is very important.

expect it :)

another opinion:
Maybe we only want to store(record) state in Jafar core, so can init state in next render(mount) component.
If so, we do not need return(back) state to component from Jafar core, let component itself manage state.
And React framework manage state well.

It is possible to use jafar with components that manage their own states and not update the state outside in Jafar...
Take a look on "Stateless VS Statful components" in - https://yahoo.github.io/jafar/docs/react-components.html

Using state outside the component and store it in jafar give you some advantages (that are not must to have) like

  • the ability to persist a for state between refreshed (you can restore the exact same component status as before the refresh)
  • tacking / logs - on debug - you can look on "verbose / debug" tab to look only all jafar lifectcle messages and see exactly what change
  • we can implement undo / redo operation - and if we have the complete for snapshot (the model object with all the field status including its state) - we can return to any prev snapshot by taking the prev model snapshot and pass it to the Form component

I understand and remember it in document, Jafar is very powerful:)

My opinion is 'Stateful Components' + restore status + tacking/logs

In current Jafar, Stateful Components will lose 1) and 2) feature.

@WangLarry 10x for the idea! I added the support on
issue - #56
pr- #57

I updated the Validators.jsx bug fix to use the new updater function -
https://github.com/yahoo/jafar/pull/57/files#diff-ed891647db1d963f7c32b391ac0b1fb3

available now on v1.0.6

Updater function support docs available on -
https://yahoo.github.io/jafar/docs/actions.html#changestate
https://yahoo.github.io/jafar/docs/actions.html#changevalue

Looks very well. Standard code and fast response, you are a talented engineer.