Search code examples
javascriptreactjstypescriptrecoiljs

Easier way to set React or Recoil state objects with array properties


Hope someone can help me with an easier way of updating my recoil states on more complex objects/arrays. I'm mainly a C# developer, but trying to learn some decent ways of coding javascript. This just seems to ugly and overcomplicated, the way my code looks currently.

As the state instance is read only, I cannot change values on it directly. Using underscores clone method does even not change that.

So here's my simplified objects, in real life the have a lot of non relevant properties:

interface IDeviceAttributeValue {
  /** The unique value key
  id: string;
  /** The attribute value */
  value: any;
}

interface IDeviceAttribute {
  /** A unique key to identify the attribute. Normally the OSC address of the setting is used */
  key: string;
  /** The list of attribute values */
  values: IDeviceAttributeValue[];
}

In React i have the state declaration const [attribute, setAttribute] = useState(props.attribute as IDeviceAttribute);

Or some other place a Recoil state: const [deviceAttributeState, setDeviceAttributeState] = useRecoilState(recoilDeviceAttributeState);

And somewhere in the code I need to change a value on the value array and update the state. In both cases with React state and Recoil state, then the 'getter' instance is readonly/const.

I end up with this:

... code calculating a new value for existing value in editedSetting: IDeviceAttributeValue
...

// Now update state, first find the element in the array
let index = attribute.values.findIndex(l => l.id === editedSetting.id);
if (index !== -1) {
  let newValueItem = {
     ...attribute.values[index],
     value: newValue
  }
  setAttribute({
    ...attribute, 
    values: [...attribute.values.slice(0,index - 1), newValueItem, 
    ...attribute.values.slice(index + 1)]
  })
}

So many lines of code for a simple state update! I'm sure for someone this is very trivial task and can be done much more elegant:-)

Thanks for help and time


Solution

  • Ok, seems like there's no way to update object/array based states without using spread operator and taking care of deep updating as the shallow property update only works on top level.

    This means that you must take care of providing the existing property values from current state, and set the ones you wanna change in parallel, and this you need to do on each nesting level.

    I found a nice q/a providing examples here: blog

    So in my case I ended up with code like this for updating an object with a array property (settings) with a new value for one specific element in the array:

    setEditedDeviceState(curVal => ({
       ...curVal,
       settings: curVal.settings.map(
         el => el.key === attribute.key ? attribute : el
       )
    }));
    

    I must admin that I find this to be a pain in the ... and a candidate to easily introduce errors in your data models. If this is a lack in the language itself or the implementation of react (recoil, redux or whatever) states is probably something that can be discussed further. But seems like this is what you have to live with currently.