GoonGamja
GoonGamja

Reputation: 2286

Better way of updating several states using react hook inside if statement?

I am making a simple app that user can increment or decrement number of adult, child and baby.

I want to manage these state inside one state as a form of object like this below.

  const [numberState, setNumberState] = useState({
    adultCount: 0,
    childCount: 0,
    babyCount: 0
  });

And below code is the part where user can increase or decrease number of adult, child and baby.

            <div className="tit">Number</div>
                <dl>
                    <dt><span>Adult</span></dt>
                        <dd>
                            <div className="number_count">
                                <button type="button" className="btn_minus" onClick={() => handleNumberState("adult", "minus")}>minus</button>
                                <span className="input-num"><input type="number" value={adultCount} /></span>
                                <button type="button" className="btn_plus" onClick={() => handleNumberState("adult", "plus")}>plus</button>
                            </div>
                        </dd>
                    <dt><span>Child</span></dt>
                        <dd>
                            <div className="number_count">
                                <button type="button" className="btn_minus" onClick={() => handleNumberState("child", "minus")}>minus</button>
                                <span className="input-num"><input type="number" value={childCount} /></span>
                                <button type="button" className="btn_plus" onClick={() => handleNumberState("child", "plus")}>plus</button>
                            </div>
                        </dd>
                    <dt><span>Baby</span></dt>
                        <dd>
                            <div className="number_count">
                                <button type="button" className="btn_minus" onClick={() => handleNumberState("baby", "minus")}>minus</button>
                                <span className="input-num"><input type="number" value={babyCount} /></span>
                                <button type="button" className="btn_plus" onClick={() => handleNumberState("baby", "plus")}>plus</button>
                            </div>
                        </dd>
                    </dl>
                </div>

As you can see when user clicks the button, it passes two arguments to handleNumberState callback function.

Then function looks like this below.

const handleNumberState = useCallback((gen, state) => {
  if (gen === "adult" && state === "minus" && numberState.adultCount > 0) {
    setNumberState(prevState => {
      return {
        adultCount: prevState.adultCount - 1,
        childCount: prevState.childCount,
        babyCount: prevState.babyCount
      }
    }) 
  }
  if (gen === "adult" && state === "plus") {
    setNumberState(prevState => {
      return {
        adultCount: prevState.adultCount + 1,
        childCount: prevState.childCount,
        babyCount: prevState.babyCount
      }
    }) 
  }
  if (gen === "child" && state === "minus" && numberState.childCount > 0) {
    setNumberState(prevState => {
      return {
        adultCount: prevState.adultCount,
        childCount: prevState.childCount - 1,
        babyCount: prevState.babyCount
      }
    }) 
  }
  if (gen === "child" && state === "plus") {
    setNumberState(prevState => {
      return {
        adultCount: prevState.adultCount,
        childCount: prevState.childCount + 1,
        babyCount: prevState.babyCount
      }
    }) 
  }
  if (gen === "baby" && state === "minus" && numberState.babyCount > 0) {
    setNumberState(prevState => {
      return {
        adultCount: prevState.adultCount,
        childCount: prevState.childCount,
        babyCount: prevState.babyCount - 1
      }
    }) 
  }
  if (gen === "baby" && state === "plus") {
    setNumberState(prevState => {
      return {
        adultCount: prevState.adultCount,
        childCount: prevState.childCount,
        babyCount: prevState.babyCount + 1
      }
    }) 
  }
})

Depends on what arguments get passed to the function, state is changed inside if statement.

But the code seems messy.

Is there a better way I can make this work using less if statement?

Upvotes: 5

Views: 311

Answers (4)

Kevin Hoopes
Kevin Hoopes

Reputation: 507

My suggestion (and the one given by the React developers in their FAQ) would be to separate out those state variables into three separate ones, so that they can be updated independently of one another.

Once those are separated, you can actually remove that whole handleNumberState() function completely, and replace the onClick with callbacks that call your set methods.

const [adultCount, setAdultCount] = useState(0);
const [childCount, setChildCount] = useState(0);
const [babyCount, setBabyCount] = useState(0);

...

<div className="tit">Number</div>
   <dl>
      <dt><span>Adult</span></dt>
         <dd>
            <div className="number_count">
               <button type="button" className="btn_minus" onClick={() => if (adultCount > 0) { setAdultCount(adultCount - 1) }}>minus</button>
               <span className="input-num"><input type="number" value={adultCount} /></span>
               <button type="button" className="btn_plus" onClick={() => setAdultCount(adultCount + 1)}>plus</button>
            </div>
         </dd>
      <dt><span>Child</span></dt>
         <dd>
            <div className="number_count">
               <button type="button" className="btn_minus" onClick={() => if (childCount > 0) { setChildCount(childCount - 1) }}>minus</button>
               <span className="input-num"><input type="number" value={childCount} /></span>
               <button type="button" className="btn_plus" onClick={() => setChildCount(childCount + 1)}>plus</button>
            </div>
         </dd>
      <dt><span>Baby</span></dt>
         <dd>
            <div className="number_count">
               <button type="button" className="btn_minus" onClick={() => if (babyCount > 0) { setBabyCount(babyCount - 1) }}>minus</button>
               <span className="input-num"><input type="number" value={babyCount} /></span>
               <button type="button" className="btn_plus" onClick={() => setBabyCount(babyCount + 1)}>plus</button>
            </div>
         </dd>
      </dl>
  </div>

Upvotes: 1

Awais Rafiq Chaudhry
Awais Rafiq Chaudhry

Reputation: 490

Change your handler to this:

const handleNumberState = useCallback((gen, state) => {
    setNumberState(prevState => {
      return { ...prevState, [gen]: prevState[gen] + state }
    }) 
  })

and update your handleNumberState arguments like below, passing the name and the value that needs to be added i.e 1(for plus) & (-1)minus. This will reduce your function handleNumberState and help get rid of lengthy code.

<div className="tit">Number
      <dl>
        <dt><span>Adult</span></dt>
            <dd>
                <div className="number_count">
                    <button type="button" className="btn_minus" onClick={() => handleNumberState("adultCount", -1)}>minus</button>
                    <span className="input-num"><input type="number" value={adultCount} /></span>
                    <button type="button" className="btn_plus" onClick={() => handleNumberState("adultCount", +1)}>plus</button>
                </div>
            </dd>
        <dt><span>Child</span></dt>
            <dd>
                <div className="number_count">
                    <button type="button" className="btn_minus" onClick={() => handleNumberState("childCount", -1)}>minus</button>
                    <span className="input-num"><input type="number" value={childCount} /></span>
                    <button type="button" className="btn_plus" onClick={() => handleNumberState("childCount", +1)}>plus</button>
                </div>
            </dd>
        <dt><span>Baby</span></dt>
            <dd>
                <div className="number_count">
                    <button type="button" className="btn_minus" onClick={() => handleNumberState("babyCount", -1)}>minus</button>
                    <span className="input-num"><input type="number" value={babyCount} /></span>
                    <button type="button" className="btn_plus" onClick={() => handleNumberState("babyCount", +1)}>plus</button>
                </div>
            </dd>
        </dl>
    </div>

Upvotes: 2

Drew Reese
Drew Reese

Reputation: 202608

Update handler to take a field and value instead.

const handleNumberState = (field, value) => {
  setNumberState(prevState => ({
    ...prevState,
    [field]: Math.max(0, prevState[field] + value),
  }))
}

Pass the field name and value to add to the handler.

<div className="tit">Number</div>
<dl>
  <dt>
    <span>Adult</span>
  </dt>
  <dd>
    <div className="number_count">
      <button
        type="button"
        className="btn_minus"
        onClick={() => handleNumberState("adultCount", -1)}
      >
        minus
      </button>
      <span className="input-num">
        <input type="number" value={adultCount} />
      </span>
      <button
        type="button"
        className="btn_plus"
        onClick={() => handleNumberState("adultCount", 1)}
      >
        plus
      </button>
    </div>
  </dd>
  <dt>
    <span>Child</span>
  </dt>
  <dd>
    <div className="number_count">
      <button
        type="button"
        className="btn_minus"
        onClick={() => handleNumberState("childCount", -1)}
      >
        minus
      </button>
      <span className="input-num">
        <input type="number" value={childCount} />
      </span>
      <button
        type="button"
        className="btn_plus"
        onClick={() => handleNumberState("childCount", 1)}
      >
        plus
      </button>
    </div>
  </dd>
  <dt>
    <span>Baby</span>
  </dt>
  <dd>
    <div className="number_count">
      <button
        type="button"
        className="btn_minus"
        onClick={() => handleNumberState("babyCount", -1)}
      >
        minus
      </button>
      <span className="input-num">
        <input type="number" value={babyCount} />
      </span>
      <button
        type="button"
        className="btn_plus"
        onClick={() => handleNumberState("babyCount", 1)}
      >
        plus
      </button>
    </div>
  </dd>
</dl>

Edit better-way-of-updating-several-states-using-react-hook-inside-if-statement

A minor optimization would be to create a curried handler that consumes a value and returns a callback function consuming the click event

const handleNumberState = value => e => {
  e.preventDefault();
  const { name } = e.target;
  setNumberState((prevState) => ({
    ...prevState,
    [name]: Math.max(0, prevState[name] + value)
  }));
};

Give each button a name attribute which is passed with the click event, ex

<button
  name="adultCount"
  type="button"
  className="btn_minus"
  onClick={handleNumberState(-1)}
>
  minus
</button>
<span className="input-num">
  <input type="number" value={adultCount} />
</span>
<button
name="adultCount"
  type="button"
  className="btn_plus"
  onClick={handleNumberState(1)}
>
  plus
</button>

Upvotes: 6

strdr4605
strdr4605

Reputation: 4352

You may want to useReducer

const initialState = { adultCount: 0, childCount: 0, babyCount: 0 };

function reducer(state, action) {
  switch (action.type) {
    case "increment":
      return { ...state, [action.payload]: state[action.payload] + 1 };
    case "decrement":
      return {
        ...state,
        [action.payload]: Math.max(state[action.payload] - 1, 0),
      };
    default:
      throw new Error();
  }
}

...

const [state, dispatch] = useReducer(reducer, initialState);

...

onClick={() => dispatch({type: "increment", payload:"adultCount"})}

Upvotes: 2

Related Questions