Skip to content

Commit e743c70

Browse files
committed
update reducer exercise
1 parent fb1363f commit e743c70

File tree

4 files changed

+114
-58
lines changed

4 files changed

+114
-58
lines changed

src/components/patterns/CustomHooks/exercise/index.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const Bonus = () => {
1010
<h3>🏋️‍♀️Exercise</h3>
1111
<h4>
1212
🎯 The goal is to extract some component logic into a reusable function
13-
avoiding pitfalls
13+
avoiding common pitfalls
1414
</h4>
1515

1616
<h1>

src/components/patterns/HookReducer/Page.jsx

Lines changed: 70 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,36 +5,84 @@ import Exercise from "./exercise";
55
const Page = () => (
66
<React.Fragment>
77
<h2>Hook Reducer</h2>
8+
<p>
9+
From the{" "}
10+
<a
11+
href="https://reactjs.org/docs/hooks-reference.html#usereducer"
12+
target="_blank"
13+
>
14+
React docs
15+
</a>
16+
</p>
17+
<blockquote>
18+
React.useReducer is usually preferable to useState when you have complex
19+
state logic that involves multiple sub-values or when the next state
20+
depends on the previous one.{" "}
21+
<em>(We'll practice this in this exercise)</em>
22+
</blockquote>
23+
<blockquote>
24+
React.useReducer also lets you optimize performance for components that
25+
trigger deep updates because you can pass dispatch down instead of
26+
callbacks.{" "}
27+
<em>(We'll practice this in the next exercise about Context)</em>
28+
</blockquote>
829
<h3>Example</h3>
30+
<p>Basic login form</p>
931
<Example />
32+
<hr />
33+
<h3>🥑 Before the exercise 🏋️‍♀️</h3>
34+
<p>
35+
The code of the example is very similar to the code of the exercise. The
36+
example contains explanations 👩‍🏫, the exercise contains tasks 🚧 and hints
37+
🕵️‍♀️. If there are things that you don't understand about the code it's
38+
better to look at the example. If there are things that are not clear
39+
about what needs to done in the exercise after checking the tasks, let me
40+
know.
41+
</p>
1042

1143
<h3>Exercise</h3>
44+
45+
<h4>
46+
🎯 The goal is to understand how to handle complex state logic in our
47+
components that involves multiple sub-values
48+
</h4>
49+
1250
<p>
1351
Refactor the LoginForm component in{" "}
1452
<code>src/components/patterns/HookReducer/exercise/index.jsx</code> so it
15-
implements the following features:
16-
</p>
17-
<ul>
18-
<li>
19-
Implement the SET_ERRORS action in the reducer. It should add an errors
20-
object to the the state using the action.payload
21-
</li>
22-
<li>
23-
<code>dispatch</code> a SET_ERRORS action with the errors (output from
24-
the
25-
<code>validate(state.values)</code> invocation) as payload.
26-
</li>
27-
<li>
28-
<code>dispatch</code> the SET_ERRORS action only when the state of the
29-
input fields change. Hint, you need to use the useEffect second
30-
argument.
31-
</li>
32-
<li>
33-
Bonus, refactor the Hook Reducer so it's in a custom hook. You can call
34-
it useForm.
35-
</li>
36-
</ul>
53+
implements the following:
54+
</p>
55+
<p>
56+
<input type="checkbox" /> 1. Handles the SET_ERRORS action in the reducer.
57+
It should add an errors object to the the state using the action.payload
58+
</p>
59+
<p>
60+
<input type="checkbox" /> 2. <code>dispatch</code> a SET_ERRORS action
61+
with the errors (output from the
62+
<code>validate(state.values)</code> invocation) as payload.
63+
</p>
64+
<p>
65+
<input type="checkbox" /> 3.<code>dispatch</code> the SET_ERRORS action
66+
only when the state of the input fields change. Hint, you need to use the
67+
useEffect second argument.
68+
</p>
69+
3770
<Exercise />
71+
<hr />
72+
<h3>Bonus exercise</h3>
73+
<p>
74+
<input type="checkbox" /> Create a custom hook from your Login Form. You
75+
can call it useForm.
76+
</p>
77+
<p>
78+
🕵️‍♀️ Hints :
79+
<ul>
80+
<li>Extract the useReducer outside the Login Form</li>
81+
<li>
82+
Don't think of state only, but also functions that create "props".
83+
</li>
84+
</ul>
85+
</p>
3886
</React.Fragment>
3987
);
4088

src/components/patterns/HookReducer/example/index.jsx

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,42 +2,53 @@ import React from "react";
22

33
const SET_FIELD_VALUE = "SET_FIELD_VALUE";
44

5+
// 👩‍🏫The state of a form is a relatively complex state logic.
6+
// Using a reducer also helps separate reads, from writes.
57
function reducer(state, action) {
68
switch (action.type) {
9+
// 👩‍🏫reducers should respond to a given action
710
case SET_FIELD_VALUE:
11+
// 👩‍🏫reducers should not mutate the current state but create a new one
12+
// (If you’re familiar with Redux, you already know how this works, but we have to mention it :)
813
return {
914
...state,
1015
values: {
1116
...state.values,
12-
...action.payload
13-
}
17+
...action.payload,
18+
},
1419
};
1520
default:
21+
// 👩‍🏫reducers should return the current state if the received action is not a case to be handled
22+
// (If you’re familiar with Redux, you already know all this, but we have to mention it anyway :)
1623
return state;
1724
}
1825
}
1926

20-
const LoginForm = props => {
27+
const LoginForm = (props) => {
2128
const initialState = {
22-
values: props.initialValues
29+
values: props.initialValues,
2330
};
24-
// https://reactjs.org/docs/hooks-reference.html#usereducer
31+
32+
// 👩‍🏫 useReducer accepts a reducer of type (state, action) => newState,
33+
// and returns the current state paired with a dispatch method
2534
const [state, dispatch] = React.useReducer(reducer, initialState);
2635

27-
const handleChange = fieldName => event => {
36+
// 👩‍🏫 Notice we are using a closure here. As a mental model from the closure exercise we did before:
37+
// add = (a) => (b) =>
38+
const handleChange = (fieldName) => (event) => {
2839
event.preventDefault();
2940
dispatch({
3041
type: SET_FIELD_VALUE,
31-
payload: { [fieldName]: event.target.value }
42+
payload: { [fieldName]: event.target.value },
3243
});
3344
};
3445

35-
const getFieldProps = fieldName => ({
46+
const getFieldProps = (fieldName) => ({
3647
value: state.values[fieldName],
37-
onChange: handleChange(fieldName)
48+
onChange: handleChange(fieldName), // 👩‍🏫 fieldName gets "captured" in the handleChange closure
3849
});
3950

40-
const handleSubmit = e => {
51+
const handleSubmit = (e) => {
4152
e.preventDefault();
4253
alert(JSON.stringify(state.values));
4354
};
@@ -62,15 +73,12 @@ const LoginForm = props => {
6273
};
6374

6475
const Example = () => (
65-
<React.Fragment>
66-
<p>Basic Login Form</p>
67-
<LoginForm
68-
initialValues={{
69-
password: "",
70-
userId: ""
71-
}}
72-
/>
73-
</React.Fragment>
76+
<LoginForm
77+
initialValues={{
78+
password: "",
79+
userId: "",
80+
}}
81+
/>
7482
);
7583

7684
export default Example;

src/components/patterns/HookReducer/exercise/index.jsx

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
import React from "react";
22

3-
// Using a reducer helps separate reads, from writes
43
function reducer(state, action) {
54
switch (action.type) {
6-
// TODO add a SET_ERRORS case that adds an erros key to the state with the action.payload
5+
// 🚧 Add a SET_ERRORS case that adds an errors key to the state with the action.payload
6+
// 🕵️‍♀️ You probably want to clear previous errors every time you do SET_ERRORS
77
case "SET_FIELD_VALUE":
88
return {
99
...state,
1010
values: {
1111
...state.values,
12-
...action.payload
13-
}
12+
...action.payload,
13+
},
1414
};
1515
default:
1616
return state;
@@ -19,7 +19,8 @@ function reducer(state, action) {
1919

2020
function LoginForm(props) {
2121
const { initialValues, onSubmit } = props;
22-
const validate = values => {
22+
// 👮‍♀you don't have to edit this validate function
23+
const validate = (values) => {
2324
let errors = {};
2425
if (!values.password) {
2526
errors.password = "Password is required";
@@ -32,36 +33,35 @@ function LoginForm(props) {
3233

3334
const [state, dispatch] = React.useReducer(reducer, {
3435
values: initialValues,
35-
errors: {}
36+
errors: {},
3637
});
3738

3839
React.useEffect(() => {
3940
if (validate) {
4041
const errors = validate(state.values);
41-
// TODO dispatch a SET_ERRORS action with the errors as payload
42+
// 🚧 dispatch a SET_ERRORS action with the errors as payload
4243
}
43-
}, []); // TODO dispatch the SET_ERRORS action only when the state of the input fields change.
44+
}, []); // 🚧 dispatch the SET_ERRORS action only when the state of the input fields change.
4445

45-
// This is a function that returns another function so it creates a closure to capture fieldName
46-
const handleChange = fieldName => event => {
46+
const handleChange = (fieldName) => (event) => {
4747
event.preventDefault();
4848
dispatch({
4949
type: "SET_FIELD_VALUE",
50-
payload: { [fieldName]: event.target.value }
50+
payload: { [fieldName]: event.target.value },
5151
});
5252
};
5353

54-
const handleSubmit = event => {
54+
const handleSubmit = (event) => {
5555
event.preventDefault();
5656
const errors = validate(state.values);
5757
if (!Object.keys(errors).length) {
5858
onSubmit(state.values);
5959
}
6060
};
6161

62-
const getFieldProps = fieldName => ({
62+
const getFieldProps = (fieldName) => ({
6363
value: state.values[fieldName],
64-
onChange: handleChange(fieldName) // fieldName gets captured in the handleChange closure
64+
onChange: handleChange(fieldName),
6565
});
6666

6767
const { errors } = state;
@@ -95,9 +95,9 @@ const Exercise = () => (
9595
<LoginForm
9696
initialValues={{
9797
password: "",
98-
userId: ""
98+
userId: "",
9999
}}
100-
onSubmit={values => {
100+
onSubmit={(values) => {
101101
alert(JSON.stringify(values, null, 2));
102102
}}
103103
/>

0 commit comments

Comments
 (0)