1This is the very basic example on React homepage.
2useState() to hold the count
3
4Since we need to persist the count and also render it on UI, useState() is the right approach here.
5
6import React, {useState} from 'react'
7export function App() {
8 const [count, setCount] = useState(0)
9 return (
10 <div>
11 <button data-testid="decrement-button" onClick={() => setCount(count - 1)}>-</button>
12 <button data-testid="increment-button" onClick={() => setCount(count + 1)}>+</button>
13 <p>clicked: {count}</p>
14 </div>
15 )
16}
17
18better use useState((prevState) => newStae)
19
20setCount(count - 1) is not the best option, explained here, let's try passing a function.
21
22import React, {useState} from 'react'
23export function App() {
24 const [count, setCount] = useState(0)
25 return (
26 <div>
27 <button data-testid="decrement-button" onClick={() => setCount(count => count - 1)}>-</button>
28 <button data-testid="increment-button" onClick={() => setCount(count => count + 1)}>+</button>
29 <p>clicked: {count}</p>
30 </div>
31 )
32}
33
34useCallback() to persist the callbacks
35
36onClick now is set with an arrow function, this function is newly created on every render, we can use useCallbac() to avoid this.
37
38import React, {useState, useCallback} from 'react'
39export function App() {
40 const [count, setCount] = useState(0)
41 const decrease = useCallback(() => setCount(count => count - 1), [])
42 const increase = useCallback(() => setCount(count => count + 1), [])
43 return (
44 <div>
45 <button data-testid="decrement-button" onClick={decrease}>-</button>
46 <button data-testid="increment-button" onClick={increase}>+</button>
47 <p>clicked: {count}</p>
48 </div>
49 )
50}
51
52data attributs could be used to create single event handler
53
54In above example, we need to create hander for decrease and increase, this is not scalable. We can create just one handler and config it by data attributes
55
56import React, {useState, useCallback} from 'react'
57export function App() {
58 const [count, setCount] = useState(0)
59 const update = useCallback((e: React.MouseEvent<HTMLButtonElement>) => {
60 const change = parseInt(e.currentTarget.dataset.change ?? '0')
61 setCount(count => count + change)
62 }, [])
63 return (
64 <div>
65 <button data-testid="decrement-button" data-change="-1" onClick={update}>-</button>
66 <button data-testid="increment-button" data-change="1" onClick={update}>+</button>
67 <p>clicked: {count}</p>
68 </div>
69 )
70}
71
72useReducer() could be used for even more complex interactions
73
74This is the code demo from React homepage, which demonstrrate useReducer() as an alternative to useState(), which might be more favorable if you are used to Redux toolchain.
75
76import React, { useReducer } from 'react';
77const initialState = { count: 0 };
78function reducer(state: {count: number}, action: {type: 'increment' | 'decrement'}) {
79 switch (action.type) {
80 case 'increment':
81 return { count: state.count + 1 };
82 case 'decrement':
83 return { count: state.count - 1 };
84 }
85}
86export function App() {
87 const [state, dispatch] = useReducer(reducer, initialState);
88 return (
89 <div>
90 <button
91 data-testid="decrement-button"
92 onClick={() => dispatch({ type: 'decrement' })}
93 >
94 -
95 </button>
96 <button
97 data-testid="increment-button"
98 onClick={() => dispatch({ type: 'increment' })}
99 >
100 +
101 </button>
102 <p>clicked: {state.count}</p>
103 </div>
104 );
105}