Skip to content

Commit 3fdeebb

Browse files
committed
add custom hooks files
1 parent 1460471 commit 3fdeebb

File tree

8 files changed

+359
-0
lines changed

8 files changed

+359
-0
lines changed

src/components/RGALogo.jsx

Lines changed: 26 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
let me = {
2+
name: "Alex Lobera",
3+
loves: [
4+
"JavaScript",
5+
"React",
6+
"GraphQL",
7+
"Testing",
8+
"Serverless",
9+
"UX",
10+
"startups",
11+
"salsa",
12+
"bachata",
13+
"my girlfriend",
14+
//Not in this particular order!
15+
],
16+
company: "https://reactgraphql.academy",
17+
title: "Developer, coach, and decision-maker",
18+
twitter: "@alex_lobera",
19+
};
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import React from "react";
2+
import Example from "./example";
3+
import Exercise from "./exercise";
4+
5+
const Page = () => (
6+
<div>
7+
<h2>Custom Hooks</h2>
8+
<Example />
9+
<Exercise />
10+
</div>
11+
);
12+
13+
export default Page;
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import React, { useMemo, useCallback, useEffect } from "react";
2+
import { useIncrement } from "./index";
3+
4+
const computeExpensiveValue = (a, b) => {
5+
console.log("[custom hooks exercise] computing expensive value");
6+
7+
return a * b;
8+
};
9+
10+
const UseCallbackOrMemo = ({ a = 1, b = 2 }) => {
11+
const { count, increment } = useIncrement();
12+
// useMemo returns a value
13+
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
14+
// useCallback returns a function
15+
const memoizedCallback = useCallback(increment, []);
16+
17+
useEffect(() => {
18+
memoizedCallback();
19+
// 👩‍🏫 increment instead of memoizedCallback and you'll get an infinite loop
20+
// increment();
21+
}, [memoizedCallback]);
22+
23+
/*
24+
👩‍🏫 Do:
25+
- Go to http://localhost:3000/custom-hooks
26+
- Open the console on the dev tools
27+
- Click on the "Increment 🍄" button
28+
- You should only see one "[custom hooks exercise] computing expensive value"
29+
*/
30+
31+
return (
32+
<React.Fragment>
33+
<h3>🥑 Before the exercise 🏋️‍♀️</h3>
34+
<h4>useMemo</h4>
35+
<blockquote>
36+
Returns a memoized <strong>value</strong>.
37+
<a
38+
href="https://reactjs.org/docs/hooks-reference.html#usememo"
39+
target="_blank"
40+
>
41+
{` `} The docs
42+
</a>
43+
</blockquote>
44+
<h4>useCallback</h4>
45+
<blockquote>
46+
Returns a memoized <strong>callback</strong> (function).
47+
<a
48+
href="https://reactjs.org/docs/hooks-reference.html#usecallback"
49+
target="_blank"
50+
>
51+
{` `} The docs
52+
</a>
53+
</blockquote>
54+
<p>Last render on {new Date().toString()}</p>
55+
<p>
56+
memoized value: {memoizedValue}
57+
<br />
58+
<em>
59+
If you look at the console, you should not see "[custom hooks
60+
exercise] computing expensive value" on every render
61+
</em>
62+
</p>
63+
<p>
64+
<button onClick={() => increment(memoizedValue)}>Increment 🍄</button>
65+
current count: {count}
66+
</p>
67+
</React.Fragment>
68+
);
69+
};
70+
71+
export default UseCallbackOrMemo;
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import React, { useState } from "react";
2+
import UseCallbackOrMemo from "./UseCallbackOrMemo";
3+
4+
// const calculateValue = (a, b) => a + b;
5+
6+
// export const useIncrement = (initialValue, calculateValue) => {
7+
export const useIncrement = (initialValue) => {
8+
const [count, setCount] = useState(initialValue || 0);
9+
// 👩‍🏫 the value of the increment could be resolved by a function that is passed to the useIncrement (e.g. inversion of control)
10+
// const increment = (value = 1) => setCount(calculateValue(count, value));
11+
const increment = (value = 1) => setCount(count + value);
12+
13+
return { count, increment };
14+
};
15+
16+
// Using a custom hook
17+
// const ExampleComponent = (props) => {
18+
// const { count, increment } = useIncrement(props.initialValue);
19+
20+
// return (
21+
// <p>
22+
// <button onClick={() => increment()}>Increment</button>
23+
// current count: {count}
24+
// </p>
25+
// );
26+
// };
27+
28+
const ExampleComponent = (props) => {
29+
const { count, increment } = useIncrement(props.initialValue);
30+
31+
return (
32+
<p>
33+
<button onClick={() => increment()}>Increment</button>
34+
Current count: {count}
35+
</p>
36+
);
37+
};
38+
39+
const Example = () => (
40+
<React.Fragment>
41+
<h3>👩‍🏫 Example</h3>
42+
<p>
43+
A React Hook is a function that lets us reuse component logic across
44+
different function components. Component logic is:
45+
</p>
46+
<ul>
47+
<li>State</li>
48+
<li>Lifecyle</li>
49+
<li>Context</li>
50+
</ul>
51+
<p>A custom Hook is a function that calls another Hook</p>
52+
53+
<p>
54+
Custom Hooks documentation{" "}
55+
<a href="https://reactjs.org/docs/hooks-custom.html" target="_blank">
56+
https://reactjs.org/docs/hooks-custom.html
57+
</a>
58+
</p>
59+
<ExampleComponent initialValue={5} />
60+
<hr />
61+
<UseCallbackOrMemo />
62+
</React.Fragment>
63+
);
64+
65+
export default Example;
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import React from "react";
2+
// import useWidth, { LARGE, MEDIUM } from "./useWidth";
3+
// remove the following import after refactoring the Width component to a custom hook
4+
import Width from "./useWidth";
5+
6+
const Bonus = () => {
7+
return (
8+
<React.Fragment>
9+
<hr />
10+
<h3>🏋️‍♀️Exercise</h3>
11+
<h4>
12+
🎯 The goal is to extract some component logic into a reusable function
13+
avoiding pitfalls
14+
</h4>
15+
16+
<h1>
17+
{/* Comment out the following <Width /> after implementing part 1 */}
18+
The width is: <Width />
19+
{/* Use the width value from your custom hook in the next line after you implment part 1 */}
20+
{/* {width} */}
21+
</h1>
22+
23+
<h4>Part 1, refactoring</h4>
24+
25+
<p>
26+
<input type="checkbox" /> 1. Refactor the{" "}
27+
<code>src/components/patterns/CustomHooks/exercise/useWidth.jsx</code>{" "}
28+
class to a function component using the <code>useState</code> and{" "}
29+
<code>useEffect</code> Hooks.
30+
</p>
31+
<p>
32+
Tip: to remove the event listeners we need to use the{" "}
33+
<a
34+
href="https://reactjs.org/docs/hooks-effect.html#effects-with-cleanup"
35+
target="_blank"
36+
>
37+
{" "}
38+
cleanup phase
39+
</a>{" "}
40+
of the effect. To clean up the side effects you must return a function.
41+
</p>
42+
<p>
43+
You'll know it works because the "The width is: X" will change properly
44+
when you resize the screen.
45+
</p>
46+
<h4>Part 2, reusing</h4>
47+
<p>
48+
<input type="checkbox" /> 2.1. Import your
49+
src/components/patterns/CustomHooks/exercise/useWidth.js custom hook in{" "}
50+
<code>src/components/patterns/CustomHooks/exercise/index.jsx</code> and
51+
replace the &lt;Width /&gt; component using:
52+
<code>const width = useWidth()</code>
53+
</p>
54+
<p>
55+
You'll know it works because the "The width is: X" will change properly
56+
when you resize the screen. 🤔wait... then (discuss with your peer in
57+
the group):
58+
</p>
59+
<p>
60+
<input type="checkbox" /> 2.2. What's the difference between &lt;Width
61+
/&gt; and useWidth if I can also do{" "}
62+
<code>import Width from "./useWidth"</code> and do &lt;Width /&gt;?
63+
</p>
64+
<h4>Part 3, pitfalls</h4>
65+
<p>
66+
<input type="checkbox" /> 3.1. We have dissabled the{" "}
67+
<a
68+
href="https://github.com/facebook/react/issues/14920"
69+
target="_blank"
70+
>
71+
'exhaustive-deps' lint rule
72+
</a>
73+
, and you might have a memory leak in your <code>useWidth</code>. Your
74+
task is to identify it and fix it using useMemo or useCallback (you'll
75+
have to decide). Notice, you might be following good practices and
76+
already fixed the problem without realizing it. Double check with your
77+
peers in your group.
78+
</p>
79+
<p>
80+
⚠️ Warning, you should use eslint to help you identify potential bugs.
81+
It's not enable in this exercise to help you understand the problem.
82+
</p>
83+
84+
<h4>🤸‍♀️Bonus exercise, legacy</h4>
85+
<p>
86+
<input type="checkbox" /> Bonus 1. Replace the HoC{" "}
87+
<code>withWidth</code> in
88+
<code>
89+
src/components/patterns/CompoundComponents/exercise/Menu.jsx
90+
</code>{" "}
91+
with your custom hook <code>useWidth</code>.
92+
</p>
93+
94+
<p>
95+
<input type="checkbox" /> Bonus 2. We want to replace the HoC{" "}
96+
<code>withWidth</code> in
97+
<code>src/components/App.jsx</code> with your custom hook but we don't
98+
have much time and confidence (ohh no tests!). Create a HoC that uses
99+
the useWidth hook and injects the width value via props to the App
100+
component.
101+
</p>
102+
</React.Fragment>
103+
);
104+
};
105+
106+
export default Bonus;
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import React from "react";
2+
3+
export const SMALL = 1;
4+
export const MEDIUM = 2;
5+
export const LARGE = 3;
6+
7+
const largeWidth = 992,
8+
mediumWidth = 768;
9+
10+
class WithWidth extends React.Component {
11+
constructor() {
12+
super();
13+
this.state = { width: this.windowWidth() };
14+
}
15+
16+
componentDidMount() {
17+
if (typeof window !== "undefined") {
18+
window.addEventListener("resize", this.handleResize);
19+
this.handleResize();
20+
}
21+
}
22+
23+
componentWillUnmount() {
24+
if (typeof window !== "undefined")
25+
window.removeEventListener("resize", this.handleResize);
26+
}
27+
28+
handleResize = () => {
29+
let width = this.windowWidth();
30+
if (width !== this.state.width) this.setState({ width });
31+
};
32+
33+
windowWidth() {
34+
let innerWidth = 0;
35+
let width;
36+
if (window) innerWidth = window.innerWidth;
37+
38+
if (innerWidth >= largeWidth) {
39+
width = LARGE;
40+
} else if (innerWidth >= mediumWidth) {
41+
width = MEDIUM;
42+
} else {
43+
// innerWidth < 768
44+
width = SMALL;
45+
}
46+
47+
return width;
48+
}
49+
50+
render() {
51+
return this.state.width;
52+
}
53+
}
54+
55+
export default WithWidth;
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import withWidth from "./withWidth";
2+
export { LARGE, MEDIUM, SMALL } from "./withWidth";
3+
4+
export default withWidth({ largeWidth: 1000 });

0 commit comments

Comments
 (0)