Reputation: 2286
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
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
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
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>
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
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