5

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?

2
  • Have you seen this? reactjs.org/docs/hooks-reference.html#usereducer Commented Aug 20, 2020 at 7:01
  • @ksav Yeah I forgot to mention that I want to use states only inside of a component Commented Aug 20, 2020 at 7:19

4 Answers 4

6

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>
Sign up to request clarification or add additional context in comments.

Comments

2

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"})}

3 Comments

This is a cleaner and more scalable approach especially when something like redux
@malikbagwala You do not have to be using redux to make use of useReducer.
@ksav I am well aware of that. I just meant this makes things consistent when you are also using something like redux
2

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>

Comments

1

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>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.