DEV Community

Nate
Nate

Posted on

What is the best way to update a property on a model using React hooks

I'm switching from angularjs to react, and still learning the basic philosophy behind react.

I ran into an issue trying to bind an input element to a property on a model. I first tried using useReducer, which worked fine to update the model. The problem is that it does not trigger a re-render of the component, since the model is still the same model, just with an updated property. As far as react is concerned, nothing has changed, so it does not need to re-render.

I came up with two different solutions. The first is to clone the model on each update. In some cases this is fine, and maybe it's the functional programming way of doing things (I'm almost as new to functional programming as I am to React). But it seems like a waste to go around cloning things all the time.

The second solution is to use the forceUpdate strategy posted at https://medium.com/crowdbotics/how-to-use-usereducer-in-react-hooks-for-performance-optimization-ecafca9e7bf5. If I wrap that thing up in a nice custom hook, I can make it pretty clean. But I know that forceUpdate is not really recommended, and I'm not comfortable enough yet with react to know if this is a reasonable place to use it.

I've got everything laid out clearly in a jsfiddle at https://jsfiddle.net/nbrustein/odjwkbvs/31/

So, what should I be doing here? Is the solution:

  1. Stop worrying and love the clone. It's what all the cool functional programmers are doing these days.
  2. Use forceUpdate. This is what it's for.
  3. Something else I haven't thought of yet.

Thanks in advance for any insights.

Top comments (2)

Collapse
 
nbrustein profile image
Nate • Edited

I figured out how to get it working without forceUpdate. What I did was to stop trying to bind directly to the property on the model. Instead, I set up a state variable using useState, and then whenever I observe a change to it, I push that change onto the model. Seems like the React way of doing things.

I've updated the jsfiddle with the new strategy: jsfiddle.net/nbrustein/odjwkbvs/40/

Collapse
 
dance2die profile image
Sung M. Kim • Edited

React updates the React component tree by comparing the reference of a state (Refer to Reconciliation).

In your case, updateProperty updates the property of the model, not the reference of myModel. Thus React thinks that there is no need to update the tree.

  const updateProperty = useReducer((myModel, newValue) => {
    myModel.property = newValue;
    return myModel;
  }, myModel)[1];

When using useReducer, you would usually dispatch an action, which is a Redux way of returning a new state.

And you can return a new reference return Object.assign({}, action.model, {property: action.property}); by returning a new object from the reducer (modelReducer).

Instead of using myModel directly, you use model returned from useReducer, which gets the new value from input.onChange.

function modelReducer(state, action) {
    switch (action.type) {
    case 'UPDATE_PROPERTY': 
        // return a new object reference
        return Object.assign({}, action.model, {property: action.property});
        // if using ES9, which supports object spread
        // return {...action.model, property: action.property);
    default: return state;
  }
}

function DontWork() {
  const myModel = useModel()[0];
  const [model, dispatch] = useReducer(modelReducer, myModel);

  return (
    <div>
      <h1>Now works</h1>
      <input
        onChange={e => dispatch({type: 'UPDATE_PROPERTY', model, property: e.target.value})}
      /> 
      <span>{model.property}</span>
    </div>
  )
}

I've forked your initial fiddle and created a working version using the code above.

jsfiddle.net/dance2die/tw8759da/21/