Search code examples
javascriptreact-reduxjavascript-objectsoptional-chaining

Can't implement simple optional chaining


I am trying to implement a simple optional chaining state update. What I want is for the items to ONLY be changed IF the item is defined/exists; if not/is undefined, I want the item to keep the previous state (e.g userID should remain = 2 if not updated).

To test this out I created an object with three variables:

const userObj = {
  firstName: "",
  lastName: "",
  userID: 2,
};

Then I created a function to update state:

const updateState = (item) => {
  return {
    userObj.firstName = item?.firstName,
    userObj.lastName = item?.lastName,
    userObj.userID = item?.userID,
  };
};

Finally I pass the item which contains only one item to update (firstName) and I call the function:

const item = {
  firstName: "None",
};

console.log(updateState(item));

The output:

 userObj.firstName = item?.firstName,
           ^

SyntaxError: Unexpected token '.'

But when I hover over userObj I can see its properties:

enter image description here


Solution

  • You can use null coalescing in conjunction with optional chaining:

    const updateState = item => ({
      userObj.firstName = item?.firstName ?? userObj.firstName ,
      userObj.lastName  = item?.lastName  ?? userObj.lastName  ,
      userObj.userID    = item?.userID    ?? userObj.userId    ,
    });
    

    You could use the spread operator:

    const updateState = item => {
      userObj = { ...userObj, ...item };
      return userObj;
    }
    

    Or you can use lodash's defaults() function:

    const _ = require('lodash');
    
    const updateState = item => {
      userObj = _.defaults(userObj, item)
      return userObj;
    }
    

    Or... if you really want to mutate the state object, rather than creating a new one and replacing it, roll your own, similar:

    const updateState = item => {
      for (const key in item ) {
        const hasValue = key != null && key != undefined && key != NaN ;
    
        if ( hasValue ) {
          userObj[prop] = item[prop];
        }
    
      }
    }
    

    There is, as they say, more than one way to skin a cat.

    [Edited: Add explanation of the spread operator]

    The spread operator,

    const obj = { ...obj1, ...obj2, . . . , ...objN };
    

    is somewhat akin to calling a function like this:

    const obj = mergeObjects( obj1, obj2, . . ., objN );
    

    where mergeObjects() is defined as:

    function mergeObjects(...objects) {
      const mergedObject = {};
    
      for (const obj of objects ) {
        for (const key in obj ) {
          mergedObject[key] = item[key];
        }
      }
    
      return mergedObject;
    }
    

    Or perhaps a better explanation might be done using Object.assign(). One could say that an expression like:

    const obj = {
      
      prop1: 'a' ,
      prop2: 'b' ,
      
      ...obj1    ,
      
      prop3: 'c' ,
      prop4: 'd' ,
      
      ...obj2    ,
      
      prop5: 'e' ,
      prop6: 'f' ,
      
      ...obj3    ,
      
    }
    

    is the equivalent of this:

    const obj = Object.assign( {},
      {
        prop1: 'a' ,
        prop2: 'b' ,
      },
      obj1 ,
      {
        prop3: 'c' ,
        prop4: 'd' ,
      } ,
      obj2 ,
      {
        prop5: 'e' ,
        prop6: 'f' ,
      } ,
      obj3 ,
    );