9 | Closure is when a function is able to remember and access its lexical
10 | scope even when that function is executing outside its lexical scope. Kyle
11 | Simpson
12 |
13 |
14 | “[Lexical scoping] how a parser resolves variable names when functions are
15 | nested”. Mozilla Dev Net
16 |
17 |
🏋️♀️Exercise
18 |
19 | Open the console on your browser and type [closure exercise] in the
20 | console filter. You should see on the console the console.log for this
21 | exercise.
22 |
23 |
24 | 1. Go to{" "}
25 | src/components/functional-programming/closure/exercise.js and
26 | uncomment line 11. You should get the following error "TypeError: addFive
27 | is not a function"
28 |
29 |
30 | 2. To fix it you are only allowed to change the{" "}
31 | function add(). The function add() should be
32 | implemented in a way that
33 | addFive(7) outputs 12
34 |
35 | 🚨 YOU CAN ONLY EDIT THE function add() IN THAT FILE 🚨
36 |
37 |
38 |
39 | You know your implementation works because you'll see on the console:
40 | [closure exercise] addFive(7) is 12
41 |
42 |
Bonus, discuss about the solution with your peers
43 |
1- Is the inner function pure?
44 |
2- What's executed first, the inner function or the outer function?
45 |
46 | );
47 |
48 | export default Page;
49 |
--------------------------------------------------------------------------------
/src/components/functional-programming/closure/exercise.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-unused-vars */
2 |
3 | // Open the console on your browser and type [closure exercise] in the console filter.
4 | // You should see on the console the console.log() for this exercise.
5 |
6 | function add() {}
7 |
8 | const addFive = add(5);
9 |
10 | let result;
11 | // result = addFive(7); // should output 12
12 |
13 | console.log(`[closure exercise] addFive(7) is ${result}`);
14 |
--------------------------------------------------------------------------------
/src/components/functional-programming/composition/Page.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable jsx-a11y/accessible-emoji */
2 | import React from "react";
3 |
4 | import { transformText } from "./exercise";
5 | import FormExercise from "./bonus";
6 |
7 | const exampleText = "1 2 3 React GraphQL Academy is a m a z i n g";
8 |
9 | const Page = () => (
10 |
11 |
Function composition
12 |
Exercise
13 | With the transformText function we can transform{" "}
14 | "{exampleText}" into
15 | "{transformText(exampleText)}"
16 |
17 | Let's make that composition more declarative using a compose{" "}
18 | function:
19 |
20 |
21 | 1. Your first task is to implement the compose
22 | function. Go to{" "}
23 |
24 | {" "}
25 | src/components/functional-programming/composition/exercise/index.js
26 | {" "}
27 | and follow the hints.
28 |
29 |
30 | 2. Can you use your compose{" "}
31 | function to compose HoCs? You can try to use it along with the{" "}
32 | withWidth at the bottom of the file{" "}
33 | src/components/App.jsx
34 |
35 |
Bonus Exercise
36 |
37 | Validate the following form composing the validators defined in
38 |
39 | src/components/functional-programming/composition/bonus/valiators
40 |
41 | . To do that you'll need to finish the implementation of the
42 | composeValidators function defined in
43 |
44 | src/components/functional-programming/composition/bonus/index.js
45 |
46 |
68 |
69 | );
70 |
71 | export default Page;
72 |
--------------------------------------------------------------------------------
/src/components/functional-programming/composition/bonus/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Form, Field } from "react-final-form";
3 | import { required, mustBeEmail, atLeastFiveCharacters } from "./validators";
4 |
5 | // 🚧 Task 1, implement the composeValidators function
6 | // each validator has a value as input and returns undefined or the error message
7 | export const composeValidators = (...validators) => (value) =>
8 | validators.reduceRight((error, validator) => undefined, undefined);
9 |
10 | // 🚧 Task 2, you need to use the composeValidators so
11 | // - Email is validated with required and mustBeEmail
12 | // - Password is validatie with required and atLeastFiveCharacters
13 | const FormExercise = () => (
14 |
44 | )}
45 | />
46 | );
47 |
48 | const onSubmit = () => {};
49 |
50 | const Input = ({ input, meta, placeholder, type }) => (
51 |
52 |
53 | {meta.error && meta.touched && (
54 | {meta.error}
55 | )}
56 |
57 | );
58 |
59 | export default FormExercise;
60 |
--------------------------------------------------------------------------------
/src/components/functional-programming/composition/bonus/validators.js:
--------------------------------------------------------------------------------
1 | export const required = (value) => (value ? undefined : "Required");
2 |
3 | export const mustBeEmail = (value) => {
4 | const reEmail = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; // eslint-disable-line
5 | return reEmail.test(value) ? undefined : "Email format is not correct";
6 | };
7 |
8 | export const atLeastFiveCharacters = (value) =>
9 | value && value.length >= 5
10 | ? undefined
11 | : "You need to type at least 5 characters";
12 |
--------------------------------------------------------------------------------
/src/components/functional-programming/composition/exercise/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-unused-vars */
2 |
3 | const toUpperCase = (text) => text.toUpperCase();
4 |
5 | const removeSpaces = (text) => text.replace(/\s/g, "");
6 |
7 | const removeNumbers = (text) => text.replace(/[0-9]/g, "");
8 |
9 | // 🚧 Task 0: comment out the following transformText function and uncomment the one bellow
10 | export const transformText = (text) =>
11 | toUpperCase(removeSpaces(removeNumbers(text)));
12 |
13 | // 🚧 Task 1: implement the following compose function
14 | // export const transformText = compose(
15 | // toUpperCase,
16 | // removeNumbers,
17 | // removeSpaces
18 | // );
19 | // 🕵️♀️Hints:
20 | // - The compose function should return another function (think of the previous addFive, same idea)
21 | // - Spread the arguments of the compose function
22 | // - Use https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduceRight
23 |
--------------------------------------------------------------------------------
/src/components/functional-programming/memoization/Page.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable jsx-a11y/accessible-emoji */
2 | import React from "react";
3 | import "./exercise";
4 |
5 | const Page = () => (
6 |
7 |
Memoization
8 |
9 | Memoization is an optimization technique that stores the results of a
10 | function call and returns the cached result when the same inputs are
11 | supplied again.
12 |
13 |
🏋️♀️ Exercise
14 |
15 | Open the console on your browser and type [memoization exercise] in the
16 | console filter. You should see on the console the console.log() for this
17 | exercise.{" "}
18 |
19 |
20 | Given the function memoize() in
21 | `src/components/functional-programming/memoization/exercise`:
22 |
23 |
24 |
25 | 1. Pair up and explain to each other how the memoize function works with
26 | the doEasyWork function.
27 |
28 |
29 |
Where is the closure? Between line X and Y
30 |
What variable/s are captured in the closure?
31 |
32 |
33 |
34 |
35 | 2. Explain to each other how the memoize function works with doHardWork.
36 | Does the memoize function work differently?
37 |
38 |
39 |
Bonus exercise
40 |
41 |
42 | b.1, Refactor the memoize function so it can memoize functions with any
43 | number of arguments. You can use the function doAnyWork to test your
44 | refactored memoize function.
45 |
46 |
47 |
48 | b.2, Extract the key cache functionality to a "resolver" function. 🕵️♂️
49 | hint: see how{" "}
50 |
55 | lodash implements it
56 |
57 |
58 |
59 | );
60 |
61 | export default Page;
62 |
--------------------------------------------------------------------------------
/src/components/functional-programming/memoization/exercise.js:
--------------------------------------------------------------------------------
1 | /*
2 | Exercise
3 |
4 | Open the console on your browser and type [memoization exercise] in the console filter.
5 | You should see on the console the console.log() for this exercise.
6 |
7 | 1. Pair up and explain to each other how the memoize function works with the doEasyWork function.
8 |
9 | 2. Where is the closure?
10 |
11 | 3. Explain to each other how the memoize function works with doHardWork.
12 | Does the memoize function work differently?
13 |
14 | 4. Bonus, refactor the memoize function so it can memoize functions with any number of arguments-
15 | You can use the function doAnyWork to test your refactored memoize function
16 | */
17 |
18 | export async function doEasyWork(amount) {
19 | console.log(`[memoization exercise] ${amount} easy units produced`);
20 | }
21 |
22 | export async function doHardWork(amount) {
23 | console.log("[memoization exercise] doing work");
24 | await new Promise((resolve) => setTimeout(resolve, amount));
25 | console.log(`[memoization exercise] ${amount} units of hard work produced!`);
26 |
27 | return amount;
28 | }
29 |
30 | export function doAnyWork(amount = 1, amount2 = 1, amount3 = 1) {
31 | return amount + amount2 + amount3;
32 | }
33 |
34 | function memoize(fn) {
35 | let cache = {};
36 | return (amount) => {
37 | if (amount in cache) {
38 | console.log("[memoization exercise] output from cache");
39 | return cache[amount];
40 | } else {
41 | let result = fn(amount);
42 | cache[amount] = result;
43 | return result;
44 | }
45 | };
46 | }
47 |
48 | const memoizedDoWork = memoize(doEasyWork);
49 | memoizedDoWork(4000);
50 | memoizedDoWork(4000);
51 |
52 | // Bounus
53 | // const memoizedDoWork = memoize(doAnyWork);
54 | // console.log(`[memoization exercise] ${memoizedDoWork(1, 2, 3)} === 6 ?`);
55 | // console.log(`[memoization exercise] ${memoizedDoWork(1, 50, 104)} === 155 ?`);
56 |
57 | // Bonus 2, extract the key cache functionality to a "resolver" function
58 |
--------------------------------------------------------------------------------
/src/components/patterns/CompoundComponents/Page.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import RadioGroup from "./example/RadioGroup";
4 | import RadioOption from "./example/RadioOption";
5 |
6 | const Page = () => (
7 |
28 | {
31 | console.log("radioValue", radioValue);
32 | }}
33 | >
34 |
35 | A component that returns another component
36 |
37 | A function that returns a component
38 |
39 | A component that passes props dinamically to its children
40 |
41 |
42 | I have no idea so I'll wait for my pair to answer
43 |
44 |
45 | ... still waiting for my pair to answer, I think neither of us have
46 | any clue
47 |
48 |
49 |
61 | Refactor the components in patterns/CompoundComponents/exercise/ so we
62 | don't have to pass explicitly the toggleMenu property on every MenuItem.
63 |
9 | Accepts a context object (the value returned from React.createContext) and
10 | returns the current context value for that context. The current context
11 | value is determined by the value prop of the nearest
12 | <MyContext.Provider> above the calling component in the tree.{" "}
13 |
18 | React docs
19 |
20 |
21 |
Example
22 |
23 |
24 |
25 |
26 |
Bonus Exercise 1
27 |
Now that you know how the React Context works:
28 |
29 |
30 | Would use the React Context for the form in
31 | the previous React Reducer Exercise? What are the pros and cons?
32 |
33 |
34 | If you use the React Context for the form,
35 | would you pass the value of the field and other props to the Field
36 | component using context or props?
37 |
38 |
39 | If you pass the value of the "input" and the
40 | other required props to the Field component using the React Context, do
41 | you think it still makes sense to use the React.memo HoC in the Field
42 | component?
43 |
44 |
45 |
46 |
Bonus Exercise 2
47 |
48 | In our current implementation the cache (data
49 | key in our reducer) for each pair query & variables, we can only send 1
50 | query at a time. How would you make it possible to send requests
51 | concurrently?
52 |
25 | When the nearest <MyContext.Provider> above the component updates,
26 | this Hook will trigger a rerender with the latest context value passed
27 | to that MyContext provider.{" "}
28 |
33 | React docs
34 |
35 |
36 |
Let me show you on the React Profiler what that quote means
25 | 🎯 The goal of this exercise is to use context in some real-world use
26 | cases, along with custom hooks and some other React additional Hooks
27 |
28 |
29 |
Exercise part 1, using context for the GraphQL client
30 |
31 | Our current implementation works, but we won't be able to easly write
32 | tests for our GraphQL queries because the fetching to the API is
33 | hardcoded.
34 |
35 |
36 | We can solve that by moving the GraphQL data fetching to the context.
37 | This way we can "inject" the data fetching dependency via context. Just
38 | like the{" "}
39 |
44 | MockedProvider
45 | {" "}
46 | from Apollo
47 |
48 |
Tasks part 1:
49 |
50 | 1.1. Go to{" "}
51 |
52 | src/components/patterns/Context/exercise/GraphQLProvider.jsx
53 | {" "}
54 | and create a context for the data fetching client. You can call it
55 | ClientContext.
56 |
57 |
58 | 1.2. Add the ClientContext Provider to the
59 | GraphQLProvider. Should the ClientContext wrap StoreContext? The other
60 | way around? Does it matter in this case?
61 |
62 |
63 | 1.3. Use the client from the context inside
64 | your useQuery. You can create a handy useClient custom hook like we did
65 | in the example{" "}
66 |
67 | src/components/patterns/Context/example/modal.jsx : useModal function{" "}
68 |
69 |
70 |
71 |
Tasks part 2:
72 |
73 | In large component trees, an alternative we recommend is to pass down a
74 | dispatch function from useReducer via context...
75 |
76 |
77 | ...If you use context to pass down the state too,{" "}
78 | use two different context types — the dispatch context
79 | never changes, so components that read it don’t need to rerender unless
80 | they also need the application state.{" "}
81 |
86 | React docs
87 |
88 |
89 |
🤔 React docs say "use two different context types". Let's do it!
90 |
91 | 2.1. Create two different context types for
92 | our StoreContext. One context for the dispatch, and another context for
93 | the state.
94 |
95 |
96 | 2.2. Great! We've implemented task 2.1. but,
97 | wait 🤔... does it make any difference in our use case? Why? Discuss
98 | with your peers.
99 |
100 |
Tasks part 3:
101 |
102 | 3. In{" "}
103 |
104 | src/components/patterns/Context/exercise/GraphQLProvider.jsx
105 | {" "}
106 | we are using const memoizedHashGql = memoize(hashGql);.
107 | Should we use useMemo instead? Why?
108 |
59 | memoized value: {memoizedValue}
60 |
61 |
62 | If you look at the console, you should not see "[custom hooks
63 | exercise] computing expensive value" on every render
64 |
65 |
68 |
69 |
70 |
71 | );
72 |
73 | export default Example;
74 |
--------------------------------------------------------------------------------
/src/components/patterns/CustomHooks/exercise/index.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable jsx-a11y/accessible-emoji */
2 | import React from "react";
3 | // import useWidth, { LARGE, MEDIUM } from "./useWidth";
4 | // remove the following import after refactoring the Width component to a custom hook
5 | import Width from "./useWidth";
6 |
7 | const Bonus = () => {
8 | return (
9 |
10 |
11 |
🏋️♀️Exercise
12 |
13 | 🎯 The goal is to extract some component logic into a reusable function
14 | avoiding common pitfalls
15 |
16 |
17 |
18 | {/* Comment out the following after implementing part 1 */}
19 | The width is:
20 | {/* Use the width value from your custom hook in the next line after you implment part 1 */}
21 | {/* {width} */}
22 |
23 |
24 |
Part 1, refactoring
25 |
26 |
27 | 1. Refactor the{" "}
28 | src/components/patterns/CustomHooks/exercise/useWidth.js{" "}
29 | class to a function component using the useState and{" "}
30 | useEffect Hooks.
31 |
32 |
33 | Tip: to remove the event listeners we need to use the{" "}
34 |
39 | {" "}
40 | cleanup phase
41 | {" "}
42 | of the effect. To clean up the side effects you must return a function.
43 |
44 |
45 | You'll know it works because the "The width is: X" will change properly
46 | when you resize the screen.
47 |
57 | You'll know it works because the "The width is: X" will change properly
58 | when you resize the screen. 🤔wait... then (discuss with your peer in
59 | the group):
60 |
61 |
62 | 2.2. What's the difference between <Width
63 | /> and useWidth if I can also do{" "}
64 | import Width from "./useWidth" and do <Width />?
65 |
66 |
Part 3, pitfalls
67 |
68 | 3.1. We have dissabled the{" "}
69 |
74 | 'exhaustive-deps' lint rule
75 |
76 | , and you might have a memory leak in your useWidth. Your
77 | task is to identify it and fix it using useMemo or useCallback (you'll
78 | have to decide). Notice, you might be following good practices and
79 | already fixed the problem without realizing it. Double check with your
80 | peers in your group.
81 |
82 |
83 | ⚠️ Warning, you should use eslint to help you identify potential bugs.
84 | It's not enable in this exercise to help you understand the problem.
85 |
86 |
87 |
🤸♀️Bonus exercise, legacy
88 |
89 | Replace the HoC withWidth in
90 |
91 | src/components/patterns/CompoundComponents/exercise/Menu.jsx
92 | {" "}
93 | with your custom hook useWidth.
94 |
95 |
96 |
97 | We want to replace the HoC{" "}
98 | withWidth in
99 | src/components/App.jsx with your custom hook but we don't
100 | have much time and confidence (ohh we don't have tests!). Create a HoC
101 | that uses the useWidth hook and injects the width value via props to the
102 | App component.
103 |
24 | The following component is a higher-order component called
25 | withMousePosition, it prints the position of the mouse when you move the
26 | mouse over the component using props.
27 |
A typical use case of a HoC is to fetch data into a component.
49 |
50 | Task: Can you compose the List component displayed below
51 | using{" "}
52 |
53 | src/components/patterns/HigherOrderComponents/exercise/withData
54 |
55 | ?. Hint: the composition happens in List component not in
56 | Question1/index.js. For the task you'll have to edit 2 files:
57 |
72 | Why do you think we need to use a HoC to fetch the data? why not just
73 | having a "helper" function that fetches data and we call it from List
74 | component to fetch data?
75 |
76 |
77 | In this particular case there are two reasons we need to encapsulate
78 | that logic in a component
79 |
80 |
81 |
We are tracking some state
82 |
83 | We are triggering the logic in a life cycle method (componentDidMount)
84 |
99 | A good implementation of a HoC lets the developer that uses the HoC
100 | configure it easily without having to reimplement anything.
101 |
102 |
103 | The{" "}
104 |
105 | src/components/patterns/HigherOrderComponents/exercise/withWidth
106 | {" "}
107 | implementation we use has the largeWidth and mediumWidth hardcoded. It
108 | makes it more difficult to reuse this HoC in different projects where we
109 | might consider different screen sizes.
110 |
111 |
112 | Task: Refactor the higher-order component withWidth so it
113 | accepts the sizes as a parameter. You can implement it in different ways:
114 |
115 |
116 |
117 |
118 | The first way, and more naive, is by adding an extra parameter/s to
119 | the HoC functions. Example:
120 |
143 | Tip. What you are trying to implement is similar to the{" "}
144 | connect function from react-redux.
145 |
146 |
147 |
Heads-up
148 |
149 | There is more than one place where the HoC withWidth is being used.
150 | You'll need to update any call to withWidth using the new implemention
151 |
20 | React.useReducer is usually preferable to useState when you have complex
21 | state logic that involves multiple sub-values or when the next state
22 | depends on the previous one.{" "}
23 | (We'll practice this in this exercise)
24 |
25 |
26 | React.useReducer also lets you optimize performance for components that
27 | trigger deep updates because you can pass dispatch down instead of
28 | callbacks.{" "}
29 | (We'll practice this in the next exercise about Context)
30 |
41 | The code of the example is very similar to the code of the exercise. The
42 | example contains explanations 👩🏫, the exercise contains tasks 🚧 and hints
43 | 🕵️♀️. If there are things that you don't understand about the code it's
44 | better to look at the example. If there are things that are not clear
45 | about what needs to done in the exercise after checking the tasks, let me
46 | know.
47 |
48 |
49 |
Exercise part 1
50 |
51 |
52 | 🎯 The goal is to understand how to handle complex state logic in our
53 | components that involves multiple sub-values
54 |
55 |
56 |
57 | Refactor the LoginForm component in{" "}
58 | src/components/patterns/HookReducer/exercise/index.jsx so it
59 | implements the following:
60 |
61 |
62 | 1. Handles the SET_ERRORS action in the reducer.
63 | It should add an errors object to the the state using the action.payload
64 |
65 |
66 | 2. dispatch a SET_ERRORS action
67 | with the errors (output from the
68 | validate(state.values) invocation) as payload.
69 |
70 |
71 | 3.dispatch the SET_ERRORS action
72 | only when the state of the input fields change. Hint, you need to use the
73 | useEffect second argument.
74 |
75 |
76 |
77 |
78 |
Exercise part 2
79 |
80 | Create a custom hook from your Login Form. You
81 | can call it useForm.
82 |
83 |
🕵️♀️ Hints :
84 |
85 |
Extract the useReducer outside the Login Form
86 |
87 | Don't think of state only, but also functions that create "props".
88 |
89 |
90 |
91 |
Exercise part 3
92 |
93 | By default we are displaying the error message to the user even if the
94 | user did not use the form. That's not a great user experience. To improve
95 | that we are going to add two more states in our form to identify which
96 | fields are `dirty` and if the form is `submitted`.
97 |
98 |
99 | A field is dirty when the value of the field is not equal to the initial
100 | value, false if the values are equal.
101 |
102 |
103 |
A field is submitted if the form is submitted :D
104 |
105 |
Bonus exercise part 1
106 |
107 | We are going to add some state to our form to know when the form is being
108 | submitted.
109 |
110 |
111 | If the form is being submitted then we'll display the text "submitting"
112 | instead of "submit" in the submit button.
113 |
114 |
115 |
Bonus exercise part 2
116 |
117 | Use the{" "}
118 |
122 | React Profiler
123 | {" "}
124 | in the React Dev Tools to record what happens when you type in the user
125 | id. Is the password being rendered as well? Why?
126 |
127 |
128 | To avoid unnecessary renders you can create another component called
129 | "Field" and use{" "}
130 |
134 | React.memo
135 |
136 | .
137 |
138 |
139 | );
140 |
141 | export default Page;
142 |
--------------------------------------------------------------------------------
/src/components/patterns/HookReducer/example/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const SET_FIELD_VALUE = "SET_FIELD_VALUE";
4 |
5 | // 👩🏫The state of a form is a relatively complex state logic.
6 | // Using a reducer also helps separate reads, from writes.
7 | function reducer(state, action) {
8 | switch (action.type) {
9 | // 👩🏫reducers should respond to a given action
10 | 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 :)
13 | return {
14 | ...state,
15 | values: {
16 | ...state.values,
17 | ...action.payload,
18 | },
19 | };
20 | 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 :)
23 | return state;
24 | }
25 | }
26 |
27 | const LoginForm = (props) => {
28 | const initialState = {
29 | values: props.initialValues,
30 | };
31 |
32 | // 👩🏫 useReducer accepts a reducer of type (state, action) => newState,
33 | // and returns the current state paired with a dispatch method
34 | const [state, dispatch] = React.useReducer(reducer, initialState);
35 |
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) => {
39 | event.preventDefault();
40 | dispatch({
41 | type: SET_FIELD_VALUE,
42 | payload: { [fieldName]: event.target.value },
43 | });
44 | };
45 |
46 | const getFieldProps = (fieldName) => ({
47 | value: state.values[fieldName],
48 | onChange: handleChange(fieldName), // 👩🏫 fieldName gets "captured" in the handleChange closure
49 | });
50 |
51 | const handleSubmit = (e) => {
52 | e.preventDefault();
53 | alert(JSON.stringify(state.values));
54 | };
55 |
56 | return (
57 |
72 | );
73 | };
74 |
75 | const Example = () => (
76 |
82 | );
83 |
84 | export default Example;
85 |
--------------------------------------------------------------------------------
/src/components/patterns/HookReducer/exercise/index.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-unused-vars */
2 | import React from "react";
3 |
4 | function reducer(state, action) {
5 | switch (action.type) {
6 | // 🚧 Add a SET_ERRORS case that adds an errors key to the state with the action.payload
7 | // 🕵️♀️ You probably want to clear previous errors every time you do SET_ERRORS
8 | case "SET_FIELD_VALUE":
9 | return {
10 | ...state,
11 | values: {
12 | ...state.values,
13 | ...action.payload,
14 | },
15 | };
16 | default:
17 | return state;
18 | }
19 | }
20 |
21 | function LoginForm(props) {
22 | const { initialValues, onSubmit } = props;
23 | // 👮♀you don't have to edit this validate function
24 | const validate = (values) => {
25 | let errors = {};
26 | if (!values.password) {
27 | errors.password = "Password is required";
28 | }
29 | if (!values.userId) {
30 | errors.userId = "User Id is required";
31 | }
32 | return errors;
33 | };
34 |
35 | const [state, dispatch] = React.useReducer(reducer, {
36 | values: initialValues,
37 | errors: {},
38 | });
39 |
40 | React.useEffect(() => {
41 | if (validate) {
42 | const errors = validate(state.values);
43 | // 🚧 dispatch a SET_ERRORS action with the errors as payload
44 | }
45 | }, []); // 🚧 dispatch the SET_ERRORS action only when the state of the input fields change.
46 |
47 | const handleChange = (fieldName) => (event) => {
48 | event.preventDefault();
49 | dispatch({
50 | type: "SET_FIELD_VALUE",
51 | payload: { [fieldName]: event.target.value },
52 | });
53 | };
54 |
55 | const handleSubmit = (event) => {
56 | event.preventDefault();
57 | const errors = validate(state.values);
58 | if (!Object.keys(errors).length) {
59 | onSubmit(state.values);
60 | }
61 | };
62 |
63 | const getFieldProps = (fieldName) => ({
64 | value: state.values[fieldName],
65 | onChange: handleChange(fieldName),
66 | });
67 |
68 | const { errors } = state;
69 |
70 | return (
71 |
90 | );
91 | }
92 |
93 | const Exercise = () => (
94 |
95 |
19 | In the example the state of an input is managed by an Input (notice the
20 | capital I) component. We can say that the input is a controlled
21 | component, and it's controlled by the Input. The Input owns the state of
22 | the input.
23 |
11 | The advantage of separating the previous code into an Input to handle
12 | the state and an input (notice it's not capital I) to handle the
13 | appearance is that the Input functionality can also be applied to other
14 | form controls like radio buttons, or select fields, etc. In the end, al
15 | the form fields have some state that changes. Threfore, it probably
16 | makes more sense to call it Field instead of Input.
17 |
18 |
Task
19 |
20 | You need to edit this file{" "}
21 | patterns/RenderProps/exercise/index.jsx so:
22 |
23 |
24 |
25 | {" "}
26 | The <select> (there is only one select in that
27 | file) should be composed with a <Field>{" "}
28 | component (the Field.jsx component is already imported
29 | ).
30 |
31 |
32 | The Field component will provide{" "}
33 | 1) the value and 2) the onChange function to the
34 | <select> using a function as a children (AKA Render Props
35 | pattern)
36 |
37 |
38 | You will know it works because the value of the <select> will be
39 | displayed on the console every time the <select> changes.
40 |
41 |
42 | You don't have to edit this file{" "}
43 |
44 | patterns/RenderProps/exercise/Field.jsx
45 |
46 |
47 |
48 |
49 | Hint: it's the same we do when using the Input component in the previous
50 | example. Check this file patterns/RenderProps/example/index.jsx
51 |
71 | In this case we want to implement a Measure component that can compose
72 | other components (they'll be the Measure's children) and provide them
73 | with its width (width of Measure's children).
74 |
75 |
Tasks
76 |
77 |
78 | Edit this file patterns/RenderProps/exercise/Measure.jsx so
79 | the render method invokes the this.props.children and passes the width
80 | as an argument
81 |
82 |
83 |
84 | Edit this file patterns/RenderProps/exercise/index.jsx so the
85 | <figure> (there is only one <figure> on that file) is
86 | composed with the <Measure>
87 |
88 |
89 | Inside the <figcaption> render the width provided by the Measure
90 | component.
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 | My width is {"REPLACE THIS WITH THE ACTUAL width"} px
99 |
100 |
101 |
12 | State reducer allows consumers to control how the state is managed. This
13 | means the consumer has control over some logic in the parent. This is very
14 | useful in combination with the Render Props. This is called inversion of
15 | control. Heads up, the State Reducer pattern exposes implementation
16 | details by exposing component state logic. Think twice before applying
17 | this pattern.
18 |
31 |
32 | );
33 |
34 | export default Example;
35 |
--------------------------------------------------------------------------------
/src/components/patterns/StateReducer/exercise_1/Field.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export class Field extends React.Component {
4 | initialState = {
5 | value: "😄"
6 | };
7 | state = this.initialState;
8 | static defaultProps = {
9 | stateReducer: (state, { type, ...change }) => change
10 | };
11 |
12 | onChange = e => {
13 | const { stateReducer } = this.props;
14 | const { value } = e.target;
15 |
16 | this.setState(state => stateReducer(state, { ...state, value }));
17 | };
18 |
19 | onReset = e => {
20 | // TODO finish the implementation of this method so when it's
21 | // executed it invokes the state reducer with the initial state
22 | };
23 |
24 | render() {
25 | const { value } = this.state;
26 | const { onChange, onReset } = this;
27 |
28 | return this.props.children({ value, onChange, onReset });
29 | }
30 | }
31 |
32 | export default Field;
33 |
--------------------------------------------------------------------------------
/src/components/patterns/StateReducer/exercise_1/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Field from "./Field";
3 |
4 | const Example = () => (
5 |
6 |
7 | Implement the onReset method on
8 | `src/components/patterns/StateReducer/exercise_1/Field.jsx`, so when the
9 | input executes onReset the state reducer is invoked with the initial state
10 |
7 | We are adding a type key to the change object. By doing so we get more
8 | flexibility on how to handle the changes in the state reducer based on the
9 | action that is executed.
10 |
11 |
12 | Extend `src/components/patterns/StateReducer/exercise_2/Field.jsx` so the
13 | intitial value in the state is a emoji smiley face but when the user
14 | resets the state it's an empty string. To achieve this you'll need to edit
15 | two different files:
16 |
17 |
18 |
19 | In `src/components/patterns/StateReducer/exercise_2/Field.jsx` you will
20 | have to refactor the onReset method and add an{" "}
21 | ON_FIELD_RESET type to the change.
22 |
23 |
24 | In `src/components/patterns/StateReducer/exercise_2/index.js` you will
25 | have to add a stateReducer prop to the Field component. Hint, use a
26 | ternary operator based on the type ON_FIELD_RESET.
27 |
35 | Extend the Dropdown so it doesn't close when the user clicks outside the
36 | dropdown. To achieve this you'll need to edit only one file:
37 |
38 |
39 |
40 | In `src/components/patterns/StateReducer/exercise_3/index.js` you will
41 | have to add a stateReducer prop to the Dropdown component. Hint, use a
42 | ternary operator based on the type CLICKED_OUTSIDE_ACTION.
43 |
36 | Extend the Dropdown so it can close when the user presses on the ESC key.
37 | Then implement two Dropdowns on this page, one that closes on ESC pressed,
38 | and another Dropdown that doesn't close on ESC pressed. To achieve this
39 | you'll need to edit two files.
40 |
41 |
42 |
43 | In `src/components/patterns/StateReducer/exercise_bonus/index.js` you
44 | will have to add a stateReducer prop to one of the Dropdown components.
45 |
46 |
47 | In `src/components/patterns/StateReducer/exercise_bonus/Dropdown.js` you
48 | will have to add some code to handle the key pressed event.
49 |
9 | styled-components has full theming support by exporting a{" "}
10 | ThemeProvider wrapper component. This component provides a
11 | theme to all React components underneath itself via the context API. In
12 | the render tree all styled-components will have access to the provided
13 | theme, even when they are multiple levels deep.
14 |
15 |
16 | Using a theme help us to share values and styles through out all styled
17 | components. you can see the theme we are using in
18 | src/components/patterns/Theming/example/theme.js. On every
19 | styled component you will have access to a theme prop
20 | attached to component props.
21 |
40 | The goal of this exercise is to create variants for some alert components.
41 | In this simple example, we can focus on changing the{" "}
42 | background-color & color of each variant.
43 |
44 |
The variants should be:
45 |
46 |
default
47 |
success
48 |
warning
49 |
error
50 |
51 |
52 |
53 |
Bonus
54 |
55 | now let's try to embrace more tools from styled-system. the
56 | new things we are going to cover as a demo are:
57 |
58 |
59 |
60 | styled-system utilities like space
61 |
62 |
63 | the use of the prop as from styled-components
64 |