0

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

  • You doesn't seem to be using Recoil. Or at least I can not find any reference in the example you given. Can you either update the question or the title please? – Zoltan Magyar Feb 23 '21 at 14:47
  • Yes you're right, thanks, I corrected it, but the same applies to both cases. Setting a simple value is easy. But applying a change deep in a object/array stack seems difficult. Spreaders are useful but it's just not very elegant! – Peter Stjernholm Meldgaard Feb 23 '21 at 20:03

2 Answers2

0

If this is frequent in your code then you can extract your update logic to a custom hook. I was thinking about something like

function useDeviceAttribute(initialValue: IDeviceAttribute) {
   const [attribute, setAttribute] = useState(initialValue);
   
   const updateAtId = (id: number, newValue: any) => {
      let index = attribute.values.findIndex(l => l.id === 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)]
        })
      }
   };

   return [attribute, updateAtId];
}

So your component code would be something like

function YourComponent(props) {
   const [attribute, updateAtId] = useDeviceAttribute(props.attribute);
   //
   // your component code goes here
   //
   // ... code calculating a new value for existing value in editedSetting: IDeviceAttributeValue
   /// ...
   // Now update state, first find the element in the array
   updateAtId(editedSetting.id, newValue);
}
Zoltan Magyar
  • 874
  • 1
  • 6
  • 19
  • Thanks a lot for your nice suggestions Zoltan. But this seems more like a refactoring, and does not reduce the amount of codelines for updating the state that I was looking for. Maybe I was not specific enough. It is the code from line 6 to 12 in your suggested 'useDeviceAttribute' function I like to simplify. Looking for something like setAttribute((updateable) => { return updateable.values[index].value = newvalue}); – Peter Stjernholm Meldgaard Feb 25 '21 at 21:24
  • Simplifying that has nothing to do with the question's subject. What you have done there is a really cumbersome way to replace a value in an array. You can simplify that by the answer posted here: https://stackoverflow.com/questions/5915789/how-to-replace-item-in-array – Zoltan Magyar Feb 25 '21 at 21:47
  • @ZoltanMagyar the library makes the list immutable, so your comment is incorrect. – William R. Marchand Apr 22 '23 at 18:57
0

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.

  • This is pretty bad... and just asking for things to be missed, and new errors to be introduced. As you mentioned. Never-mind the performance implications, as this sorta thing is unnecessarily expensive. Do we have other options yet? – Douglas Gaskell Apr 17 '22 at 01:20