This is one of the things that you should see with your eyes, so I'd recommend watching me go through the advantages and disadvantages of different ways of using hooks for dealing with state in React.
I start with useState, show a bug in the below code, then refactor to useReducer, add redux/toolkit to it, and then finally refactor to "use-complex-state". I'm leaving the source code here, so you can play around with it:
useState with a bug
function App() {
const [counter, setCount] = useState(0);
return (
<div className="App">
<header className="App-header">
{counter}
<div
onClick={() => {
console.log("2 points");
setCount(counter + 2);
}}
>
2 points!
<button
onClick={() => {
console.log("1 point");
setCount(counter + 1);
}}
>
1 point
</button>
</div>
</header>
</div>
);
}
useReducer - everything works, but verbose code
const initialState = { counter: 0 };
function reducer(
state = initialState,
action: { type: string; payload?: any }
) {
switch (action.type) {
case "increment":
return { counter: state.counter + 1 };
case "incrementBy":
return { counter: state.counter + action.payload };
default:
throw new Error();
}
}
function App() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div className="App">
<header className="App-header">
{state.counter}
<div
className="Points"
onClick={() => {
console.log("div clicked add two points payload");
dispatch({ type: "incrementBy", payload: 2 });
}}
>
<br />
<button
onClick={() => {
console.log("button clicked add one point payload");
dispatch({ type: "increment" });
}}
>
1 point
</button>
</div>
</header>
</div>
);
}
useReducer with Redux Toolkit
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
const initialState = { count: 0 };
const {
actions: { increment, incrementBy },
reducer,
} = createSlice({
name: "counter",
initialState,
reducers: {
increment: (state) => {
state.count += 1;
},
incrementBy: (state, action: PayloadAction<number>) => {
state.count += action.payload;
},
},
});
function App() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div className="App">
<header className="App-header">
{state.count}
<div
onClick={() => {
dispatch(incrementBy(2));
}}
>
2 points!
<button
onClick={() => {
dispatch(increment());
}}
>
1 point
</button>
</div>
</header>
</div>
);
}
useComplexState hook
import { PayloadAction } from "@reduxjs/toolkit";
import { useComplexState } from "use-complex-state";
import "./App.css";
const initialState = { count: 0 };
export default function App() {
const [state, { incrementBy, increment }] = useComplexState({
initialState,
reducers: {
increment: (state) => {
state.count += 1;
},
incrementBy: (state, action: PayloadAction<number>) => {
state.count += action.payload;
},
},
});
return (
<div className="App">
<header className="App-header">
{state.count}
<div
className="Points"
onClick={() => {
console.log("increment by 2 with complex state");
incrementBy(2);
}}
>
2 points!
<br />
<button
onClick={() => {
console.log("increment with complex state");
increment();
}}
>
1 point
</button>
</div>
</header>
</div>
);
}
Let me know if you have any questions or thoughts in the comments below.
