Home > Mobile >  Is there any cleaner way to make this amount of comparations in an if clause?
Is there any cleaner way to make this amount of comparations in an if clause?

Time:10-04

if (this.state.height !== prevState.height || this.state.hair !== prevState.hair || this.state.weight !== prevState.weight || this.state.eyes !== prevState.eyes || this.state.activity !== prevState.activity || this.state.gender !== prevState.gender || this.state.age !== prevState.age || this.state.wage !== prevState.wage || this.state.city !== prevState.city || this.state.disability !== prevState.disability) {
      this.setState({
        person: {
          height: this.state.height, hair: this.state.hair, weight: this.state.weight, city: this.state.city, eyes: this.state.eyes, disability: this.state.disability,
        },
      });
      this.setState({ showSave: this.state.height.length > 0 && this.state.hair.length > 0 && this.state.activity.length > 0 && this.state.gender.length > 0 && this.state.age.length > 0 && this.state.wage.length > 0 && this.state.overtime.length > 0 && this.state.allowance.length > 0 });
    }

What I'm doing is saving the state of a form, the state person has the fields that are set above whenever one of them change, and the state showSave depends of some of those fields and some others, which is also updated if any of the above fields change. Also, the state of the class has more fields that are not needed in this if.

I'm looking for a cleaner / easier to read way of making all these comparations without needing to add all of this.

CodePudding user response:

/**
* Factory function to define which properties in state you want to compare
* @param {string[]} keys the keys in state you want to compare
* @returns {(state, prevState) => boolean} function to compare state instances
*/
const makeCompareState = (keys) => {
  /**
  * @param {state} current state instance
  * @param {prevState} previous state instance
  * @returns {boolean} true if properties in state are unchanged, false otherwise
  */
  const compareState = (state, prevState) => {
    return keys.every(key => state[key] === prevState[key])
  }

  return compareState;
};


// Define the keys of state you want to compare
const compareState = makeCompareState(["height", "weight", "hair", "city"])

if(compareState(state, prevState) {
 // state has not changed
} else {
 // state has changed
}

Note, this solution only works for primitive value since it does a shallow comparison, ie. this will not work for comparing objects, functions, or arrays.

Alternatively, you can use a third-party solution if you don't mind the extra bundle size, like dequal, to do a deep comparison of state

import { isEqual } from "dequal";

if(isEqual(state, prevState)) {
  // state is unchanged
} else {
  // state has changed
}

CodePudding user response:

You can use loadash library isEqual method for this. It deep compare 2 objects.This way it is more cleaner.

const _ = require('lodash');
if(!_.isEqual(this.state, prevState)){//...}

CodePudding user response:

Your state is inconsistent. It's signature is either

{
  gender:any,
  height:any,
  hair:any,
  weight:any,
  city:any,
  eyes:any,
  allowance:any,
  wage:any,
  overtime:any,
  disability:any
}

or

{
  showSave:boolean
}

I would strongly encourage you upgrade to at least React 16.8, convert your app into functional components wherever possible, and use useReducer instead.

const INITIAL_STATE = {
  gender: '',
  height: '',
  hair: '',
  weight: '',
  city: '',
  eyes: '',
  allowance: '',
  wage: '',
  overtime: '',
  disability: '',
  showSave: false
};

const characterStateReducer = (state, action) => {
  if (action.type === 'set' && action.attributes) {
    // copy from previous state
    const newState = { ...state };

    for (const attr in action.attributes) {
      newState[attr] = action.attributes[attr];
    }        

    // update showSave
    newState.showSave = 
      newState.height.length > 0 && 
      newState.hair.length > 0 && 
      newState.activity.length > 0 && 
      newState.gender.length > 0 && 
      newState.age.length > 0 && 
      newState.wage.length > 0 && 
      newState.overtime.length > 0 && 
      newState.allowance.length > 0;

    return newState;
  } else {
    // NOTE: returning same state won't trigger updates
    return state;
  }
}

and use this as

const [state, dispatch] = useReducer(characterStateReducer, INITIAL_STATE);

where dispatch is called like

dispatch({ 
  type:'set',
  attributes: {
    height:"5'10''"
  }
});
  • Related