├── .gitattributes ├── .gitignore ├── 1. React Fundamentals ├── 1. Basic JS Hello World.md ├── 2. Intro to raw React APIs.md ├── 3. Use JSX.md ├── 4. Creating custom components.md ├── 5. Styling.md ├── 6. Forms.md └── 7. Rendering Arrays.md ├── 2. React Hooks ├── 1. useState greeting.md ├── 2. useEffect 'persistent state'.md ├── 3. Lifting state.md ├── 4. useState tic tac toe.md ├── 5. useRef and useEffect DOM interaction.md └── 6. useEffect HTTP requests.md ├── 3. Advanced React Hooks ├── 1. useReducer simple Counter.md ├── 2. useCallback custom hooks.md ├── 3. useContext simple Counter.md ├── 4. useLayoutEffect auto-scrolling textarea.md ├── 5. useImperativeHandle scroll to top-bottom.md └── 6. useDebugValue useMedia.md ├── 4. Advanced React Patterns ├── 1. Context Module Functions.md ├── 2. Compound Components.md ├── 3. Flexible Compound Components.md ├── 4. Prop Collections and Getters.md ├── 5. State Reducer.md └── 6. Control Props.md ├── 8. Build an Epic React App ├── 1. Render a React App.md ├── 2. Style React Components.md ├── 3. Make HTTP Requests.md ├── 4. Authentication.md └── 5. Routing.md ├── Attachments ├── DOM-not-supported-props.png ├── Schermata 2020-12-26 alle 15.40.52 PM.png ├── Schermata 2020-12-26 alle 16.00.08 PM.png ├── hook-flow.png ├── improved-debug-information.png ├── standard-debug-information.png └── where-to-put-react-state.png ├── JavaScript ├── Object.entries().md ├── Recursion.md ├── Regular Expression.md ├── Utility │ ├── allValues.md │ ├── isObject.md │ ├── isObjectEmpty.md │ └── isSquare.md ├── classNames.md ├── interpolate.md └── memoize.md ├── LICENSE ├── README.md └── React ├── React Element.md ├── React.Fragment.md ├── React.createElement().md ├── React.useEffect().md ├── React.useReducer().md ├── React.useRef()].md ├── React.useState().md ├── ReactDOM.render().md ├── custom Hook.md └── key prop.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/vscode,osx 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=vscode,osx 4 | 5 | ### OSX ### 6 | # General 7 | .DS_Store 8 | .AppleDouble 9 | .LSOverride 10 | 11 | # Icon must end with two \r 12 | Icon 13 | 14 | # Thumbnails 15 | ._* 16 | 17 | # Files that might appear in the root of a volume 18 | .DocumentRevisions-V100 19 | .fseventsd 20 | .Spotlight-V100 21 | .TemporaryItems 22 | .Trashes 23 | .VolumeIcon.icns 24 | .com.apple.timemachine.donotpresent 25 | 26 | # Directories potentially created on remote AFP share 27 | .AppleDB 28 | .AppleDesktop 29 | Network Trash Folder 30 | Temporary Items 31 | .apdisk 32 | 33 | ### vscode ### 34 | .vscode/* 35 | !.vscode/settings.json 36 | !.vscode/tasks.json 37 | !.vscode/launch.json 38 | !.vscode/extensions.json 39 | *.code-workspace 40 | 41 | ### Obsidian ### 42 | obsidian.css 43 | .obsidian 44 | .obsidian/workspace 45 | -------------------------------------------------------------------------------- /1. React Fundamentals/1. Basic JS Hello World.md: -------------------------------------------------------------------------------- 1 | In JavaScript you can create an element with `createElement()` function: 2 | ```js 3 | const div = document.createElement('div'); 4 | div.textContent = 'Hello World'; 5 | div.className = 'container' 6 | ``` 7 | And you can append it to any DOM node like in following example: 8 | ```js 9 | document.getElementById('root').append(newElement) 10 | ``` 11 | 12 | If you need to append to the `` of the page you can use `document.body.append()`. 13 | 14 | *Note to self:* the `body` and all other HTML elements are in the **document** and not window 😉 -------------------------------------------------------------------------------- /1. React Fundamentals/2. Intro to raw React APIs.md: -------------------------------------------------------------------------------- 1 | React works similarly to custom DOM as in [[1. Basic JS Hello World]], it has it's function to create an element [[React.createElement()]] that simulates the `document.createElement()` and the ReactDOM has [[ReactDOM.render()]] that is similar to `element.append()` and let us add all our application and render it to the DOM (*doh*) of the page to the second param passed. -------------------------------------------------------------------------------- /1. React Fundamentals/3. Use JSX.md: -------------------------------------------------------------------------------- 1 | JSX is an HTML-like syntax that help us write easier [[React.createElement()]] and needs to be transpiled in order to be properly used. 2 | 3 | In order to transpile this syntax generally we use Babel that will turn the JSX code into standard JavaScript/React code, a simple example of this is the following: 4 | ```js 5 | // JSX 6 | const ui =

Hey there

7 | 8 | // ↓ ↓ ↓ ↓ compiles to ↓ ↓ ↓ ↓ 9 | 10 | // JavaScript/React 11 | const ui = React.createElement('h1', {id: 'greeting', children: 'Hey there'}) 12 | ``` 13 | 14 | When you use JSX you've access to the *'best of both worlds'*, you can write your component as standard HTML but you can easily add some JS into the mix. This make easy to create complex component passing variables or entire functions to them. 15 | 16 | The basic sintax to tell the component that you're passing some JavaScript is by using `{}` like in the following example: 17 | ```js 18 | // Interpolate example 19 | const className = 'container' 20 | const children = 'Hello World' 21 | const element =
{children}
22 | 23 | // Resulting HTML 24 |
Hello World
25 | ``` 26 | The transpiler will [[interpolate]] the content inside the curly braces, like the [[template literal|s]] does, and will use the value inside the variables to generate the HTML. 27 | 28 | Since we're using JavaScript here we are not limited to passing single variables but we can also use the [[spread operator]] to fill the component with all values: 29 | ```js 30 | const children = 'Hello World' 31 | const className = 'container' 32 | const props = {children, className} 33 | const element =
34 | ``` -------------------------------------------------------------------------------- /1. React Fundamentals/4. Creating custom components.md: -------------------------------------------------------------------------------- 1 | Create a custom component mean create a function that returns a [[React Element]]. The first iteration on creating a custom component is via using the [[React.createElement()]] function and it could be as simple as: 2 | ```js 3 | const customComponent = React.createElement( message, {children: 'Hello World'}) 4 | 5 | // Then you can insert it via JSX in the element that gets rendered 6 | const element = ( 7 |
8 | {customComponent} 9 |
10 | ) 11 | ``` 12 | 13 | Better yet, a custom component is generally a function with that **must** have a capital letter for its name. A simple example of it is: 14 | ```js 15 | const Message = ({children}) =>
{children}
; 16 | ``` 17 | Now you can use it as a proper component inside your JSX: 18 | ```js 19 | const element = ( 20 |
21 | Hello World 22 | Goodbye World 23 |
24 | ) 25 | ``` 26 | In this way the `children` get passed but we can do much more, for example we can pass multiple props in the component declaration: 27 | ```js 28 | const Message = ({subject, greeting}) =>
{greeting}, {subject}
; 29 | ``` 30 | Now we can create the custom component and pass the different props like so: 31 | ```js 32 | 33 | ``` 34 | Since we can create custom components that get used by other developers it'll be a good idea to specify the kind of props that we want to use within the component. This means that if the component does not get the specific kind of props in the console we can show a proper messege that will help the developer that uses our component. 35 | 36 | The first approach is to use the special property `propTypes` of the component and pass into it a function that will validate the props like so: 37 | ```js 38 | Message.propTypes = { 39 | subject(props, propName){ 40 | const type = typeof props[propName]; 41 | if(type !== 'string' ){ 42 | console.log('type', type); 43 | return new Error(`${propName} must be a string`) 44 | } 45 | }, 46 | greeting(props, propName){ 47 | const type = typeof props[propName]; 48 | if(type !== 'string' ){ 49 | console.log('type', type); 50 | return new Error(`${propName} must be a string`) 51 | } 52 | }, 53 | } 54 | ``` 55 | React will run all the code against our component but we can already see that there is a lot of duplicated code doing so and even if we can simplify the situation creating a `PropType` object that will have specific methods for validate the props: 56 | ```js 57 | const PropTypes = { 58 | string(props, propName, componentName) { 59 | if (typeof props[propName] !== 'string') { 60 | return new Error( 61 | `Hey, the component ${componentName} needs the prop ${propName} to be a string, but a ${typeof props[ 62 | propName 63 | ]} was passed`, 64 | ) 65 | } 66 | }, 67 | } 68 | 69 | Message.propTypes = { 70 | subject: PropTypes.string, 71 | greeting: PropTypes.string, 72 | } 73 | ``` 74 | The react team already shares a nice npm package that will solve many of our problems, the `prop-types` one, and will let us also declare if a specific prop is also required: 75 | ```js 76 | // Once the prop-types is installed and imported we can write something like 77 | Message.propTypes = { 78 | subject: PropTypes.string.isRequired, 79 | greeting: PropTypes.string.isRequired, 80 | } 81 | ``` 82 | ### Special mention `` 83 | React gives us a special component called *Fragment*, that we can call like [[React.Fragment]], that behaves as a container that we can use in JSX but that will not be rendered on the page. -------------------------------------------------------------------------------- /1. React Fundamentals/5. Styling.md: -------------------------------------------------------------------------------- 1 | In order to style a React component we can use [[inline styles]] or [[JavaScript/classNames]]. While using the inline styles is not the preferred way when you need to do so there is a little difference from common HTML. 2 | In HTML to define some stiles for an element you write something like this: 3 | ```html 4 |
5 | ``` 6 | Basically inside the HTML attribute `style` we write CSS rules like in a normal `.css` file. 7 | 8 | In JSX though we need to change a bit our syntax and pass an *CSSStyleDeclaration* object that defines the CSS props in a different way: 9 | ```jsx 10 |
11 | ``` 12 | Following the common JS best practices each property that in CSS uses an hyphen in this case is written in camelCase. 13 | 14 | *Also note that in JSX we can self-close any DOM element if it does not contain any children.* 15 | 16 | ## Appying styles with a custom component 17 | Sometimes is useful create a custom component to apply some default styles and rules in order to save ourself from repeat the same code, for example in the following example all the elements share the `box` class and the `font-style` CSS property: 18 | ```jsx 19 | const smallBox =
23 | small lightblue box 24 |
25 | const mediumBox =
29 | medium pink box 30 |
31 | const largeBox =
35 | large orange box 36 |
37 | ``` 38 | In this case create a custom `Box` component seems legit since will let us save a lot of repetition **but** in order to do so we need to properly use the [[spread operator]] because if not used properly we could run into troubles. 39 | 40 | For example, in the following snippet the [[spread operator]] operator]] is used in the wrong way. Can you see why? 41 | ```jsx 42 | function Box(props){ 43 | return
44 | } 45 | 46 | // Example Box in use 47 | 48 | small lightblue box 49 | 50 | ``` 51 | Basically when we use the component and set the `className` and `style` props (toghether with the `children`) with the [[spread operator]] that we uses while declaring the custom component we are **overriding** those props with the ones that we pass. The resulting HTML element will be: 52 | ```html 53 |
54 | small lightblue box 55 |
56 | ``` 57 | This **is not** the result we are aiming to. Let's improve it using the [[rest operator]]: 58 | ```jsx 59 | function Box({className = '', style, ...otherProps}){ 60 | return
65 | } 66 | ``` 67 | Now we [[destruct]]struct]] the props that our custom component passes in the implementation but also we **add** (not override) the `className` and `style` values without removing their default ones. 68 | 69 | We then use the [[rest operator]] `...otherProps` to collect the remaining props passed and use them inside the component with the [[spread operator]]. 70 | 71 | **Some things worth noticing:** 72 | * [[rest operator]] and [[spread operator]] are different - the first is used in the declaration phase of the function (can be used while we [[destruct]] an object) and **collects** all the props that we do not destruct and the second one help us expanding those props while invoke it 73 | * while deconstructoring the props the `className` gets a default value of *empty string* because if in the implementation we will not declare it, since we are using a [[template literal]], we will get `undefined` 74 | * `style` does not need a default value because we use a the [[spread operator]] to insert all the customization that our user can add in the implementation 75 | * **sometimes the order is important** others not - while using the [[template literal]] the order of the classes is not important because they both gets added (also it is the CSS that will apply those styles). Instead in `style` the order is really important because if the user passes a prop to replace the default style declared we need to let him override it, and this means put the [[spread operator]] at the end of our *CSSStyleDeclaration* object 76 | 77 | ## Apply styles with a custom prop 78 | Making a component accepting a prop to set the size of our component is pretty easy. In the implementation of the component all that we need to do is to add a `size` prop and we can pass to it the size that we are looking for. 79 | 80 | So the developer that uses our components, instead to specify the `className` and let them add the CSS class by hand all they need to do is spedify the size like so: 81 | ```js 82 | 83 | small lightblue box 84 | 85 | ``` 86 | But React does not do it by magic, we need to use the value passed in `size` in order to create the CSS class: 87 | ```js 88 | function Box({style, size, className = '', ...otherProps}) { 89 | const sizeClassName = size ? `box--${size}` : '' 90 | return ( 91 |
95 | ) 96 | } 97 | ``` 98 | In this way we still accept a `className` prop so users of our component are still able to add as many classes as they want but the important thing is that we are the one creating the CSS class for the size of our box in `sizeClassName` and we pass the string created as a class of our element. -------------------------------------------------------------------------------- /1. React Fundamentals/6. Forms.md: -------------------------------------------------------------------------------- 1 | React does not change much at how JS interact with the DOM. We can attach a submit handler specifying a `onSubmit` prop, this gets called when form gets submitted and will send an event with `target`. 2 | 3 | To get the `value` of an element exists many ways: 4 | * Via their index: `event.target.elements[0]` 5 | * Via the elements object by their `name` or `id` attribute: `event.target.elements.usernameInput.value` 6 | 7 | Once you get the value you can store it in a variable, pass to a function or do whathever you want. It's just a value 😉 8 | 9 | In React the default event system of the browser is substitute from the SyntheticEvent, generally we do not need to access to the 'original' event system of our browsers but in any case if we need to do so we can do it by accessing the `nativeEvent` from the event object you're using. 10 | ```js 11 | function CustomComponent(){ 12 | // Can be fired by any event handled by React, onSubmit or onChange for example 13 | function eventHandler( event ){ 14 | console.log(event); // returns an SyntheticEvent object 15 | console.log(event.nativeEvent); // returns a standard event 16 | } 17 | } 18 | ``` 19 | ## Using refs 20 | React allows us to access to a DOM element, in this example allows us to access the `value` of an input by using a Ref. Use a ref require us to import a new Hook from React itself, the Hook is [[useRef]]. 21 | ```js 22 | import {useRef} from React 23 | ``` 24 | Once we have this Hook in our component we can store it in a variable and assign it's `current` value declaring the special prop `ref` right inside our component. 25 | ```js 26 | function CustomComponent(){ 27 | const myRef = useRef(); 28 | 29 | function handleSubmit(){ 30 | // Access the ref value once assigned via the first render of the component 31 | const InputValue = myRef.current.value; // It is extremely important to use current 32 | } 33 | 34 | return ( 35 |
36 | 37 |
38 | ) 39 | 40 | } 41 | ``` 42 | Using a ref is not always adviced and we will discover why in next workshop. 43 | ## Validating input via `useState` 44 | When we change a ref we **do not cause a re-render** of our application, this means that we could fire some side-effects, for example checking an input, only if we force in some way a new render of our application (that obviously means that we should store somewhere the value we're working with). 45 | 46 | In React we can re-render a component if it's state is changed, in order to manage a state we use the [[React.useState()|useState]] Hook that we can use straight from React or via importing. 47 | ```js 48 | // Both are valid syntax 49 | 50 | // In this case we first import useState and then use in our component 51 | import {useState} from React; 52 | function CustomComponent(){ 53 | const [state, setState] = useState(0); 54 | } 55 | 56 | // Here we use straight the Hook without importing it first 57 | function CustomComponent(){ 58 | const [state, setState] = React.useState(0); 59 | } 60 | ``` 61 | [[React.useState()|useState]] is an Hook that returns an array, we destructure those values with the knowledge that the first value we get is the current state and the second one is a special function that allow us to update the value in the state and fire the re-render of our application. 62 | 63 | In this example having a state that holds the input value comes handy because allows us to check the value of the input at any moment and by re-rendering the component React allows us to show an error message id needed and also disable the submit button. 64 | 65 | ```js 66 | function UsernameForm({onSubmitUsername}) { 67 | const [error, setError] = React.useState(null) 68 | 69 | function handleSubmit(event) { 70 | event.preventDefault() 71 | onSubmitUsername(event.target.elements.usernameInput.value) 72 | } 73 | 74 | function handleChange(event) { 75 | const {value} = event.target 76 | const isLowerCase = value === value.toLowerCase() 77 | setError(isLowerCase ? null : 'Username must be lower case') 78 | } 79 | 80 | return ( 81 |
82 |
83 | 84 | 85 |
86 |
87 | {error} 88 |
89 | 92 |
93 | ) 94 | } 95 | ``` 96 | -------------------------------------------------------------------------------- /1. React Fundamentals/7. Rendering Arrays.md: -------------------------------------------------------------------------------- 1 | While rendering an array React needs a way to know which element of the array we are working on because if we do not give this reference it just guess for us. 2 | 3 | In order to give this kind of information we need to pass a [[key prop|key]] prop that will help React identify the element in an unique way. Most of the time the data we're working with already have a kind of unique identifier, the ID, so let's just fake an answer from an API that gets a list of fruits from a database and use the ID stored to uniquely identify each element. 4 | 5 | ```js 6 | const list = [ 7 | {id: 'a', value: 'apple'}, 8 | {id: 'o', value: 'orange'}, 9 | {id: 'g', value: 'grape'}, 10 | {id: 'p', value: 'pear'}, 11 | ] 12 | const listUI = list.map(listItem => ( 13 |
  • {listItem}
  • // Here I am using the ID of the item 14 | ) 15 | ); 16 | ``` 17 | -------------------------------------------------------------------------------- /2. React Hooks/1. useState greeting.md: -------------------------------------------------------------------------------- 1 | [[React.useState()]] is the first Hook that we encounter and accept a value that will be used to set the initial state of our custom component, this functions returns two values in an array: 2 | 1. the first value will be the value of the state at the current time we're calling it 3 | 2. the second value is a function that will let us update the state 4 | 5 | In this case React returns those two values in a form of an array and generally we [[destruct]] those to make simplier to access and use them later. 6 | ```js 7 | // Here I define a new state called state, to update it I'll use the setState fn 8 | // and it's initial value will be 0 9 | const [ state, setState ] = React.useState(0); 10 | ``` 11 | State can be defined as: data that changes over time. 12 | 13 | When we update a state we tell React to re-render the component itself and this will help us to show the new value in the UI of our app. Basically re-render means that we tells React that something has changed and we want it to generate a new JSX based on the new values and this means re-execute the function that defines the new component. -------------------------------------------------------------------------------- /2. React Hooks/2. useEffect 'persistent state'.md: -------------------------------------------------------------------------------- 1 | The [[React.useEffect()]] is a built-in Hook that allows us to run some code when the component gets [[render|rendered]] (and re-rendered), it also accept a [[callback]] that React will call after the DOM has been updated: 2 | ```js 3 | React.useEffect(() => { 4 | // this is where you can make HTTP requests or interact with browser APIs. 5 | }) 6 | ``` 7 | To better understand the flow of the when our functions get run in React have a look at: ![[hook-flow.png]] 8 | 9 | ### Extra Credit 2 - *effect dependencies* 10 | [[React.useEffect()]] will be called for any render, the first and all the following, and this could not be that good for complex applications where a component gets re-rendered because it's parent has been re-render (this is how it works out of the box). 11 | 12 | In order to avoid this we can specify a **second argument** to our [[React.useEffect()]], we can pass it an array that contains only the value that we want to monitor and run the callback function **when this value changes**. 13 | 14 | For the scope of the exercise, since we are monitoring an input field storing it's value in [[localStorage]], we want to run the callback for this `useEffect` only when the value of `name` get's updated. 15 | 16 | Remember that what we set into the dependency array it get's a [[shallow comparison]] so if use use object or arrays in here React will consider them different at each render because, even if values are the same, the reference to the object changes. 17 | 18 | ### Extra Credit 3 - *custom Hook* 19 | We can create a [[custom Hook]] to wrap within a function the shared logic between components. What makes a custom Hook a custom Hook is: 20 | * the fn name **must** start with `use`, this is just a best practice that if not followed will thrown an error 21 | * it **must** use other Hooks in it and this is what accounts for 'shared logic' 22 | 23 | Create a function is basic JavaScript stuff and something we do each single day, always remember that even if we're using React here a [[custom Hook]] is nothing less nothing more than a function. 24 | 25 | So the first thing we do to solve our problem is to create the function that will contain the shared logic: 26 | ```js 27 | function useLocalStorageState(){ 28 | // Shared logic 29 | } 30 | ``` 31 | Once we have the function we need to start thinking about the logic that we want to put in it. Our component will accept and use a value for `name` but use it as the name for the state declared in a custom component is not that generic, is it? 32 | 33 | So we need to generalize the kind of data that the user of our custom Hook want to use. 34 | ```js 35 | function useLocalStorageState(key, defaultValue = '') { 36 | const [state, setState] = React.useState( 37 | () => window.localStorage.getItem(key) || 38 | defaultValue, 39 | ) 40 | React.useEffect( () => { 41 | window.localStorage.setItem(key, state) 42 | }, [key, state] ) 43 | 44 | return [ state, setState ] 45 | } 46 | ``` 47 | As we can see this custom Hook `useLocalStorageState` gets two parameters: 48 | * a `key` that will identify the value stored in `localStorage` 49 | * a `defaultValue` that we initialize with an empty string and will identify the value that we need to store in `localStorage` for the specified string 50 | 51 | Note that this custom Hook is behaving similarly to the built-in [[React.useState()|useState]] Hook since is returning an array where the first value is the actual value of the state and the second one is the updater fn. 52 | 53 | Once this custom Hook is in place and imported within our component we can start to use it as follow: 54 | ```js 55 | const [name, setName] = useLocalStorageState('name', 'Andrea') 56 | ``` 57 | 58 | ### Extra credit 4: flexible localStorage hook 59 | At the moment we store in `localStorage` only a primitive value like a string, a number or a boolean and that's fine since those can get converted quite easily in a string (the only type of value we can store in `localStorage`). 60 | 61 | But if we want to allow our user to store arrays or objects we need to change the way we treat those data before to read or write them in `localStorage`, so we need to serialize the data when we save it into `localStorage` (we make it a string) and we need to deserialize it while reading. 62 | 63 | In JavaScript we normally use `JSON.stringify()` to serialize and `JSON.parse()` to deserialize. Now that we know that we can change our custom component to 64 | ```js 65 | function useLocalStorageState(key, defaultValue = '') { 66 | 67 | const [state, setState] = React.useState( 68 | () => { 69 | const valueInLocalStorage = window.localStorage.getItem(key); 70 | if (valueInLocalStorage){ 71 | return JSON.parse( valueInLocalStorage ) 72 | } 73 | return defaultValue 74 | }) 75 | 76 | React.useEffect( () => { 77 | window.localStorage.setItem(key, JSON.stringify(state)) 78 | }, [key, state] ) 79 | 80 | return [ state, setState ] 81 | } 82 | ``` 83 | **But** in doing so we're forcing the user of our custom Hook to rely on the JSON methods to de/serialize the data, we could do a better job if we give them the option to specify the kind of serialization that they would like to use. 84 | 85 | To do so we add a new parameter `option` (an object) to our custom Hook and we create the methods `serialize` and `deserialize` defaulting them to the JSON methods. 86 | ```js 87 | function useLocalStorageState( 88 | key, 89 | defaultValue = '', 90 | { serialize = JSON.stringify, deserialize = JSON.parse} = {} // Destructured options 91 | ) { 92 | ``` 93 | 94 | Moving on what if the `defaultValue` that our user is passing is something really expensive to get? 95 | 96 | To preserve our app from running the expensive calculation at each render we can do like `useState` does and let the user pass a function, all we need to change in our [[custom Hook]] is the `return` of it: 97 | ```js 98 | return typeof defaultValue === 'function' ? defaultValue() : defaultValue 99 | ``` 100 | The last missing piece of making this custom Hook future proof is to answer to the question: *what happen if the user changes the `key` of our value between re-renders?* 101 | 102 | We should delete the old value of the `key` in `localStorage` and save the new one. 103 | 104 | To do so we can reach for the [[React.useRef()|useRef]] build-in Hook that allow us to store a value between renders without firing a render if changed (like state does). 105 | 106 | The first thing to do is to create the ref and store in it the current `key`. 107 | ```js 108 | const prevKeyRef = React.useRef(key) 109 | ``` 110 | Once done that in our [[React.useEffect()|useEffect]] we check if the user is sending a new `key` and we will delete the old one: 111 | ```js 112 | React.useEffect( () => { 113 | // Getting the value from ref 114 | const prevKey = prevKeyRef.current 115 | // If different just delete it 116 | if (prevKey !== key) { 117 | window.localStorage.removeItem(prevKey) 118 | } 119 | // Set the ref to the new key 120 | prevKeyRef.current = key 121 | ... 122 | ``` -------------------------------------------------------------------------------- /2. React Hooks/3. Lifting state.md: -------------------------------------------------------------------------------- 1 | Lifting the state is a practice that is put to work when we need a specific value hold in a state to be shared between two or more components. In order to make it work we have to identify the closest shared parent component and lift there the state we want to share. 2 | 3 | This means that if we have a component, for example ``, that let us define a name for an animal that keeps it's value in local state as follow: 4 | ```js 5 | function Animal( {animal, onAnimalChange }) { 6 | const [ animal, setAnimalName ] = React.useState(''); 7 | return ( 8 |
    9 | 10 | setAnimalName(event.target.value)} 14 | /> 15 |
    16 | ) 17 | } 18 | ``` 19 | If we have an `App` that has also a `` component that's a sibling of `` and it also needs to access the animal name we can lift the state in the `` component and pass it to both as a prop. 20 | ```js 21 | function App() { 22 | const [animal, setAnimal] = React.useState('') 23 | return ( 24 | <> 25 | setAnimal(event.target.value)} /> 26 | 27 | 28 | ) 29 | } 30 | ``` 31 | Now we've lifted the state and we pass it down to `` and `` as a prop, in `` we pass as a prop also the fn in charge of update the state and we change the component as follow in order to work with the lifted state. 32 | ```js 33 | function FavoriteAnimal( {animal, onAnimalChange }) { 34 | return ( 35 |
    36 | 37 | 42 |
    43 | ) 44 | } 45 | ``` 46 | ### Colocating state 47 | Colocate a state is the practice to move back a `useState` Hook within the component that uses it if there is no need to share the lifted state. 48 | 49 | This means that we will remove the lifted state from the parent component and we will localize it inside the component that actually uses it. We do this because: 50 | * it will improve the maintainability of our component (the state and update mechanism are right inside it) 51 | * it will improve also the performances because when we will update the component where we colocate the state we do not need to render the parent component anymored -------------------------------------------------------------------------------- /2. React Hooks/4. useState tic tac toe.md: -------------------------------------------------------------------------------- 1 | We start the exercise by defining the difference between **managed state** and **derived state** 2 | 3 | * **Managed State**: State that you need to explicitly manage 4 | * **Derived State**: State that you can calculate based on other state 5 | 6 | In this exercise plenty of the hard calculations are done from the fn already in place so is more a refresher on [[React.useState()|useState]] than a deep dive because all you have to do is simply follow the instruction and create the managed state. 7 | 8 | All the derived state is done in the fns `calculateNextValue`, `calculateWinner` and `calculateStatus` and we call it **derived state** because we pass in them our state but we do not use any [[React.useState()|useState]] Hooks in them an neither the setter function for our state. 9 | 10 | The *Extra Credit 1/2* is something that we've already seen in [[1. useState greeting|the first workshop]] so I do not bother to describe them here. 11 | 12 | ### Extra Credit 3 13 | In this exercise we want to create an history of the game, that means that we want to record each action players have made during the game and add the ability to move between those steps. 14 | 15 | The first thing we need to do is [[3. Lifting state|lift the state]] so we can share it between the `Board` component and he parent `Game` one. In order to do so we replace the current definition of the `Game` component: 16 | ```js 17 | function Game() { 18 | return ( 19 |
    20 |
    21 | 22 |
    23 |
    24 | ) 25 | } 26 | ``` 27 | With the new one that uses the state: 28 | ```js 29 | function Game() { 30 | return ( 31 |
    32 |
    33 | 34 | 37 |
    38 |
    39 |
    {status}
    40 |
      {moves}
    41 |
    42 |
    43 | ) 44 | } 45 | ``` 46 | Now we render `Board` passing the click handler via `onClick` prop and we pass the `currentSquares` state via the `squares` prop. Also the `Game` is responsible to show the reset button and to show the lists of moves inside the `.game-info` element. 47 | 48 | We need to refactor the state because we do not keep track anymore of a single array (the board) but we need to keep track of all the boards for the game, so we need an array of arrays. 49 | 50 | We do so creating two new states: 51 | * **history** - this will keep track of all the boards and its index will let us time-travel between states 52 | * **currentStep** - defines the steps we're in 53 | 54 | ```js 55 | const [ history, setHistory ] = React.useState([Array(9).fill(null)]); 56 | const [ currentStep, setCurrentStep ] = React.useState(0); 57 | ``` 58 | We use both of them to find out the `currentSquare` that will be used to calculate `nextValue`, `winner` and the `status`: 59 | ```js 60 | const currentSquares = history[currentStep]; 61 | ``` 62 | 63 | The main work that we need to do is based on the `selectSquare` fn because now we need to insert the new array generated by the board as the last one in `history`, before was easier because all we had to do was to replace the whole array. 64 | ```js 65 | function selectSquare(square) { 66 | if( winner || currentSquares[square] ){ 67 | return; 68 | } 69 | const newHistory = history.slice(0, currentStep + 1); 70 | const squaresCopy = [...currentSquares]; 71 | squaresCopy[square] = nextValue; 72 | setHistory([...newHistory, squaresCopy]) 73 | setCurrentStep(newHistory.length); 74 | } 75 | ``` 76 | One of the most interesting parts of this is the definition of `newHistory`, we use the [[Array.prototype.slice()|slice]] method because we do not care about the following steps. This is especially useful when a user time-travel backwards since we will replace the following steps with the new one. -------------------------------------------------------------------------------- /2. React Hooks/5. useRef and useEffect DOM interaction.md: -------------------------------------------------------------------------------- 1 | Always remember that when we write JSX like `
    helloooo
    ` in React at the end of the day we **always** call the [[React.createElement()|createElement]] fn and for this reason we do not have direct access to that DOM element and that's because DOM nodes aren't created at all until the [[ReactDOM.render()]] method is called. 2 | 3 | The render method that we use in React, the `return` statement that we use in a [[functional component]], is solely responsible to create and return the React Element. 4 | 5 | So in order to get access to a DOM element in React we have to ask for it, or better yet [[React.useRef()|ref]] to it 😉 6 | 7 | ```js 8 | function MyDiv() { 9 | const myDivRef = React.useRef() 10 | React.useEffect(() => { 11 | const myDiv = myDivRef.current 12 | // myDiv is the div DOM node! 13 | console.log(myDiv) 14 | }, []) 15 | return
    hi
    16 | } 17 | ``` 18 | Most of the time we use [[React.useEffect()|useEffect]] because even React **can access a DOM element only once this has been mounted** and for this we use an `useEffect` with [[React.useEffect()#Running after mount the component - with an empty dependencies array|an empty array]] that will be run only once right after the mounting phase. 19 | 20 | In this exercise we need to access to the DOM because the library that we use, `vanilla-tilt`, works directly with the DOM and for this reason we need to provide a way to the library to access this. 21 | 22 | Basically we use [[React.useRef()|useRef]] any time we want keep a reference to something inside our app (could be a DOM node as this case or a [[React Element]]) without firing a re-render. This reference is mutable, means that it'll update its `current` property based on what happen to the reference. -------------------------------------------------------------------------------- /2. React Hooks/6. useEffect HTTP requests.md: -------------------------------------------------------------------------------- 1 | One of the most used side effect on any app, at least nowadays, is making HTTP requests. Generally with an HTTP request we make a request to an API, we wait for the response of the server and once got it we display the data. This is no different from the side-effects we need to apply to a [[5. useRef and useEffect DOM interaction|rendered DOM]] or when interacting with [[2. useEffect 'persistent state'|browser APIs like localStorage]]. 2 | 3 | The exercise require us to make an HTTP request to a Pokemon API and show the information we get from the server response. To treat this as a side effect we use [[React.useEffect()#Make an HTTP request|useEffect]] that will help use make the request and update the state with the correct information. 4 | 5 | The funny thing about making HTTP requests within [[React.useEffect()|useEffect]] is that we cannot use the [[async/await syntax]] as we can think. Write something like: 6 | ```js 7 | React.useEffect(async () => { 8 | const result = await doSomeAsyncThing() 9 | // do something with the result 10 | }) 11 | ``` 12 | Is an error because when we make a function [[async/await syntax|async]] this returns a [[Promise]] and does not allow us to write the cleanup function. So if we want to use the [[async/await syntax]] we need to do a little workaround: 13 | ```js 14 | React.useEffect(() => { 15 | async function effect() { 16 | const result = await doSomeAsyncThing() 17 | // do something with the result 18 | } 19 | effect() 20 | }) 21 | ``` 22 | Basically we need to create a named function an then execute it after the declaration, this ensures that you don’t return anything but a cleanup function if present. 23 | 24 | If we do not want to write this kind of syntax we could always create an utility function, like `fetchPokemon` from the exercise and then call it inside our [[React.useEffect()|useEffect]]. 25 | ```js 26 | // Utility fn saved or written separately 27 | function fetchPokemon(name, delay = 1500) { 28 | const pokemonQuery = `` 29 | 30 | return window 31 | .fetch('path') 32 | .then(async response => { 33 | const {data} = await response.json() 34 | if (response.ok) { 35 | const pokemon = data?.pokemon 36 | if (pokemon) { 37 | return pokemon 38 | } else { 39 | return Promise.reject(new Error(`No pokemon with the name "${name}"`)) 40 | } 41 | } else { 42 | // handle errors 43 | return Promise.reject(error) 44 | } 45 | }) 46 | } 47 | 48 | // Using the utility fn inside our useEffect 49 | React.useEffect( () => { 50 | fetchPokemon(pokemonName).then( () => { 51 | // Do stuff with the response 52 | }) 53 | }, [pokemon]) 54 | ``` 55 | 56 | ### Extra credit 1: handling errors 57 | Since we're using a [[Promise]] here we are always able to set a callback for the success of our request and on on error. We already created the one for success that sets our pokemon 58 | ```js 59 | fetchPokemon(pokemonName).then(pokemon => setPokemon(pokemon)) 60 | ``` 61 | But now it's time to manage also the error, that in this case is generally the response that hasn't been found a pokemon at all. So we need to create a new piece of state that will store the error (so we fire a new re-render) and will use the updater function to save the message of the error. 62 | ```js 63 | const [error, setError] = React.useState(null) 64 | 65 | // Other stuff... 66 | React.useEffect(() => { 67 | if (!pokemonName) { 68 | return 69 | } 70 | // We reset the pokemon and the error at each fetch 71 | setPokemon(null) 72 | setError(null) 73 | fetchPokemon(pokemonName).then( 74 | pokemon => setPokemon(pokemon), 75 | error => setError(error), // We set the error and will display it on our UI 76 | ) 77 | }, [pokemonName]) 78 | ``` 79 | ### Extra credit 2 - use a status 80 | In this case the example of a status is really close at what a [[state machine]] is. A state machine describes any kind of state/status our app can be. For example for a simple "fetching app" like this one statuses can be: 81 | * idle 82 | * pending 83 | * resolved 84 | * error 85 | 86 | Based on this knowledge we can write the logic necessary to update our app at any point without encounter nasty problems that we could face if we were relying just on a boolean or on the status of different variables. 87 | 88 | ```js 89 | const [status, setStatus] = React.useState('idle'); 90 | 91 | ... 92 | 93 | React.useEffect(() => { 94 | ... 95 | setStatus('pending'); // Changing the status when making the call 96 | fetchPokemon(pokemonName).then( 97 | // In case of success 98 | pokemon => { 99 | setPokemon(pokemon); 100 | setStatus('resolved'); // Changing the status in case of success 101 | }, 102 | // In case of error 103 | error => { 104 | setError(error); 105 | setStatus('error'); // Changing the status in case of error 106 | } 107 | ) 108 | }, [pokemonName]) 109 | 110 | switch(status){ 111 | case 'idle': 112 | // Waiting some input 113 | case 'pending': 114 | // Making the request 115 | case 'resolved': 116 | // Request succesfull 117 | case 'error': 118 | // Error in request 119 | default: 120 | throw new Error('This should be impossible') 121 | } 122 | ``` 123 | As we can see now our logic depend solely on the value of `status` and it make a lot clearer how the app is responding based on it. 124 | ### Extra credit 3 - store the state in an object 125 | No notes because in future we will use [[React/useReducer()|useReducer]]. 126 | ### Extra credit 4 - create an ErrorBoundary component 127 | `ErrorBoundary` components must be class based component, this is the **only time** where in React is requested this kind of structure (at least until now Oct 2020). 128 | 129 | The component will be wrapping the other where we want to catch those errors, in our exercise it was the `PokemonInfo` because at the beginning if we submitted and empty string the page went blank. 130 | 131 | The `ErrorBoundary` component does not have anything special in its `render` method, we just pass the `children` prop, but in the body you'll find the declaration of the method `getDerivedStateFromError` that returns an object that will be pushed to the state of the component. 132 | ```js 133 | class ErrorBoundary extends React.Component{ 134 | state = {error: null} 135 | static getDerivedStateFromError(error) { 136 | return {error} 137 | } 138 | render(){ 139 | return this.props.children; 140 | } 141 | } 142 | ``` 143 | There is one last piece that we need to manage and it is the case of **what do we render in case of the component**? 144 | 145 | As for now we still render the `children` but in order to manage the error we need to check for it inside the `render` method and output something more useful if there is one. 146 | ```js 147 | render(){ 148 | const {error} = this.state; 149 | if(error){ 150 | return ( 151 |
    152 | There was an error:{' '} 153 |
    {error.message}
    154 |
    155 | ) 156 | } 157 | return this.props.children; 158 | } 159 | ``` 160 | Now our `ErrorBoundary` has all the structure needed and we can modify the logic inside our component to just throw an error in case we need to. So from this: 161 | ```js 162 | case 'rejected': 163 | return ( 164 |
    165 | There was an error:
    {error.message}
    166 |
    167 | ); 168 | ``` 169 | We change into this: 170 | ```js 171 | case 'rejected': 172 | throw error; 173 | ``` 174 | The position of our `ErrorBoundary` component it is important because if an error is thrown it will be reflected into our app, this means that is I put the component at the root of my application and we got an error **the whole application** will be replaced by the error. 175 | ### Extra credit 5 - re-mount the error boundary 176 | The solution implemented in the previous exercise has broken our app, even if it handles the error correctly 😃 177 | 178 | The reason is that we're not reset the state for `ErrorBoundary` and even if we pass a new value that needed to be evaluated the component does not allow us to render its children: aka the `PokemonInfo` component. 179 | 180 | In order to solve this in we have many solution, with this particular example we're going to use the `key` prop to force the mount and unmount of our `ErrorBoundary` component. 181 | ```js 182 | 183 | ``` 184 | Since we already have a value that we will re-render of our app we use the `pokemonName` as key for the `ErrorBoundary` component so each time it's changes we will unmount and mount the component because from the React point of view they are different components because have different `key` properties. 185 | ### Extra credit 6 - use react-error-boundary 186 | This is easy, in order to not have to write a class component we could install the `react-error-boundary` npm package and use it in place of our `ErrorBoundary` component. The API are the same so we just need to import it and we're good to go. 187 | ```js 188 | // Install it first with 'npm install react-error-boundary' 189 | import {ErrorBoundary} from 'react-error-boundary' 190 | ``` 191 | ### Extra credit 7 - reset the error boundary 192 | The quick solution of adding a `key` to our `ErrorBoundary` component is however no good because each time it changes we will unmount even the children components and this is something that we need to worry about. 193 | 194 | Go and check the solution in the video 😉 It's just how to use the API of the new `ErrorBoundary` component that will let us create a button to reset it. 195 | 196 | Here's the full code: 197 | ```js 198 | function ErrorFallback({error, resetErrorBoundary}) { 199 | return ( 200 |
    201 | There was an error:{' '} 202 |
    {error.message}
    203 | 204 |
    205 | ) 206 | } 207 | 208 | function App() { 209 | const [pokemonName, setPokemonName] = React.useState('') 210 | 211 | function handleSubmit(newPokemonName) { 212 | setPokemonName(newPokemonName) 213 | } 214 | 215 | function handleReset() { 216 | setPokemonName('') 217 | } 218 | 219 | return ( 220 |
    221 | 222 |
    223 |
    224 | 225 | 226 | 227 |
    228 |
    229 | ) 230 | } 231 | ``` 232 | Notice how we use `resetErrorBooundary` (we didn't create the fn, it's part of the component API) to create a button that resets that component state and how we use the `handleReset` fn to set `pokemonName` to empty and give to the user of our app a way to do a new search. 233 | 234 | Also notice that we solved a different minor problem in the initialization of the state of `PokemonInfo`, go to the video for the details: 235 | ```js 236 | const [state, setState] = React.useState({ 237 | status: pokemonName ? 'pending' : 'idle', 238 | ``` 239 | ### Extra credit 8 - use resetKeys 240 | This video explain the `resetKeys` prop of `ErrorBoundary` component that will accept an array of values that once change it will reset the component automatically, so in our case we only need to add the `pokemonName` in it and it will reset. 241 | ```js 242 | 247 | ``` -------------------------------------------------------------------------------- /3. Advanced React Hooks/1. useReducer simple Counter.md: -------------------------------------------------------------------------------- 1 | Reducers became *"famous"* (at least for me) with the [[Redux]] library because help you to manage big chucks of states of your app. 2 | 3 | The same is happening with Hooks. 4 | 5 | Most of the time we work well with state handled by [[React.useState()|useState]] but this is forcing us to keep it **inside our component** and make it difficult to pass these data to a different component that could use the values stored inside. 6 | 7 | To store the state inside a single object that we can pass around our application we use the [[React.useReducer()|useReducer]] Hook. In it simplest we use like below: 8 | ```js 9 | function nameReducer(previousName, newName) { 10 | return newName 11 | } 12 | 13 | const initialNameValue = 'Joe' 14 | 15 | function NameInput() { 16 | const [name, setName] = React.useReducer(nameReducer, initialNameValue) 17 | const handleChange = event => setName(event.target.value) 18 | return ( 19 | <> 20 | 23 |
    You typed: {name}
    24 | 25 | ) 26 | } 27 | ``` 28 | One important thing to note is that `nameReducer` is called with two arguments: 29 | 30 | 1. the current state/*store* 31 | 2. whatever the dispatch fn `setName` is called with. This is often called an *"action"*. 32 | 33 | Store and Action are two important concept of [[Redux]], that important that the same React is using the same logic to help us handle our (global) state. 34 | ## Exercise 35 | We're implementing a Counter and starts with the component that already works with [[React.useState()|useState]], we need to change the logic to implement the same but with ([[React.useReducer()|useReducer]]). 36 | #### `useState` implementation 37 | ```js 38 | function countReducer(state, newState) { 39 | return newState; 40 | } 41 | 42 | function Counter({initialCount = 0, step = 1}) { 43 | const [count, setCount] = React.useState(initialCount) 44 | const increment = () => setCount(count + step) 45 | return 46 | } 47 | 48 | function App() { 49 | return 50 | } 51 | 52 | export default App 53 | ``` 54 | #### `useReducer` implementation 55 | ```js 56 | function countReducer(state, newState) { 57 | return newState; 58 | } 59 | 60 | function Counter({initialCount = 0, step = 1}) { 61 | const [count, setCount] = React.useReducer(countReducer, initialCount) 62 | const increment = () => setCount(count + step) 63 | return 64 | } 65 | 66 | function App() { 67 | return 68 | } 69 | 70 | export default App 71 | ``` 72 | This example is really simple and use [[React.useReducer()|useReducer]] is an overkill but we need to start somewhere. 73 | ## Extra Credit 74 | 75 | ### 1. 💯 accept the step as the action 76 | We need to implement a little change in our reducer. Now the API does not pass anymore the current state value (the `store` if you like [[Redux]]) and the value we need to update the state to, now we pass only the `step` representing the amount that we need to update the state to. 77 | ```js 78 | const [count, changeCount] = React.useReducer(countReducer, initialCount); 79 | const increment = () => changeCount(step) 80 | ``` 81 | Now we do not pass anymore the updated value for our `count` state, we only pass the amount we want it to increment. 82 | 83 | Luckly for us we can use the value that we keep in the reducer as the first parameter and we can change it to respect our different API: 84 | ```js 85 | function countReducer(count, step) { 86 | return count + step; 87 | } 88 | ``` 89 | Since we returning right away the value we can leverage [[arrow function]] syntax: 90 | ```js 91 | const countReducer = (count, step) => count + step; 92 | ``` 93 | 94 | ### 2. 💯 simulate setState with an object 95 | Now it is time to simulate how the old [[this.setState()|setState]] with the class syntax was working before the advent of Hooks. If you need a refresher here you find the most important part, if you need more check the linked doc. 96 | 97 | `setState` in a class component was very useful because let us **update only portion of the state**, this means that we could have really complex object representing the state of the component but the update function, the `this.setState()` was really simple to style. 98 | ```js 99 | class MyComponent extends React.Component{ 100 | constructor(props){ 101 | super(props); 102 | // Defining the state for the component 103 | this.state = { 104 | complex: 'object', 105 | isComplex: true, 106 | } 107 | } 108 | 109 | // Pseudo code 110 | 111 | // Changing complex 112 | this.setState({ complex: 'not true' }); 113 | // State is now { complex: 'not true, isComplex: true } 114 | 115 | // Changing isComplex 116 | this.setState({ isComplex: false }); 117 | // State is now { complex: 'not true, isComplex: false } 118 | } 119 | ``` 120 | As you can see from the example the great thing about `this.setState` was that with it was possible update parts of the state without the need to handle the presence of properties of the object that were not handled by the update fn. 121 | 122 | In order to behave like so I set my `counterReducer` as follows: 123 | ```js 124 | function countReducer(state, newState) { 125 | return { 126 | ...state, 127 | ...newState 128 | }; 129 | } 130 | ``` 131 | Basically I am using the [[spread operator]] to "expand" the current state and let the spread of `newState` override only the properties that contains. 132 | 133 | ### 3. 💯 simulate setState with an object OR function 134 | Now we want to make a little edit on how we send the new data, we want the `this.setState` fn to accept a function that will be in charge of updating the object. 135 | 136 | This is the basic code that we can work with: 137 | ```js 138 | const [state, setState] = React.useReducer(countReducer, { 139 | count: initialCount, 140 | }) 141 | const {count} = state 142 | const increment = () => 143 | setState(currentState => ({count: currentState.count + step})) 144 | ``` 145 | As you can see `setState` now accept an [[arrow function]] that will call the `currentState` value and will update the specific values stored in the object properties. 146 | 147 | So our function in `setState` needs to access to the state current value. 148 | 149 | If we want to accept **only** functions to update our state we could change our `countReducer` like so: 150 | ```js 151 | const countReducer = (state, action) => action(state); 152 | ``` 153 | But we want to support both API. The one that let us update the state passing an object and the one that let us the same by passing a function. 154 | 155 | All we need to do different by previous implementation is to check if the passed `action` is a fn or not and spread the right value: 156 | ```js 157 | const countReducer = (state, action) => ( { 158 | ...state, 159 | ...(typeof action === 'function' ? action(state) : action ) 160 | } ); 161 | ``` 162 | Maybe you're a bit confused from this, at least I was the first time I encounter this syntax. 163 | 164 | After the [[spread operator]] we run an expression ([[ternary operators]] are seen like expressions), if `action` is a function we call it with the current state and then we spread the returned value but if it is not we just spread the `action` as is. 165 | 166 | I am sure we will get more confident with this syntax but to be clear is just JavaScript, React here does not get involved. 167 | 168 | ### 4. 💯 traditional dispatch object with a type and switch statement 169 | Now we get more close to the [[Redux]] way of doing things, our `action` becomes an object that will have a `type` property that will be checked by an `switch` statement to let the reducer decide how to behave. 170 | 171 | Our starting point now is this: 172 | ```js 173 | const [state, dispatch] = React.useReducer(countReducer, { 174 | count: initialCount, 175 | }) 176 | const {count} = state 177 | const increment = () => dispatch({type: 'INCREMENT', step}) 178 | ``` 179 | As you can see now we have a `dispatch` fn that is creating the action object for us, now it is time to change our reducer to accept it. 180 | ```js 181 | function countReducer( state, action ){ 182 | switch (action.type) { 183 | case "INCREMENT": 184 | return { count: state.count + action.step } 185 | default: 186 | throw new Error(`Unsupported action type: ${action.type}`) 187 | } 188 | } 189 | ``` 190 | In the `default` we follow Kent's advice and throw an error in case that the `type` is not treated by our reducer. -------------------------------------------------------------------------------- /3. Advanced React Hooks/2. useCallback custom hooks.md: -------------------------------------------------------------------------------- 1 | It's time to dig deeper in the [[React.useEffect()|useEffect]] Hook. In the previous lessons ([[2. useEffect 'persistent state'|this]] and [[6. useEffect HTTP requests|this]]) we discovered the `useEffect` Hook and how we can keep it in sync with the state of the app without let it run every time. 2 | 3 | To handle the *'runs'* of `useEffect` we have to pass to it an array that will holds all the values that, if changed, will require the Hook to run. It is common to put data in there but in some cases we could call an external function inside our Hook and the things here get's a bit complicated. 4 | ```js 5 | const updateLocalStorage = () => window.localStorage.setItem('count', count) 6 | React.useEffect(() => { 7 | updateLocalStorage() 8 | }, []) 9 | ``` 10 | As this Hook has been written it'll run only once after the render of the component. This is **not a good solution** because if our `count` changes during the life of our component we will never save it to [[localStorage]]. 11 | 12 | We can think of passing the `count` variable as a dependency of our [[React.useEffect()|useEffect]] but what happen if the function changes? 13 | ```js 14 | const updateLocalStorage = () => window.localStorage.setItem(key, count) 15 | ``` 16 | Well we should update the dependency array too... **No good** because this will get complicated over time. 17 | 18 | We can think to **add the same function** to the dependency array/list **but** even in this case it would be a bad idea because: 19 | > The function is defined **inside the component function** and for this reason at each render we will have a brand new function. It gets declared one more time. 20 | 21 | For this reason we can not put our function inside the dependency array, at each render React will see a **new function** and our `useEffect` Hook will be called each time. 22 | 23 | But problem persists. We have a `useEffect` Hook and we want it to run only when needed. 24 | 25 | **`useCallback` to the rescue!!** 26 | React gives us this Hook that will help us use a function, declared inside the component function, as a dependency of our `useEffect`. 27 | 28 | [[React.useCallback()|useCallback]] Hook has been created solely to be able to create a [[memoize|memoized]] version of a function. 29 | ```js 30 | const updateLocalStorage = React.useCallback( 31 | () => window.localStorage.setItem('count', count), 32 | [count], // dependency array 33 | ) 34 | 35 | React.useEffect(() => { 36 | updateLocalStorage() 37 | }, 38 | [updateLocalStorage] // Now I can use it, has been memoized 39 | ) 40 | ``` 41 | When we want to use [[React.useCallback()|useCallback]] Hook the syntax is pretty similar to `useEffect` but in this case we are not *running* the function contained, we [[memoize]] it so we can use it as a dependency of the `useEffect`. 42 | 43 | # Exercise 44 | In the first iteration of the exercise we need to transform the standard use of [[React.useReducer()|useReducer]] and make a custom hook that will handle the async calls, `useAsync`. 45 | 46 | > When we import something with the `import` keyword at the top of the file, as happen for the `fetchPokemon` async fn, we do not need to worry about the dependency list in our `useReducer` because something that has been imported via module **can never change** and this React knows pretty well. 47 | 48 | Before digging deep inside the `useAsync` fn (a [[custom Hook]] to be precise) we need to understand **why** we should not use a fn as a parameter in our dependency list. 49 | 50 | ```js 51 | function PokemonInfo({pokemonName}) { 52 | const state = useAsync( 53 | () => { 54 | // this is the asyncCallback 55 | }, 56 | // more code... 57 | ) 58 | // rest of component... 59 | ``` 60 | In this little small snippet of code you see that I define the `asyncCallback` right inside the component. This makes a **new declaration of my fn** at each render because React does not know that it already met the function before. 61 | 62 | In order to solve this we can leverage the [[React.useCallback()|useCallback]] Hook but this is for the next extra credit... 63 | 64 | Now we want/need to solve this situation with a different approach, we need to pass values inside our dependency list. The quick solution is to pass an additional parameter `dependencies` that'll be the array containing all the deps for our [[custom Hook]]. 65 | ```js 66 | function useAsync(asyncCallback, initialState, dependencies) { 67 | const [state, dispatch] = React.useReducer(asyncReducer, { 68 | status: 'idle', 69 | data: null, 70 | error: null, 71 | ...initialState, 72 | }) 73 | 74 | React.useEffect(() => { 75 | const promise = asyncCallback() 76 | if (!promise) { 77 | return 78 | } 79 | dispatch({type: 'pending'}) 80 | promise.then( 81 | data => { 82 | dispatch({type: 'resolved', data}) 83 | }, 84 | error => { 85 | dispatch({type: 'rejected', error}) 86 | }, 87 | ) 88 | // too bad the eslint plugin can't statically analyze this :-( 89 | // eslint-disable-next-line react-hooks/exhaustive-deps 90 | }, dependencies) 91 | 92 | return state 93 | } 94 | ``` 95 | As you can see there's nothing special in our `useAsync` custom Hook, we use [[React.useReducer()|useReducer]] to manage the state and inside the [[React.useEffect()|useEffect]] we handle the state changes based on the promise that is returned from the `asyncCallback` fn. 96 | ## Extra credit 97 | ### 1. 💯 use [[React.useCallback()|useCallback]] to empower the user to customize memoization 98 | Basically all we want is to transform the `asyncCallback` into a [[memoize|memoized]] fn so we can use it as a dependency of our `useEffect` inside the custom Hook and get rid of the `dependencies` array that could not always be the best solution. 99 | 100 | In order to do so we need to use the [[React.useCallback()|useCallback]] Hook and pass into it the function defined while using `useAsync`: 101 | ```js 102 | // Previous usage of useAsync 103 | const state = useAsync( 104 | () => { 105 | if (!pokemonName) { 106 | return 107 | } 108 | return fetchPokemon(pokemonName) 109 | }, 110 | {status: pokemonName ? 'pending' : 'idle'}, 111 | [pokemonName], 112 | ) 113 | 114 | // New approach with useCallback 115 | // Before I memoize the fn with useCallback() 116 | const asyncCallback = React.useCallback( () => { 117 | if (!pokemonName) { 118 | return 119 | } 120 | return fetchPokemon(pokemonName) 121 | }, [pokemonName] ) // <- useCallback() has a dependency list too 122 | // Then I use it while calling my custom component 123 | const state = useAsync( 124 | asyncCallback, 125 | {status: pokemonName ? 'pending' : 'idle'}, 126 | ) 127 | ``` 128 | As you can see as well this it wasn't that difficult 😉 129 | ### 2. 💯 return a memoized `run` function from useAsync 130 | We're asking a lot from users of our Hook, we're asking them to read the documentation and remember that we need them to pass a memoized fn. 131 | 132 | This exercise aims to solve that in order to provide a `run` fn that will [[memoize]], in our case, the `fetchPokemon` call. 133 | 134 | In order to do so we need to change things a bit, first and foremost our component will not accept an `asyncCallback` (because now it's time for us to create it) and we need to provide our `run` fn from the Hook. This is how users of our hook will use it: 135 | ```js 136 | const {data: pokemon, status, error, run} = useAsync({ 137 | status: pokemonName ? 'pending' : 'idle', 138 | }) 139 | 140 | React.useEffect(() => { 141 | if (!pokemonName) { 142 | return 143 | } 144 | run(fetchPokemon(pokemonName)) 145 | }, [pokemonName, run]) 146 | ``` 147 | Now it's time to transform our `useAsync` by removing the `asyncCallbar` first parameter and create the `run` fn. 148 | 149 | Since now we also have the `promise` that returns the `fetchPokemon` fn right inside the custom hook we can get rid of the [[React.useEffect()|useEffect]] because it first duty was to check that we did have a promise, a little thing that our user now can take care of since this are his data from the component. 150 | 151 | So the "only thing" that our `run` function will be responsible of will be to update the state that will be returned to the component. 152 | ```js 153 | const run = React.useCallback(promise => { 154 | dispatch({type: 'pending'}) 155 | promise.then( 156 | data => { 157 | dispatch({type: 'resolved', data}) 158 | }, 159 | error => { 160 | dispatch({type: 'rejected', error}) 161 | }, 162 | ) 163 | }, []) 164 | ``` 165 | As you can see we do not need to add anything in the `run` fn because all the data that we uses, the `promise` that's a parameter and the `dispatch` fn will never change inside our custom Hook. 166 | 167 | One last thing that got me confused was: *How do I return the fn if not inside the state?* Well thankfully we can spread the state object and add it back to it: 168 | ```js 169 | return { ...state, run } 170 | ``` 171 | > *Note:* we could also have declared the `run` fn right inside our state declaration but following Kent's code seems a bit cleaner this way. 172 | 173 | ### 3. 💯 make safeDispatch with useCallback, useRef, and useEffect 174 | With this exercise we aim to handle the situations where we are waiting for a response (or a complex calculation) but once the operation finishes the component got unmounted and React will throw this warning: 175 | 176 | *Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.* 177 | 178 | In order to solve this issue we have to create a [[useRef]] that will track if the component is mounted or not, based on this information we can wrap the dispatch function so we can make use of the [[React.useCallback()|useCallback]] to make things even safer. 179 | 180 | This is the code example of above generalized in a custom Hook `useSafeDispatch`: 181 | ```js 182 | function useSafeDispatch(dispatch) { 183 | const mountedRef = React.useRef(false) 184 | 185 | React.useEffect(() => { 186 | mountedRef.current = true 187 | return () => (mountedRef.current = false) 188 | }, []) 189 | 190 | return React.useCallback( 191 | (...args) => (mountedRef.current ? dispatch(...args) : void 0), 192 | [dispatch], 193 | ) 194 | } 195 | ``` 196 | 197 | --- 198 | 199 | #### Notes on memoization 200 | > Taken in part from the [linked article](https://epicreact.dev/memoization-and-react/). 201 | 202 | React gives us 3 option to [[memoize]] something but one of the first things to understand is that the cache strategy implemented has a size of 1, meaning that only the most recent value of input and result will be kept. 203 | 204 | Does not matter which API we use, if we use [[memo]], [[React.useMemo()|useMemo]] or [[React.useCallback()|useCallback]], we memoize something in React because: 205 | * we need to improve performances by not running same fn, with same arguments, between renders 206 | * value stability 207 | 208 | The first point is enough self-explanatory so let dig deeper in the second one. 209 | 210 | As explained earlier while describing the `updateLocalStorage` fn, if we use a fn that is declared **inside** our component this fn will get declared each time our component renders, this means that we will have a *'new'* `updateLocalStorage` at each render. 211 | 212 | If we need to use a fn in a dependency list/array then **we need to memoize it** and since we're talking about fn we will do so with the [[React.useCallback()|useCallback]] Hook. 213 | 214 | 215 | -------------------------------------------------------------------------------- /3. Advanced React Hooks/3. useContext simple Counter.md: -------------------------------------------------------------------------------- 1 | In React you can use the built-in `context` to share a piece of state used by many components, this can save us from the problem we can face with [prop-drilling](https://kentcdodds.com/blog/prop-drilling). 2 | 3 | In order to make use of the `context` you first have to **create** one and then you have to **provide** the values that it holds. 4 | 5 | Making the last line into code when you use `context` you'll mostly create something like this: 6 | 7 | ```js 8 | // Create the context 9 | const FooContext = React.createContext() 10 | 11 | function FooDisplay() { 12 | // Use the value stored in the specific context 13 | const foo = React.useContext(FooContext) 14 | return
    Foo is: {foo}
    15 | } 16 | 17 | ReactDOM.render( 18 | // Use the Provider to make available the value 19 | 20 | 21 | , 22 | document.getElementById('root'), 23 | ) 24 | ``` 25 | 26 | # Exercise 27 | The base exercise is pretty simple, basically you have to follow the previous code and change a little bit in order to create a `context` that is able to keep the state of a counter. 28 | 29 | Beside that we discovered that we can create the `Provider` as a custom component, this makes easier to handle it's state. 30 | 31 | ```js 32 | function CountProvider(props){ 33 | // Leverage useState to handle updates 34 | const [ count, setCount ] = React.useState(0); 35 | // Making a single array since we need to pass a single variable as value 36 | const value = [ count, setCount ]; 37 | 38 | return ( 39 | // I am spreading also all the props so also the children will be taken care of 40 | 41 | ); 42 | } 43 | ``` 44 | 45 | Once you structure the provider in this way you can get the value and the function in charge to update the state [[destructuring]] the array that we get with the [[useContext]] hook: 46 | 47 | ```js 48 | // Retrieve just the value 49 | const [count] = React.useContext(CountContext); 50 | // Retrieve just the updater function 51 | const [, setCount] = React.useContext(CountContext); 52 | // Retrieve both destructuring 53 | const [count, setCount] = React.useContext(CountContext); 54 | // Just get all the value stored in context 55 | const value = React.useContext(CountContext); 56 | ``` 57 | 58 | ## Extra credit 59 | ### 1. 💯 create a consumer hook 60 | In this exercise we want to create a [[custom Hook]] that will help us retrieve the values stored in our `context` and at the same time show an useful error message in case the user that is using our custom Hook is calling it without the proper `Provider` applied. 61 | 62 | ```js 63 | // This is our new custom Hook 64 | const [count, setCount] = useCount() 65 | ``` 66 | We are creating a custom Hook because we want to make our code as reusable as possible because without it we had to check the presence of the `Provider` in each component that uses that `context`. 67 | ```js 68 | // My custom Hook that can be saved wherever we want 69 | function useCount(){ 70 | const context = React.useContext(CountContext); 71 | 72 | if( !context ){ 73 | throw new Error('useCount must be used within the CountContext'); 74 | } 75 | 76 | return context; 77 | } 78 | ``` 79 | 80 | ### 2. 💯 caching in a context provider 81 | Now we have an example half made, the Pokemon search one. Our scope for the exercise is to put in the `context` the cache that has already prepared so we can use it in two different components at the same time. 82 | 83 | Since this exercise does not present anything new, we just move the [[React.useReducer()|useReducer]] inside the `Provider` I am just going to skip it. If you want you can check the code and the video for a full explanation. -------------------------------------------------------------------------------- /3. Advanced React Hooks/4. useLayoutEffect auto-scrolling textarea.md: -------------------------------------------------------------------------------- 1 | [[React.useLayoutEffect()|useLayoutEffect]] is a really close Hook than [[React.useEffect()|useEffect]], they share the **same API** but they differ in when are fired. 2 | 3 | Basically you have to use `useLayoutEffect` when your effect is mutating the DOM (via a DOM node ref) **_and_** the DOM mutation will change the appearance of the DOM node between the time that it is rendered and your effect mutates it. To better understand this it runs synchronously immediately after React has performed all DOM mutations. 4 | 5 | > You use `useLayoutEffect` if the side effect that you are performing makes an observable change to the DOM that will require the browser to paint that update that you've made. 6 | 7 | I am not going to add much here because the exercise is really simple, you just have to change `useEffect` with `useLayoutEffect`, and make more sense if you experience first hand what the difference are between the two. -------------------------------------------------------------------------------- /3. Advanced React Hooks/5. useImperativeHandle scroll to top-bottom.md: -------------------------------------------------------------------------------- 1 | First thing we are reassign the `MessageDisplay` fn to create its `forwardRef` version: 2 | ```js 3 | MessageDisplay = React.forwardRef(MessageDisplay); 4 | ``` 5 | This will give us a linting error, that we can ignore, but the most important thing is that now we can accept a second argument with our `MessageDisplay` that will let us handle the `ref` passed in the component. 6 | 7 | Now that I have it I can assign to the `current` prop the methods that I am calling when I press the buttons. Let's check first why I need to do this checking what's going on in the `App` component: 8 | ```jsx 9 |
    10 | 11 |
    12 | 13 |
    14 | 15 |
    16 | ``` 17 | As you can see my JSX have two `onClick` event handler that will call different fns defined in the component like so: 18 | ```js 19 | const scrollToTop = () => messageDisplayRef.current.scrollToTop() 20 | const scrollToBottom = () => messageDisplayRef.current.scrollToBottom() 21 | ``` 22 | Now we can see the relationship with the `ref.current` and we need to implement them in our `MessageDisplay`, the component that will need to scroll based on the pressed button. 23 | ```js 24 | ref.current = { 25 | scrollToTop, 26 | scrollToBottom, 27 | } 28 | ``` 29 | Those fns are defined just below my definitions and will work on the `scrollToTop` property of the DOM element I am referring to. 30 | ```js 31 | function scrollToTop() { 32 | containerRef.current.scrollTop = 0 33 | } 34 | function scrollToBottom() { 35 | containerRef.current.scrollTop = containerRef.current.scrollHeight 36 | } 37 | ``` 38 | With this approach **we have a problem though**, we are directly manipulating a value during our render method. We could use [[React.useLayoutEffect()|useLayoutEffect]] as seen in the [[4. useLayoutEffect auto-scrolling textarea|previous lesson]] (because it is an Hook that fires before the DOM gets rendered) but the React team has created something better for us. 39 | 40 | So instead to set the `ref` ourself we will leverage the `useImperativeHandle`. 41 | 42 | With this Hook we can pass the `ref` we want to work with and as a second parameter we add a callback fn that will return the properties we want to add to it. 43 | ```js 44 | React.useImperativeHandle( 45 | ref, 46 | () => ({ 47 | scrollToTop, 48 | scrollToBottom, 49 | })) 50 | ``` 51 | 52 | -------------------------------------------------------------------------------- /3. Advanced React Hooks/6. useDebugValue useMedia.md: -------------------------------------------------------------------------------- 1 | Let me begin with **finally** 🎉 2 | I am kinda of exited here because (finally) I now have an answer about a little thing that annoys me most when work with Hooks and that is the (IMHO) poor implementation inside the React Developer Tools ([Chrome](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=en) - [Firefox](https://addons.mozilla.org/it/firefox/addon/react-devtools/)) of the browsers. 3 | 4 | With class based component debug a state was easy, at the end of the day you get a `state` object for each component and you could look at all its `props` and values in it. 5 | 6 | With Hooks... Well you just have a bunch of same string without any reference to the meaning of the value, you have to check the value to try to understand what piece of state you're looking at. 7 | 8 | The `useDebugValue` works **only on custom hooks** (not in a standard component) but is really useful because you should be able now to understand the power in creating your [[custom Hook]]. 9 | 10 | All you have to do is to call the `useDebugValue` inside your custom Hook like so: 11 | ```js 12 | React.useDebugValue(`\`${query}\` => ${state}`); 13 | ``` 14 | And you will be able to see the debugging infomations improved from this: 15 | ![[standard-debug-information.png]] 16 | to this: 17 | ![[improved-debug-information.png]] 18 | 19 | Check the [source code](https://github.com/kentcdodds/advanced-react-hooks/blob/main/src/final/06.js#L6-L18) or the video to see how the custom Hook is created 😉 20 | 21 | You can also pass a second argument that is a callback function to format the function, this fn exist only because we can postpone the calculation needed to display the value. If it is going to be some expensive calc better to move them away so the final user will not pay the price but if is something simple as creating a string with values we already have, like in our example, we can just pass it. -------------------------------------------------------------------------------- /4. Advanced React Patterns/1. Context Module Functions.md: -------------------------------------------------------------------------------- 1 | This kind of pattern is going to help the user of our code, that in our case is the component that's using our [[context]], especially when we want to offer some kind of helper function to handle the state of the `context` itself. 2 | 3 | ```js 4 | const CounterContext = React.createContext() 5 | 6 | function CounterProvider({step = 1, initialCount = 0, ...props}) { 7 | const [state, dispatch] = React.useReducer( 8 | (state, action) => { 9 | const change = action.step ?? step 10 | switch (action.type) { 11 | case 'increment': { 12 | return {...state, count: state.count + change} 13 | } 14 | case 'decrement': { 15 | return {...state, count: state.count - change} 16 | } 17 | default: { 18 | throw new Error(`Unhandled action type: ${action.type}`) 19 | } 20 | } 21 | }, 22 | {count: initialCount}, 23 | ) 24 | 25 | const value = [state, dispatch] 26 | return 27 | } 28 | ``` 29 | 30 | In the example from the explanation of the lesson we have handle the state of our `context` from [[React.useReducer()|useReducer]], doing so will require us to pass also the `dispatch` fn that is listening to the actions that will modify the state. 31 | 32 | The problem is that **the user does not know anything about our actions** or to better phrase the user will need to check the declaration of our `context` to see which kind of action we are looking for in order to update the state. 33 | 34 | We could try to add the specific `dispatch` fn right inside the `context` like so: 35 | ```js 36 | const CounterContext = React.createContext() 37 | 38 | function CounterProvider({step = 1, initialCount = 0, ...props}) { 39 | const [state, dispatch] = React.useReducer( 40 | //Same reducer as before 41 | ) 42 | 43 | // Creating helper functions and pass them via value 44 | const increment = React.useCallback(() => dispatch({type: 'increment'}), [ 45 | dispatch, 46 | ]); 47 | const decrement = React.useCallback(() => dispatch({type: 'decrement'}), [ 48 | dispatch, 49 | ]); 50 | 51 | const value = {state, increment, decrement} 52 | return 53 | } 54 | 55 | // --- How user will use this 56 | const {state, increment, decrement} = useCounter() 57 | ``` 58 | The downside of this approach is that we're adding our methods **in each instance of the object**, meaning that we will bring those helper methods everywhere with us even when we do not need them. 59 | 60 | Best approach would be to create utility fns separately and import them as additional modules that our users can import in specific point of the application. 61 | ```js 62 | const CounterContext = React.createContext() 63 | 64 | function CounterProvider({step = 1, initialCount = 0, ...props}) { 65 | // Same provider as before 66 | } 67 | 68 | function useCounter() { 69 | const context = React.useContext(CounterContext) 70 | if (context === undefined) { 71 | throw new Error(`useCounter must be used within a CounterProvider`) 72 | } 73 | return context 74 | } 75 | 76 | // dispatch is passed as argument of the fn, not related to the one in Provider 77 | const increment = dispatch => dispatch({type: 'increment'}) 78 | const decrement = dispatch => dispatch({type: 'decrement'}) 79 | 80 | // Here I am also exporting the utility fns 81 | export {CounterProvider, useCounter, increment, decrement} 82 | 83 | // --- How user will use this 84 | import {useCounter, increment, decrement} from 'context/counter' 85 | 86 | function Counter() { 87 | const [state, dispatch] = useCounter() 88 | return ( 89 |
    90 |
    Current Count: {state.count}
    91 | 92 | 93 |
    94 | ) 95 | } 96 | ``` 97 | -------------------------------------------------------------------------------- /4. Advanced React Patterns/2. Compound Components.md: -------------------------------------------------------------------------------- 1 | Components that work together to form a complete UI, think of it like `select` and `option` HTML elements. Taken each separately are pretty useless but using them together let use easily create an UI where the user can select a value from a list. 2 | 3 | In React terms the `select` is responsable to handle the state of the component that gets changed with the `option` value. 4 | 5 | In basic terms the compound components pattern let us write more readable component that will help out the user of our code. Let's take the following example from the exercise: 6 | ```js 7 | 12 | ``` 13 | This is my own take on the original code for the exercise that looks as follow: 14 | ```js 15 | 16 | The button is on 17 | The button is off 18 | 19 | 20 | ``` 21 | Which one do you prefer? 22 | 23 | The example is really simple and probably you do not mind pass some props into your component but the basic idea of compound components is that will make easier to change and adapt the component that you're creating based on the requirements of it. 24 | 25 | I've seen many times components that simply have too many props, it even exists a term for it and it's *apropcalypse* (oh boy... there even a [pretty popular hashtag](https://twitter.com/hashtag/apropcalypse) for it). 26 | 27 | As Michael Jackson states in [this video](https://www.youtube.com/watch?v=3XaXKiXtNjw) the `children` prop is the key for the composition of your components. 28 | 29 | So in order to make complex use of custom props we can leverage [[React.Children]] and [[React.cloneElement]] to `map` all the children of `Toggle` and pass to the cloned elements the piece of state that we care about. 30 | 31 | ### Be aware of standard DOM elements! 32 | In the extra credit we're asked to add a simple `span` into our `Toggle` component. From an UI perspective everything was working fine but if you inspect the console you'll notice a nasty error: 33 | ![[DOM-not-supported-props.png]] 34 | 35 | Basically this happens because in our `React.cloneElement` we are passing the `on` and `toggle` as props to **any children** of `Toggle`. This works well when the children are React components (they accept any kind of prop) but for DOM elements this is no good because the browser will try to render the following `span`: 36 | ```js 37 | Hello 38 | ``` 39 | This is my own representation of what the browser will try to do, fortunately for us React already protect ourselves injecting an empty `span` but still alert us in the console to improve the situation. 40 | 41 | Since DOM elements are just strings to the eyes of React my take on it has been the following: 42 | ```js 43 | function Toggle(props) { 44 | const [on, setOn] = React.useState(false) 45 | const toggle = () => setOn(!on) 46 | 47 | return React.Children.map( 48 | props.children, child => { 49 | return typeof child.type === 'string' 50 | ? child 51 | : React.cloneElement( child, { on, toggle }); 52 | }) 53 | } 54 | ``` 55 | From Kent's video there is a nice touch though, basically we could create an array of allowed components that will get access to the state like so: 56 | ```js 57 | const allowedTypes = [ToggleOn, ToggleOff, ToggleButton]; 58 | 59 | function Toggle({children}) { 60 | const [on, setOn] = React.useState(false) 61 | const toggle = () => setOn(!on) 62 | return React.Children.map(children, child => { 63 | if(allowedTypes.includes(child.type)){ 64 | return React.cloneElement(child, {on,toggle}) 65 | } 66 | return child; 67 | }) 68 | } 69 | ``` -------------------------------------------------------------------------------- /4. Advanced React Patterns/3. Flexible Compound Components.md: -------------------------------------------------------------------------------- 1 | The limit that we encounter in the previous exercise was that we force the user of our components to use them at the first level, the user is not allowed to wrap one of the children components in other because we check the `props.children` in order to pass down the state value. 2 | 3 | The moment that you wrap one of your components within something else you brake the application because now the component is not able to access the state of the anchestor. 4 | 5 | To solve this we need to use [[React.useContext|useContext]]. 6 | 7 | # Exercise 8 | The exercise wasn't that difficult tbh, Kody was really helpful and all you have to do in order to solve it is to create your `ToggleContext` with a `value` that contains the state `on` and the setter fn `toggle`. Passing down it to your components and you're good to go. 9 | 10 | Probably the most interesting part of all the exercise is that we automatically wrap all the `children` of the main `Toggle` component without force the user to add it via code: 11 | ```js 12 | function Toggle({children}) { 13 | const [on, setOn] = React.useState(false) 14 | const toggle = () => setOn(!on) 15 | 16 | const value = { on, toggle }; 17 | return {children} 18 | } 19 | ``` 20 | 21 | ### Extra Credit 1 22 | Beside the creation of a custom Hook that help us give a comprehensive error message to the user, we already done this in [[3. useContext simple Counter]] there has been the ability to give to our `Context` a better name that will help us debugging our application with the React Dev Tools: 23 | ```js 24 | const ToggleContext = React.createContext(); 25 | ToggleContext.displayName = 'ToggleContext'; 26 | ``` 27 | With this while debugging instead of reading `Context.Provider` we will be able to see `ToggleContext.Provider` 😉 -------------------------------------------------------------------------------- /4. Advanced React Patterns/4. Prop Collections and Getters.md: -------------------------------------------------------------------------------- 1 | This kind of approach help us to ship components that are easier to use for the end user. 2 | Generally our custom hooks or components have many properties that need to be set in order to make it work as expected. 3 | # Exercise 4 | In our exercise we have the `useToggle` [[custom Hook]] that helps us to set up our `Switch` component. instead to pass the state value and the fn that updates the state we build a new object `togglerProps` that is configured in a way that let's our user do not think about of the implementation of our custom hook. 5 | 6 | Basically instead of force our user to create a `Switch` component that looks like the following: 7 | ```jsx 8 | 9 | ``` 10 | We are able to make this a lot simpler letting the end user just spread all the properties that we have prepared in our custom hook: 11 | ```jsx 12 | 13 | ``` 14 | This is possible because in our `useToggle` custom hook we defined the `return` elements like so: 15 | ```js 16 | function useToggle() { 17 | const [on, setOn] = React.useState(false) 18 | const toggle = () => setOn(!on) 19 | const togglerProps = { 20 | 'aria-pressed': on, 21 | onClick: toggle 22 | } 23 | 24 | return {on, toggle, togglerProps} 25 | } 26 | ``` 27 | `useToggle` now is not only able to handle the state and to switch the `on` prop from `false` to `true`, but it can also handle the accessibility **and** the `onClick` event. 28 | 29 | In this way our user just have to extract those value with a simple [[destructuring]]: 30 | ```js 31 | const [on, togglerProps] = useToggle(); 32 | ``` 33 | ## Extra credit 34 | ### 1. 💯 prop getters 35 | This has been really fun to discover and from Kent's word it should be the **preferred approach** to pass props. 36 | 37 | The base idea is to create a function that will get called with an objects that contains the props that I want to apply. Use a function instead of a collection will help us handling the cases where user want to fire something during an event that we are already handling. The collection will override our event while the getter can fire all the functions we attach to it. 38 | 39 | This is how our end user could try to use our props based on our previous implementation: 40 | ```jsx 41 | 48 | ``` 49 | 50 | But this is no good because our user is overriding the `onClick` fn with his own. 51 | 52 | So the first thing we need to do is to create our `getTogglerProps` fn and return it from our custom hook: 53 | ```js 54 | function getTogglerProps(props){ 55 | return { 56 | 'aria-pressed': on, 57 | onClick: toggle, 58 | ...props, 59 | } 60 | } 61 | // return inside the useToggle custom hook 62 | return { 63 | on, 64 | toggle, 65 | getTogglerProps 66 | } 67 | ``` 68 | Even if seems to work in this way, if our end user will pass an `onClick` handler we will **override** the one that handles the toggle all together. We need to **compose** the `toggle` fn and the one that our end user is passing. 69 | 70 | ```js 71 | function getTogglerProps({onClick, ...props} = {}) { 72 | return { 73 | 'aria-pressed': on, 74 | onClick: () => { 75 | onClick?.(); 76 | toggle(); 77 | }, 78 | ...props, 79 | } 80 | } 81 | ``` 82 | And now if the user is passing a fn to handle the `onClick` evt we can fire both fns without any issues. 83 | 84 | A minor improvement is suggested in how we compose those fns. Instead to compose them manually any time we can create an utility fn, that can live in your `utility/` folder 😉 And use it to compose all the fns we need: 85 | ```js 86 | function callAll(...fns){ 87 | return (...args) => { 88 | fns.forEach( fn => { 89 | fn && fn(...args) 90 | }) 91 | } 92 | } 93 | ``` 94 | This fn takes any number of fns with any number of arguments and, if each fn exists, it will call them separately passing the arguments. 95 | > If you want to dig deeper in this kind of field and learn even more about composition I cannot advice enough the amazing course [Composing Closures and Callbacks in JavaScript](https://egghead.io/playlists/composing-closures-and-callbacks-in-javascript-1223) by the great John Lindquist. 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /4. Advanced React Patterns/5. State Reducer.md: -------------------------------------------------------------------------------- 1 | The base idea of this lesson is to implement an *inversion of control* where user of our component can pass, if they want, a new reducer to handle how we update the state of our component. 2 | 3 | This is trivial, or almost impossible, to do with a simple `useState` because in that case is the updater function that is in charge on how the state gets updated. 4 | 5 | Using `useReducer`, instead, allows us to give the ability to our end user to totally override how we update the state for special use cases. 6 | # Exercise 7 | The first part of the exercise is incredibly simple, all you have to do is to follow Kody's instructions and use all the code that is already written for you that is able to customize the behaviour of the component. 8 | 9 | All that we have to do is to transform the `useToggle` custom hook from this: 10 | ```js 11 | function useToggle({initialOn = false} = {}) { 12 | const {current: initialState} = React.useRef({on: initialOn}) 13 | const [state, dispatch] = React.useReducer(toggleReducer, initialState) 14 | ``` 15 | into this: 16 | ```js 17 | function useToggle({reducer = toggleReducer, initialOn = false} = {}) { 18 | const {current: initialState} = React.useRef({on: initialOn}) 19 | const [state, dispatch] = React.useReducer(reducer, initialState) 20 | ``` 21 | Basically now we pass `toogleReducer`, the reducer fn created in our component, as a default and we allow the user of the component to override the standard behaviour by passing it's own `reducer`. 22 | 23 | This is the default `toggleReducer`: 24 | ```js 25 | function toggleReducer(state, {type, initialState}) { 26 | switch (type) { 27 | case 'toggle': { 28 | return {on: !state.on} 29 | } 30 | case 'reset': { 31 | return initialState 32 | } 33 | default: { 34 | throw new Error(`Unsupported type: ${type}`) 35 | } 36 | } 37 | } 38 | ``` 39 | As you can see nothing fancy here. We deconstruct the `action` right inside of the parentesis and we apply our changes based on the `type`. 40 | 41 | Now when our user want's to customize how the `on` state gets changed he can do it by creating it's own `toggleStateReducer`: 42 | ```js 43 | function toggleStateReducer(state, action) { 44 | switch (action.type) { 45 | case 'toggle': { 46 | if (clickedTooMuch) { 47 | return {on: state.on} 48 | } 49 | return {on: !state.on} 50 | } 51 | case 'reset': { 52 | return {on: false} 53 | } 54 | default: { 55 | throw new Error(`Unsupported type: ${action.type}`) 56 | } 57 | } 58 | } 59 | 60 | const {on, getTogglerProps, getResetterProps} = useToggle({ 61 | reducer: toggleStateReducer, 62 | }) 63 | ``` 64 | 65 | ## Extra Credit 66 | ### 💯 default state reducer 67 | The solution to this exercise is to just export both the `useToggle` custom hook **and** the reducer fn `toggleReducer` because in this case our user could import the reducer and not worry to rewrite all of it. 68 | 69 | Basically instead to recreate the entire reducer: 70 | ```js 71 | function toggleStateReducer(state, action) { 72 | switch (action.type) { 73 | case 'toggle': { 74 | if (clickedTooMuch) { // <- We care only about this 75 | return {on: state.on}0 76 | } 77 | return {on: !state.on} 78 | } 79 | case 'reset': { 80 | return {on: false} 81 | } 82 | default: { 83 | throw new Error(`Unsupported type: ${action.type}`) 84 | } 85 | } 86 | } 87 | 88 | const {on, getTogglerProps, getResetterProps} = useToggle({ 89 | reducer: toggleStateReducer, 90 | }) 91 | ``` 92 | 93 | We can just check for the case we want to override the standard behaviour and let `toggleReducer` handle the rest: 94 | ```js 95 | function toggleStateReducer(state, action) { 96 | if( action.type === 'toggle' && clickedTooMuch ){ 97 | return { on: state.on } 98 | } 99 | 100 | return toggleReducer(state, action); 101 | } 102 | ``` 103 | As you can see now, we only override the standard behaviour if `action.type` is `toggle` **and** we `clickedTooMuch`. 104 | ### 💯 state reducer action types 105 | This extra credit asks us to simply create an `actionTypes` object that contains all of our types, doing so will help our end user not making typos and the IDE is able to give autocomplete suggestions. 106 | 107 | Basically all we have to do is create: 108 | ```js 109 | const actionTypes = { 110 | toggle: 'toggle', 111 | reset: 'reset', 112 | } 113 | ``` 114 | 115 | And everywhere we have `'toggle'` change it with `actionTypes.toggle` and where we have `'reset'` change it with `actionTypes.reset`. 116 | > Remember that you have to **export** `actionTypes` from where you define it because, if not, your user will not be able to use it 😉 -------------------------------------------------------------------------------- /4. Advanced React Patterns/6. Control Props.md: -------------------------------------------------------------------------------- 1 | This concept comes right from the [controlled components](https://reactjs.org/docs/forms.html#controlled-components) from React. Basically when we use an `input`, `textarea` or a `select` element within React the library gives us the ability to handle the current state (the value) of that specific element. 2 | 3 | Beside the state itself we are also able to **listen** for the changes, generally with the `onChange` prop, and React will pass to us all the information that thinks are important to update our state. 4 | 5 | # Exercise 6 | The first step of this lesson is to make the controlled components working in sync, this means that to make it work we have to pass the value for the state as a prop in our component. 7 | 8 | At the same time, since we want to make our component as flexible as possible, we have to make the component controlled **only if** the end user is passing the `on` prop. 9 | 10 | In order to do so we need to change a bid the declaration of `useToggle` and let it accept an `onChage` and `on` attribute as well. 11 | 12 | ```js 13 | function useToggle({ 14 | initialOn = false, 15 | reducer = toggleReducer, 16 | onChange, 17 | on: controlledOn, // Making alias 'cuz on is used later 18 | } = {}) { 19 | ``` 20 | At this point the user *could* pass the `on` attribute, so we need to check if it is present and decide if we want to handle state internally (with [[React.useState()]]) or if we want to let the end user handle it with the `onChange` prop. 21 | 22 | To do so we need to change our component from this: 23 | ```js 24 | const { on } = state 25 | ``` 26 | Into this: 27 | ```js 28 | const onIsControlled = controlledOn != null; 29 | const on = onIsControlled ? controlledOn : state.on; 30 | ``` 31 | Basically we check if end user is passing `on` with `onIsControlled` and based on that we chose to set our internal state with `controlledOn` (passed as prop) or to get it straight from our `state`. 32 | 33 | Now that our state value is handled we need to decide how we will manage the change of it. If state was internal we just had to call our `dispatch` (we are using [[React.useReducer()]] here) and that was it but we want to allow the end user to pass a function that will handle the update of the state externally with `onChange`. 34 | 35 | Checking how our end user is using our `` we see that he made the `handleToggleChange` fn: 36 | ```jsx 37 | function handleToggleChange(state, action) { 38 | if (action.type === actionTypes.toggle && timesClicked > 4) { 39 | return 40 | } 41 | setBothOn(state.on) 42 | setTimesClicked(c => c + 1) 43 | } 44 | 45 | 46 | ``` 47 | `setBothOn` and `setTimesClicked` are just the updater fns for our `useState` hook but as you can see the `handleToggleChange` is expecting a `state` and an `action` so we need to provide them when we call `onChange` from our `useToggle` custom hook. 48 | 49 | So, again, we need to handle the situation where our component is controlled or not. In order to do so we need to change how we dispatch our changes with a new fn `dispatchWithOnChange`: 50 | ```js 51 | function dispatchWithOnChange(action){ 52 | if( !onIsControlled ){ 53 | dispatch( action ); 54 | } 55 | onChange?.( reducer( {...state, on}, action ), action ) 56 | } 57 | 58 | // We also changed the standard dispatch to toggle and reset 59 | const toggle = () => dispatchWithOnChange({type: actionTypes.toggle}) 60 | const reset = () => dispatchWithOnChange({type: actionTypes.reset, initialState}) 61 | ``` 62 | Now we `dispatch` with `useReducer` only if our component is not controlled because if it is we call `onChange` if it is defined. 63 | 64 | Since our `onChange` is expecting to have the updated state to work with we can pass it using our internal `reducer` that is able to update its value and sending it back to the `handleToggleChange` that it is attached to it. 65 | 66 | ## Extra Credit 67 | ### 1. 💯 add read only warning 68 | For standard controlled `input` React throw a warning if we provide a `value` prop but no `onChange`, we want to implement the same behavior in our component when a user is providing an `on` prop but no way to update its value. 69 | 70 | To get this result the first thing we can do is introduce a new variable `hasOnChange`: 71 | ```js 72 | const hasOnChange = Boolean(onChange); 73 | ``` 74 | In this snippet we use `Boolean` but in this case we could get the same result with `!!onChange`. 75 | 76 | Now that we know if our component has the `onChange` prop passed to it we can just [[React.useEffect()|useEffect]] it and show an error: 77 | ```js 78 | React.useEffect(() => { 79 | if(!hasOnChange && onIsControlled && !readOnly){ 80 | console.error('You provided an "on" prop but no "onChange"') 81 | } 82 | }, [hasOnChange, onIsControlled, readOnly]) 83 | ``` 84 | Here the dependency array of `useEffect` is populated with the new prop `readOnly` that we add to our API to give the ability to our end user to say *"I just want to use this component with `on` but without `onChange`."* 85 | 86 | In the video Kent show us a useful [warning](https://www.npmjs.com/package/warning) NPM package that is used in React to handle this kind of scenario. The API is pretty simple and allows you to add a message in case the first parameter that you pass is `false`. 87 | ```js 88 | import warning from 'warning'; 89 | 90 | warning( 91 | fireIsFalse, 92 | 'You read me if fireIsFalse is, actually, false.' 93 | ) 94 | ``` 95 | ### 2. 💯 add a controlled state warning 96 | Now that we are able to handle the presence of `onChange` is a nice addition to handle the switch of a component between controlled and uncontrolled. 97 | 98 | The first thing we take care of is the one where we switch from uncontrolled to controlled. We can do this initializing the `on` state to `undefined` like so: 99 | ```js 100 | const [bothOn, setBothOn] = React.useState() 101 | ``` 102 | Later in our `Toggle` component we set the `on` value with `onChange` and this will transform it from an uncontrolled to a controlled component. 103 | 104 | To handle this we have to `useEffect` and track if the component was already controlled with [[React.useRef()|useRef]]: 105 | ```js 106 | const { current: onWasControlled } = React.useRef(onIsControlled); 107 | ``` 108 | The ref `onWasControlled` now tracks if previously our component was controlled or not so we can add it to our `useEffect` an fire a `warning` in case the condition is false: 109 | ```js 110 | React.useEffect( () => { 111 | warning( 112 | !(onIsControlled && !onWasControlled), 113 | 'Changing from uncontrolled to controlled' 114 | ); 115 | }, [onIsControlled, onWasControlled]) 116 | ``` 117 | Now we handle the first part of the exercise, we know if a component went from uncontrolled to controlled, but we want to know even if the opposite case will happen so we need to change a bit our code for testing purposes setting our `setBothOn` fn to `undefined` when user tries to change the toggle by clicking on it: 118 | ```js 119 | function App() { 120 | const [bothOn, setBothOn] = React.useState(false) // <- Back to normal 121 | const [timesClicked, setTimesClicked] = React.useState(0) 122 | 123 | function handleToggleChange(state, action) { 124 | if (action.type === actionTypes.toggle && timesClicked > 4) { 125 | return 126 | } 127 | setBothOn() // <- We set to undefined when onChage fires this 128 | setTimesClicked(c => c + 1) 129 | } 130 | ``` 131 | Now that we set up our test we're ready to handle this situation as well and see if we get our warning: 132 | ```js 133 | React.useEffect( () => { 134 | warning( 135 | !(onIsControlled && !onWasControlled), 136 | 'Changing from uncontrolled to controlled' 137 | ); 138 | warning( 139 | !(!onIsControlled && onWasControlled), 140 | 'Changing from controlled to uncontrolled' 141 | ); 142 | }, [onIsControlled, onWasControlled]) 143 | ``` 144 | By adding the second `warning` we are able to display a message even if the component will pass from controlled to uncontrolled. 145 | 146 | ### 3. 💯 extract warnings to a custom hook 147 | This is an usual pattern, when we see something that could be useful somewhere else is better to make a custom Hook out of it so we get the logic we created earlier and create `useControlledSwitchWarning` and `useOnChangeReadOnlyWarning`. 148 | 149 | ```js 150 | function useControlledSwitchWarning( controlPropValue, controlPropName, componentName ){ 151 | const isControlled = controlPropValue != null; 152 | const { current: wasControlled } = React.useRef(isControlled); 153 | React.useEffect( () => { 154 | warning( 155 | !(isControlled && !wasControlled), 156 | `Changing \`${componentName}\` from uncontrolled to controlled. Check \`${controlPropName}\` prop.` 157 | ); 158 | warning( 159 | !(!isControlled && wasControlled), 160 | `Changing \`${componentName}\` from controlled to uncontrolled. Check \`${controlPropName}\` prop.` 161 | ); 162 | }, [isControlled, wasControlled, componentName, controlPropName]) 163 | } 164 | ``` 165 | As you can see this is pretty straightforward, we changed some variable names just to make the logic more general and we now accept some arguments while previously we used 'em within the component. 166 | 167 | Now the `useOnChangeReadOnlyWarning` seems more complicated but only because we use many variables inside our string, otherwise the logic is pretty simple. 168 | ``` 169 | function useOnChangeReadOnlyWarning( 170 | controlPropValue, 171 | controlPropName, 172 | componentName, 173 | hasOnChange, 174 | readOnly, 175 | readOnlyProp, 176 | initialValueProp, 177 | onChangeProp, 178 | ) { 179 | const isControlled = controlPropValue != null 180 | React.useEffect(() => { 181 | warning( 182 | !(!hasOnChange && isControlled && !readOnly), 183 | `A \`${controlPropName}\` prop was provided to \`${componentName}\` without an \`${onChangeProp}\` handler. This will result in a read-only \`${controlPropName}\` value. If you want it to be mutable, use \`${initialValueProp}\`. Otherwise, set either \`${onChangeProp}\` or \`${readOnlyProp}\`.`, 184 | ) 185 | }, [ 186 | componentName, 187 | controlPropName, 188 | isControlled, 189 | hasOnChange, 190 | readOnly, 191 | onChangeProp, 192 | initialValueProp, 193 | readOnlyProp, 194 | ]) 195 | } 196 | ``` 197 | ### 4. 💯 don’t warn in production 198 | One of the best thing is to warn just the developer and not the users of the app! This is simple as add a condition: 199 | ```js 200 | if (process.env.NODE_ENV !== 'production') { 201 | // eslint-disable-next-line react-hooks/rules-of-hooks 202 | useControlledSwitchWarning(controlledOn, 'on', 'useToggle') 203 | // eslint-disable-next-line react-hooks/rules-of-hooks 204 | useOnChangeReadOnlyWarning( 205 | controlledOn, 206 | 'on', 207 | 'useToggle', 208 | Boolean(onChange), 209 | readOnly, 210 | 'readOnly', 211 | 'initialOn', 212 | 'onChange', 213 | ) 214 | } 215 | ``` 216 | In here we use some comments for ESLint that will warn use that we are braking a rule of the hooks, do not conditionally fire them, but in this case we can just ignore because `process.env.NODE_ENV` will not change for the entire lifecycle of our application. -------------------------------------------------------------------------------- /8. Build an Epic React App/1. Render a React App.md: -------------------------------------------------------------------------------- 1 | The exercise goes pretty straight, [[ReactDOM.render()|render]] a React app is always the same. 2 | 3 | We take a component and we render it via `ReactDOM.render()`, just to show you some code this is the summary: 4 | ```js 5 | // Internal deps, needed to make it work 6 | import React from 'react' 7 | import ReactDOM from 'react-dom' 8 | 9 | // Define the component 10 | function App() { 11 | return ( 12 | // This is the JSX I want to return 13 | // REMEMBER: JSX in just a series of React.createElement() 14 | ) 15 | } 16 | 17 | // Now I render the component inside #root that I find in the index.html file 18 | ReactDOM.render(, document.getElementById('root')) 19 | ``` 20 | This is the base, exercise required us to use some already made components to render a simple login page. 21 | 22 | Things got more interesting while working on extra credits. 23 | 24 | ### Extra credit 1 - add a modal on click 25 | The modal component that has been suggested is the `Dialog` from the [Reach UI](https://github.com/reach/reach-ui/tree/main/packages/dialog) library. 26 | All we need to do is to use the `Dialog` component and decide if we want to show/hide it by passing to it a `boolean` as `isOpen` prop. Basic usage: 27 | ```js 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | ``` 36 | The great idea is to treat the component state like a state machine, based on the value of the state we decide which modal box to show. As suggested the possible states are: 37 | * `none` - default state that will hide any modal 38 | * `login` - modal popup for the login click 39 | * `register` - modal popup for the register click 40 | 41 | At this point we need just one state and we can change its value based on click: 42 | ```js 43 | function App() { 44 | const [ openModal, setOpenModal] = React.useState('none'); 45 | 46 | return ( 47 | <> 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 |

    Content to show

    56 |
    57 | 58 | 59 |

    Content to show

    60 |
    61 | 62 | ) 63 | } 64 | ``` 65 | The concept is really simple but most of the time we get wrapped in complex ideas 😊 66 | ### Extra credit 2 - create a LoginForm 67 | Here's mostly a simple example on how to make a new component with minor event handling functions. I did it with the following code but the tricky part I found is that I didn't know where to `preventDefault` the load of the page. 68 | ```js 69 | function LoginForm({onSubmit, buttonText}) { 70 | const [ username, setUsername ] = React.useState(''); 71 | const [ password, setPassword ] = React.useState(''); 72 | 73 | return ( 74 |
    75 | 76 | setUsername(e.target.value) } /> 77 | 78 | setPassword(e.target.value) } /> 79 | 80 |
    81 | ) 82 | } 83 | ``` 84 | Solution for this was simple, I just need to create an `handleSubmit` to handle in it the event obj (that let's me call the `preventDefault`) and call the `onSubmit` fn passed via props inside it. 85 | 86 | How to translate it into code: 87 | ```js 88 | function handleSubmit(e){ 89 | e.preventDefault(); 90 | onSubmit( [username, password] ); // You can pass what you want 91 | } 92 | 93 | return ( 94 |
    95 | ``` 96 | 97 | Here's the Kent's approach in solving this, mayor differences is that he's **not using state at all** in the component, uses the event object (that we already have) to take values from it and this actually seems to be the best approach for what we need to realize. 98 | 99 | We use plain JavaScript for something simple. 100 | ```js 101 | function LoginForm({onSubmit, buttonText}) { 102 | function handleSubmit(event) { 103 | event.preventDefault() 104 | // Destructs current form values from elements 105 | const {username, password} = event.target.elements 106 | 107 | // Send new values via fn passed with props 108 | onSubmit({ 109 | username: username.value, 110 | password: password.value, 111 | }) 112 | } 113 | 114 | return ( 115 | 116 |
    117 | 118 | 119 |
    120 |
    121 | 122 | 123 |
    124 |
    125 | 126 |
    127 |
    128 | ) 129 | } 130 | ``` 131 | -------------------------------------------------------------------------------- /8. Build an Epic React App/2. Style React Components.md: -------------------------------------------------------------------------------- 1 | We can style a component in many ways, if you use [CRA](https://create-react-app.dev/) you're free to `import` your stylesheets (even [[Sass]]) files right inside the component that uses it but there's a new movement that is getting used more and more and is [[**CSS-in-JS**]]. 2 | 3 | ## Making a styled component with emotion 4 | 5 | The implementation we will use is [emotion](https://emotion.sh) and gives us two ways to style our components while we are creating them: 6 | ```js 7 | // Remember to import 8 | import styled from '@emotion/styled' 9 | 10 | // Template string 11 | const Button = styled.button` 12 | color: turquoise; 13 | ` 14 | 15 | // Object 16 | const Button = styled.button({ 17 | color: 'turquoise', 18 | }) 19 | ``` 20 | Both example will produce `` when we will use the ` 141 | 142 | 143 | 144 | 145 | { isError ? ( 146 |
    147 |

    There was an error:

    148 |
    {error.message}
    149 |
    150 | ) : null } 151 | 152 | { /* Same as before for success... */ } 153 |
    154 | ) 155 | ``` 156 | Now we have all the code that let us handle the error when we face one, just remember to import all the colors that are stored in `styles/colors.js` as follows: 157 | ```js 158 | import * as colors from './styles/colors'; 159 | ``` 160 | ## Extra Credit 2: use the `useAsync` hook 161 | This exercise is pretty simple, all that is ask from us is to change our code in order to use the `useAsync` [[custom Hook]]. Thanks to this our code looks a lot more simpler and we have all the data that we need to handle our UI right from this Hook: 162 | ```js 163 | function DiscoverBooksScreen() { 164 | // The new custom Hook handles all the cases and previous states 165 | const {data, error, run, isLoading, isError, isSuccess} = useAsync() 166 | const [query, setQuery] = React.useState() 167 | const [queried, setQueried] = React.useState(false) 168 | 169 | React.useEffect(() => { 170 | if (!queried) { 171 | return 172 | } 173 | // We just need to fire the function that will return the promise 174 | run(client(`books?query=${encodeURIComponent(query)}`)) 175 | }, [query, queried, run]) 176 | /** Everything will stay the same */ 177 | ``` 178 | The code inside the `useAsync` custom Hook is pretty complex and understand it is out of the scope of this lesson but if you feel like you need to dig deeper you always can [right inside the file](https://github.com/AndreaBarghigiani/bookshelf/blob/exercises/03-data-fetching/src/utils/hooks.js#L21-L82). -------------------------------------------------------------------------------- /8. Build an Epic React App/4. Authentication.md: -------------------------------------------------------------------------------- 1 | Authenticate a user is a pretty complex field filled with security concerns, for this reason most of the time the advice is to use an external service that is specialized to do it. 2 | 3 | Some of my personal favorite are [Auth0](https://auth0.com) or the recent [Supabase](https://supabase.com). 4 | 5 | Beside the external services you can use pretty much the logic will be always be the same: 6 | 1. you connect your app with the auth service (internal or external does not matter) 7 | 2. the auth service will expose some methods for login, register, logout and get the current token (this will check if user is already logged in or not) 8 | 3. based on the response you'll be able to understand if the user is logged in or not and render the appropriate part of the application 9 | 10 | In general terms you want to build your application with a public version (the one seen from unauthenticated users) and a private one that will be accessible only to users that are able to login. 11 | 12 | In React you want to store the information about the user in [[React.useState()|useState]] so you will be able to use this data for the entire lifecycle of your app. 13 | # Exercise 14 | The first part of the exercise is pretty basic, you have to understand which props `AuthenticatedApp` and `UnauthenticatedApp` are looking for in order to pass to them the right information. 15 | 16 | Let's start analyzing the `UnauthenticatedApp` first since will be the first that we see: 17 | ```js 18 | function UnauthenticatedApp({login, register}) { 19 | ``` 20 | In this case all that our component is looking for are the functions that it need to use in order to login or register the user that wants to get access to the app. 21 | 22 | Our Auth Provider already give use those and we just need to handle them in an `async` way in order to populate also the state of our `App` component: 23 | ```jsx 24 | // Hidden previous not related `import` 25 | import * as auth from 'auth-provider' 26 | 27 | function App() { 28 | const [ user, setUser ] = React.useState(null); 29 | const login = form => auth.login(form).then(u => setUser(u)) 30 | const register = form => auth.register(form).then(u => setUser(u)) 31 | 32 | return 33 | } 34 | ``` 35 | As already described here I am using `useState` to store the information about the user once we get the response back from the server with `then`. 36 | 37 | Beside it I am creating the `login` and `register` fn that leverage the `auth.login` and `auth.register` method exposed from the Auth Provider. You **do not need to know the internals**, your Auth Provider is handling this part, all that is useful to you to know is that `auth.login` and `auth.register` are async fns and that you can use the returned value to set up your local state. 38 | 39 | Now that `UnauthenticatedApp` is connected to the proper fns, the ones that will be fired when user clicks on the specific button, it's time to show the `AuthenticatedApp` in case our `user` is different from `null`, this happen when `login` or `register` complete their request and pass the response to `setUser`. 40 | 41 | ```jsx 42 | function App() { 43 | // Hidden previous part... 44 | 45 | return user ? ( 46 | 47 | ) : ( 48 | 49 | ); 50 | } 51 | ``` 52 | 53 | But if you try to login/register into your app you get an error. That's because `AuthenticatedApp` relies on some props **that we are not passing**. 54 | 55 | Checking the source code we discover that `AuthenticatedApp` is looking for a `user` and a `logout` prop. 56 | ```js 57 | function AuthenticatedApp({user, logout}) { 58 | ``` 59 | Actually this make sense because, once rendered, we need to allow user to logout but more importantly we need to access the information stored in `user` in order to give access and personalize the App based on his capabilities. 60 | 61 | So now we need to leverage a new method from our Auth Provider in order to allow our app to call a fn that will logout our user. This method is called `auth.logout`. 62 | 63 | ```jsx 64 | 65 | function App() { 66 | //Hidded prev part... 67 | const logout = () => { 68 | auth.logout(); 69 | setUser(null); 70 | } 71 | 72 | return user ? ( 73 | 74 | ) : ( 75 | 76 | ); 77 | } 78 | ``` 79 | An interesting part of our `logout` fn is that on top of calling `auth.logout` we also set the user to `null`. 80 | ## Extra Credit 81 | ### 1. 💯 Load the user's data on page load 82 | Here we need to check if the user is already logged in when the page is loaded, in order to do this we need to check if `token` is present. We can leverage the `getToken` method in `auth-provider` and use it in an `async` fn defined outside of our component logic. 83 | 84 | At the same time we want to return our user with the needed information if the `token` is present, let's see first the structure of the `getUser` fn 85 | 86 | ```js 87 | async function getUser(){ 88 | let user = null 89 | 90 | const token = await auth.getToken(); 91 | 92 | if(token){ 93 | const data = await client('me', {token}); 94 | user = data.user 95 | } 96 | 97 | return user; 98 | } 99 | ``` 100 | 101 | If we have a `token` it's time to ask for the information of our user, here we can do with the `client` fn that is defined in `utils/api-client` and originally is defined like so: 102 | ```js 103 | function client(endpoint, customConfig = {}) { 104 | const config = { 105 | method: 'GET', 106 | ...customConfig, 107 | } 108 | // Return code... 109 | ``` 110 | Since now we pass the `token` to it we need to destructure it from the `customConfig` obj in order to use it inside our `Authorization` header: 111 | ```js 112 | function client(endpoint, {token, ...customConfig} = {}) { 113 | const config = { 114 | method: 'GET', 115 | headers:{ 116 | Authorization: token ? `Bearer ${token}` : undefined, 117 | }, 118 | ...customConfig, 119 | } 120 | // Return code... 121 | ``` 122 | This is not enough though because in case our user is passing a custom `headers` to the request it will get overridden by the logic we wrote to pass the `token`. In order to allow the user to override the `headers` we need a last change in the `client` fn: 123 | ```js 124 | function client(endpoint, {token, headers: customHeaders, ...customConfig} = {}) { 125 | const config = { 126 | method: 'GET', 127 | headers:{ 128 | Authorization: token ? `Bearer ${token}` : undefined, 129 | ...customHeaders, 130 | }, 131 | ...customConfig, 132 | } 133 | // Return code... 134 | ``` 135 | Here we go, now the `client` fn is able to handle the `token`, can set the `Authorization` header **and** allows our end user to set up a custom `headers`. 136 | 137 | Now that we have the ability to know if user is logged in or not it's time to make use of the `getUser` fn inside our component, to do so we handle the logic inside `useEffect` hook the first time our component gets rendered: 138 | ```jsx 139 | function App() { 140 | const [user, setUser] = React.useState(null) 141 | 142 | React.useEffect(() => { 143 | getUser().then( u => setUser(u)) 144 | }, []) 145 | ``` 146 | And now, even if user refresh the page, he is able to *auto-login* inside our app! 147 | ### 2. 💯 Use `useAsync` 148 | Inside the `utils/hooks.js` we have a `useAsync` custom hook ready to handle all async calls and giving us some *'ready to use'* state to handle the loading of our app. 149 | 150 | We also have a `FullPageSpinner` component in `components/lib` that we can use while loading our page. 151 | 152 | So the first thing we can do is to import both of them, I also include the `colors` styles to change the color of the error message in case we have one. 153 | ```js 154 | // other imports 155 | import * as colors from './styles/colors' 156 | import {useAsync} from './utils/hooks' 157 | import {FullPageSpinner} from './components/lib' 158 | ``` 159 | Now that we have all that we need to improve our app it's time to make it to work. The first thing that we will do is to stop to handle the `user` state inside our component to delegate this part to the `useAsync` custom hook: 160 | ```js 161 | const { 162 | data: user, 163 | error, 164 | isLoading, 165 | isIdle, 166 | isError, 167 | isSuccess, 168 | run, 169 | setData 170 | } = useAsync(); 171 | ``` 172 | We renamed the standard `data` to `user` to easily work with it and destructured all the other relevant values `useAsync` are returning. Now that we have our `run` fn we can make use of it inside our `useEffect`: 173 | ```js 174 | React.useEffect(() => { 175 | run(getUser()) 176 | }, [run]) 177 | ``` 178 | `run` will take care of setting up the `data` for use so no need to handle it in a `then` call. 179 | 180 | Next we want to update our `login`, `register` and `logout` fns to use `useAsync`: 181 | ```js 182 | const login = form => auth.login(form).then(user => setData(user)) 183 | const register = form => auth.register(form).then(user => setData(user)) 184 | const logout = () => { 185 | auth.logout() 186 | setData(null) 187 | } 188 | ``` 189 | If you save everything works as before, we see the login/register screen and if the user is logged in we will render the `AuthenticatedApp` component with the right information. 190 | 191 | But we did all of the above because **we wanted to avoid** this behavior! 192 | 193 | So the first thing we need to do is to check if `isLoading` or `isIdle` are `true` and show `FullPageSpinner` in case they are: 194 | ```js 195 | if( isLoading || isIdle ){ 196 | return 197 | } 198 | ``` 199 | 200 | Since we have `isError` and `error` in case something goes bananas, we can make use of those as well: 201 | ```js 202 | if( isError ){ 203 | return ( 204 |
    214 |

    There's a problem. Try refresh the app.

    215 |
    {error.message}
    216 |
    217 | ) 218 | } 219 | ``` 220 | Now that we handle the edge cases we can show the appropriate component if `isSuccess` is `true`: 221 | ```js 222 | if(isSuccess){ 223 | return user ? ( 224 | 225 | ) : ( 226 | 227 | ) 228 | } 229 | ``` 230 | Here you go, now our app shows a spinner while it is still figuring out if the user is logged in or not. 231 | ### 3. 💯 automatically logout on 401 232 | In this exercise you have to work in `api-client` file in order to handle the `response` of your query to `window.fetch`. So the first thing to do is to check if the status is `401`: 233 | ```js 234 | function client( 235 | endpoint, 236 | {token, headers: customHeaders, ...customConfig} = {} 237 | ) { 238 | // Config 239 | 240 | return window.fetch(`${apiURL}/${endpoint}`, config) 241 | .then(async response => { 242 | if( response.status === 401 ){ 243 | // Handle unauthorize 244 | } 245 | 246 | // Standard response 247 | ``` 248 | Now that we are able to handle the case our user is not authorized to do something we can bring in the `logout` fn: 249 | ```js 250 | import * as auth from 'auth-provider' 251 | ``` 252 | With the `auth` obj, that contains `logout`, we can call it from withing our logic: 253 | ```js 254 | function client( 255 | endpoint, 256 | {token, headers: customHeaders, ...customConfig} = {} 257 | ) { 258 | // Config 259 | 260 | return window.fetch(`${apiURL}/${endpoint}`, config) 261 | .then(async response => { 262 | if( response.status === 401 ){ 263 | await auth.logout(); 264 | } 265 | 266 | // Standard response 267 | ``` 268 | But logout the user could be not enough so we will force a page refresh **and** `reject` the Promise just in case: 269 | ```js 270 | function client( 271 | endpoint, 272 | {token, headers: customHeaders, ...customConfig} = {} 273 | ) { 274 | // Config 275 | 276 | return window.fetch(`${apiURL}/${endpoint}`, config) 277 | .then(async response => { 278 | if( response.status === 401 ){ 279 | await auth.logout(); 280 | // Trigger a full page refresh to update our app 281 | window.location.assign( window.location ); 282 | // Reject just in case 283 | return Promise.reject({message: 'Prease re-authenticate'}) 284 | } 285 | 286 | // Standard response 287 | ``` 288 | ### 4. 💯 Support posting data 289 | Since we have. all the client code already set up making our calls ready to send data as well is pretty easy. Before to see how to send data with code we need to understand what makes this kind of calls different: 290 | * we want to send new values so we need to pass a `data` obj 291 | * when the user sends `data` the method that we use is not `GET` but it is `POST` 292 | * the `config` object needs also to have a `body` property where we store the serialized data that user is sending 293 | * we also need to add a new `Content-Type` header to specify the kind of data we are sending 294 | 295 | To make all this edits we have to change how our `client` fn is declared so our end user can pass this kind of information. To better see how we change this fn I'll follow the steps written above to make easy to understand what and where we apply those. 296 | 297 | **Pass the `data`** 298 | ```js 299 | function client( 300 | endpoint, 301 | {data, token, headers: customHeaders, ...customConfig} = {} 302 | ) { 303 | ``` 304 | The only change we made here is to destructure the `data` from the option obj that we created to customize the `client` fn. 305 | 306 | **If `data` id present change `GET` to `POST`** 307 | ```js 308 | function client( 309 | ... 310 | ) { 311 | const config = { 312 | method: data ? 'POST' : 'GET', 313 | ``` 314 | **Pass a `body` to the `config` obj of our `fetch`** 315 | ```js 316 | function client( 317 | ... 318 | ) { 319 | const config = { 320 | ... 321 | body: data ? JSON.stringify(data) : undefined, 322 | ``` 323 | **Set the `Content-Type` header** 324 | ```js 325 | function client( 326 | ... 327 | ) { 328 | const config = { 329 | ... 330 | headers:{ 331 | Authorization: token ? `Bearer ${token}` : undefined, 332 | 'Content-Type': data ? 'application/json' : undefined, 333 | ...customHeaders, 334 | }, 335 | ``` 336 | One important thing to notice about this approach is that user is still able to override some part of our logic without the need to write all of it. -------------------------------------------------------------------------------- /8. Build an Epic React App/5. Routing.md: -------------------------------------------------------------------------------- 1 | Routing is an important part of every application since allow us to take the *'address'* (the URL) of a resource and share it with anyone. 2 | 3 | In a normal website, build with HTML, CSS and other Server-Side technologies most of the time the routes are defined with the folder structure but when you work with [[SPA]], like in React, you need to have some sort of implementation that will allow you to create routes in the client. 4 | 5 | In this exercise we use the *de-facto* library that mostly anyone is using inside a React app: [React Router](https://reacttraining.com/react-router). 6 | 7 | The logic of this is pretty simple and powerful at the same time. 8 | 9 | The first thing that you need to have to make it work is a `Router`, a component that will wrap the application where we want to handle the routing. So the first thing we need to do is to wrap the entire app with the imported `Router`: 10 | ```js 11 | // Other imports 12 | import { Router } from 'react-router-dom' 13 | 14 | function App(){ 15 | return ( 16 |
    17 |

    Your app

    18 |
    19 | ) 20 | } 21 | 22 | function AppWithRouter() { 23 | return ( 24 | 25 | 26 | 27 | ) 28 | } 29 | 30 | ReactDOM.render(, document.getElementById('app')) 31 | ``` 32 | 33 | After we have put the `Router` in place we can start declaring the routes that we want to handle and tell to React Router which component we want to render in case of a `Route` match. Simply put we need to create something like the following inside our wrapped component: 34 | ```js 35 | // Other imports 36 | import { Router } from 'react-router-dom' 37 | 38 | function App(){ 39 | return ( 40 |
    41 |

    Your app

    42 | 43 | } /> 44 | } /> 45 | } /> 46 | } /> 47 | 48 |
    49 | ) 50 | } 51 | 52 | function AppWithRouter() { 53 | return ( 54 | 55 | 56 | 57 | ) 58 | } 59 | 60 | ReactDOM.render(, document.getElementById('app')) 61 | ``` 62 | We wrap all our `Route` components inside a `Routes` wrapper, this allow React Router to do it's magic. 63 | 64 | Each `Route` has two important props: 65 | * `path` - this identifies the relative path that we need to match 66 | * `element` - this will be the component that our app will render in care the `path` is matched 67 | 68 | > As you can see `path` can be build even in a special way, passing `:` means that we want to use a variable and this makes it a dynamic route. We'll discover more the use of it in the exercise part. 69 | 70 | Now that you defined your routes it's time **to reach them**. A standard `a` does not make any sense here because you're not routing files and the browser will go nuts. The solution is to use the `Link` component that React Router is providing to you. Once imported in your component, generally in your `Nav`, you're user will be able to see the component that you specified in the `element` prop of `Route`. 71 | ```js 72 | import { Link } from 'react-router-dom' 73 | 74 | function Nav(){ 75 | return Go to home 76 | } 77 | ``` 78 | > Please not that in a `Link` you have to use the `to` prop instead of the `href` attribute of the standard `a` element. 79 | 80 | # Exercise 81 | So our focus in this lesson is to implement the routing inside our application. We just need to wrap the `AuthenticatedApp` component because we do not care to add the routing to the not authenticated part of our app since it will have no routes. 82 | 83 | Open your `app.js` and change it like the following: 84 | ```js 85 | // Other imports... 86 | import { BrowserRouter as Router } from 'react-router-dom' 87 | 88 | function App(){ 89 | // Other code... 90 | if (isSuccess) { 91 | const props = {user, login, register, logout} 92 | return user ? ( 93 | 94 | 95 | 96 | ) : ( 97 | 98 | ) 99 | } 100 | } 101 | ``` 102 | > Remember we are using `useAsync` from previous lesson that provide as, among others, the `isSuccess` boolean. 103 | 104 | Now that we set up the `Router` it's time to define the `Routes` for our `AuthenticatedApp`: 105 | ```js 106 | // Other imports... 107 | import { Routes, Route, Link } from 'react-router-dom' 108 | import { DiscoverBooksScreen } from './screens/discover' 109 | import { BookScreen } from './screens/book' 110 | import { NotFoundScreen } from './screens/not-found' 111 | 112 | function AuthenticatedApp( { user, logout } ){ 113 | <> 114 | // Other code... 115 |
    116 | 117 |
    118 | 119 | } 120 | 121 | function AppRoutes({user}){ 122 | return ( 123 | 124 | } /> 125 | } /> 126 | } /> 127 | 128 | ) 129 | } 130 | ``` 131 | I've tried my best to simplify the code as much as possible, if you find it confusing have a look at the [source file](https://github.com/kentcdodds/bookshelf/blob/exercises/05-routing/src/authenticated-app.final.js). 132 | 133 | To better handle the routes we create a new `AppRoutes` component that we define inside the same file. As you can see we also pass to it the `user` data so we can forward them inside the component that will use it based on the `path` that the user is browsing to. For example `DiscoverBooksScreen` and `BookScreen` use it, but not `NotFoundScreen`. 134 | 135 | The `AuthenticatedApp` also uses the `Nav` component that I have hidden from previous code block. It usage is pretty simple, we create a `nav` and insert a `NavLink` that renders a `Link` that points to the `/discover` path. 136 | 137 | Now that we have our basic routes it's time to start to work on the simplest component, the `NotFoundScreen`. In this one, that mostly represent a 404 page, the only thing that we have to do is to import a `Link` that will point to the `/discover` route that is behaving like the home of our app: 138 | ```js 139 | // Other imports... 140 | import { Link } from '../components/lib' 141 | 142 | function NotFoundScreen() { 143 | return ( 144 |
    145 | Sorry... nothing here. 146 | Go home 147 |
    148 | ) 149 | } 150 | 151 | export {NotFoundScreen} 152 | ``` 153 | Next we can work on the `DiscoverBookScreen` but if you follow Kent's advice it will be better if you look into the `BookRow` component since is that one where the magic happen. 154 | 155 | Inside it you have to implement a simple `Link` that will point to the single page for the book where user clicks, so all you have to do is to pass the `book.id` and change the `div` into a `Link`: 156 | ```js 157 | // Other imports... 158 | import { Link } from 'react-router-dom' 159 | 160 | function BookRow({book}) { 161 | const {title, author, coverImageUrl} = book 162 | 163 | const id = `book-row-book-${book.id}` 164 | 165 | return ( 166 |
    167 | 170 | // Other code... 171 | 172 |
    173 | ) 174 | } 175 | ``` 176 | Now you are able to create **dynamic routes** based on the book id and if you have a look above you soon discover that we already have a component that will render this: `BookScreen`. 177 | 178 | Here is coming the fun part because we will use the dynamic part of our route, the `id` of the book, to render **specific information**: 179 | ```js 180 | // Other imports... 181 | import { useParams } from 'react-router-dom' 182 | 183 | function BookScreen({user}){ 184 | const params = useParams(); 185 | const {bookId} = params; 186 | 187 | const {data, run} = useAsync() 188 | 189 | React.useEffect(() => { 190 | run(client(`books/${bookId}`, {token: user.token})) 191 | }, [run, bookId, user.token]) 192 | 193 | // Other code... 194 | } 195 | ``` 196 | Here you can see how we use the `:bookId` that we mention earlier when we were creating our `Routes`. Basically React Router is able to get the parameters we use in our path, with the `useParams` hook, and allow use to **use it** in our component. 197 | 198 | Have the `bookId` at our disposal help use with the `useAsync` custom hook and query for the right information stored in our database. 199 | > Consider that you could pass the `bookId` as a prop of the component but since we use it to also generate the route it make a lot of sense to get its value from the parameter and have a cleaner component. 200 | ### 1. 💯 handle URL redirects 201 | I am going to not write about this extra credit since it is already well explained in the instructions and also is not something that interests me much, I am sure that the day I'll need this kind of information I'll need to Google it 😂 202 | ### 2. 💯 add `useMatch` to highlight the active nav item 203 | This has been a lot more interesting since we discovered a new hook that React Router gives us `useMatch` and also we get some new knowledge on Emotion as well. 204 | 205 | The first thing needed to solve this exercise is to understand that the `useMatch` hook is able to tell use if the page we are on is matching with the path we provide to it: 206 | ```js 207 | const match = useMatch('/some/path'); 208 | ``` 209 | If I have the browser on `/some/path` the `match` value will be `true`. 210 | 211 | So we want to add special styles to the `NavLink` that is currently active. TBH this confuses me because at first I asked myself *'Well `path` is dynamic, how can I pass it to `useMatch`?'* 212 | 213 | Well the thing is easier than I thought, since `NavLink` it's just a wrapper for `Link` and we pass all the props to it I can simply leverage the presence of `props.to` to match my use case. 214 | ```js 215 | function NavLink(props){ 216 | const match = useMatch(props.to); 217 | ``` 218 | Now `match` will be `true` if the URL that we have open is the same of the our link, `false` otherwise. 219 | 220 | This is even useful to customize our CSS. 221 | 222 | Since we already have a `css` props that is passing some custom styles via Emotion, is good to know that in it we can also pass **an array of objects**, and now we use `match` to pass our customization or not. 223 | 224 | ```js 225 | function NavLink(props) { 226 | const match = useMatch(props.to); 227 | return ( 228 | = n`, the most difficult part (at least for me) when we talk about *recursive fn* is identify **when** I will exit from my fn. 31 | 32 | To play with recursion you need to know when to stop. 33 | 34 | In order to implement the same `pow()` fn with recursion we need to identify what we want from the fn, in this case, we want it to `return` the exponentiation of `num` for `exp`. 35 | 36 | We already know that we could loop, but how it looks recursively? 37 | ```js 38 | function pow(num, exp) { 39 | if (exp == 1) { 40 | return num; 41 | } else { 42 | return num * pow(num, exp - 1); 43 | } 44 | } 45 | 46 | alert( pow(2, 3) ); // 8 47 | ``` 48 | Here we're not using a `for` loop, here we call `pow()` **until** `exp` is not 1 (the *escape* from the function). 49 | 50 | Basically this is what is happening when we call `pow(2, 3)`: 51 | 52 | **First time running** 53 | `exp == 1` so we will execute the `else` and we call again `pow()` this time with `exp` set to 2. Remember that we do not have **any value yet** so everything is still in memory. 54 | ```js 55 | pow( 2, 2 ); 56 | ``` 57 | **Second time running** 58 | Now `exp` is `2` but the check `exp == 1` is still false so we go for the `else` once more. Here we will call again `pow()` **but** this time the value of `exp` will be `1`. 59 | ```js 60 | pow( 2, 1 ); 61 | ``` 62 | **Third time running** 63 | Here the things get interesting... Now we are at the point that `exp == 1` will be `true` so **now we start to return some values**. 64 | That's is one of the main point of a recursive fn, you get the value back (so you'll be able to return it) **only when** the fn reaches the *base*. 65 | 66 | Now that `exp` is `1` we start to send back the value. The first time we do this we will just return `num` and this value will be given to the second iteration: `pow(2, 2)`. 67 | 68 | So we can start run the math for it, remember that `pow()` is a fn that returns a numeric value, we where waiting for it and now it's time to run `num * num`, that in our simple example will be `2 * 2 = 4`. 69 | 70 | We've executed also `pow(2, 2)` and got the value from it as well, now it's time to pass it when we where calling `pow(2, 3)`. Basically the first time we call it. 71 | 72 | `2 * 4` this is what we need to do to get `8`, the correct value we were looking for. 73 | 74 | Let's try to make it with code: 75 | ```js 76 | // Calling pow( 2, 3 ) 77 | // { num: 2, exp: 3 }, different no returned yet 78 | 79 | // Calling pow( 2, 2 ) 80 | // { num: 2, exp: 2 }, different no returned yet 81 | 82 | // Calling pow( 2, 1 ) 83 | // { num: 2, exp: 1 }, exp == 1 returning num (2) 84 | 85 | // Returning 2 to pow( 2, 2 ) 86 | // Before knowing the returned val 87 | return 2 * pow( 2, 1 ) 88 | // Now we have the returned val 89 | return 2 * 2; // 4 90 | 91 | // Returning 4 to pow( 2, 3 ) 92 | // Before knowing the returned val 93 | return 2 * pow( 2, 2 ) 94 | // Now we have the returned val 95 | return 2 * 4; // 8 96 | ``` 97 | 98 | Based on those studies I created the [[allValues]] fn that leveraging the recursion let me get all the values contained in an object. -------------------------------------------------------------------------------- /JavaScript/Regular Expression.md: -------------------------------------------------------------------------------- 1 | A regular expression is a special set of characters that allow us to match that pattern inside of a string. 2 | 3 | In JavaScript we have two methods to create Regular Expression: the constructor method and the ... 4 | 5 | ```js 6 | // Constructor 7 | const regexConstructor = new RegExp('is') 8 | 9 | // Literal 10 | const regexLiteral = /is/; 11 | ``` 12 | It is important to remember that we are defining **a pattern** so `is` means the letter `i` followed by an `s`. 13 | 14 | With the **literal declaration** we have to wrap the pattern with `/`. 15 | 16 | Regular expression give us [[#Flags|some flag]] that allow us to identify additional parameters in our pattern. Little note is how we use them in the different declarations: 17 | * **constructor** - as second parameter 18 | * **literal** - after the closing `/` 19 | 20 | ```js 21 | // Adding global flag 22 | // Constructor 23 | const regexConstructor = new RegExp('is', 'g') 24 | 25 | // Literal 26 | const regexLiteral = /is/g; 27 | ``` 28 | 29 | ## Methods 30 | Using this gives us useful methods to find the defined pattern: 31 | * `test( str )` - returns a boolean based on presence of pattern in `str` 32 | * `exec( str )` - returns an array that will contain the founded match, the `index` for the match and the full `str` we give as `input`. These special properties [[#exec|are not easily visible in console]]. 33 | 34 | ### `exec()` 35 | Depending on browser `console.log()` will not display the special props `index` and `input` but you can always call them separately. 36 | ```js 37 | const str = "This is a string."; 38 | const regex = /is/; 39 | const result = regex.exec(str); 40 | console.log(result); // ["is"] 41 | console.log(result.index); // 2 42 | console.log(result.input); // This is a string. 43 | ``` 44 | Beside this is important to note that `null` will be returned if no match has been found but also that since regex are 'state aware' we can loop all the matches if we want because if our regex has the [[#^005eb3|global]] flag each time we call `exec()` on the same string it allow us to know the different indexes. 45 | ```js 46 | const str = "This is a string."; 47 | const regex = /is/g; 48 | const result = regex.exec(str); 49 | const result1 = regex.exec(str); 50 | const result2 = regex.exec(str); 51 | console.log(result.index); // 2 52 | console.log(result1.index); // 5 53 | console.log(result2.index); // null 54 | ``` 55 | ## Flags 56 | * `g` (*global*) - allow us to keep searching for matches even if one has already found. ^005eb3 57 | * `i` (*ignore*) - ignore the case of the char passed in the pattern 58 | * `m` (*multiline*) - allow us to run our regex even if the string is multiline 59 | 60 | ## String integration 61 | Worth mention that we can work with regular expression straight from a `String` object, even this gives us some approaches to easily run and get results. 62 | * `match( exp )` - will check the `exp` against the string and will return an array with all of the matches 63 | * `replace( exp, fn )` - check the `exp` against the string and will let us work with the matches via the `fn` callback 64 | * `search( exp )` - will search inside the string the first occurrence of `exp` and will return the index 65 | 66 | ```js 67 | const str = "This is a string."; 68 | const regex = /is/gi; 69 | 70 | str.match( regex ); // ["is", "is"] 71 | 72 | str.replace( regex, match => match.toUpperCase()); // ThIS IS a string. 73 | 74 | str.search( regex ); // 2 75 | ``` 76 | ## Meta characters 77 | They have special meaning inside a Regex: 78 | * `.` - the dot will match any single character (letter, numbers or special) but will not match a line break 79 | * `*` - 80 | 81 | ## Useful 82 | `\s?` means *"potentially followed by whitespace"* 83 | `\.` simple dot 84 | `\|\|` double pipe `||` -------------------------------------------------------------------------------- /JavaScript/Utility/allValues.md: -------------------------------------------------------------------------------- 1 | > Code for this function is kept in [this repo](https://github.com/AndreaBarghigiani/utility-fns/tree/main/objects/valueFromPath) 2 | 3 | The reason that I started dig into [[Recursion]] is because I needed to get all the values inside an object. Lets say for example that I have this kind of object: 4 | ```js 5 | const obj = { 6 | dismiss: { 7 | feature_popup: { 8 | custom_labels: "dismiss_feature_popup_custom_labels", 9 | website_switcher: "dismiss_feature_popup_website_switcher", 10 | expand_view: "dismiss_feature_popup_expand_view" 11 | }, 12 | 13 | hideOverviewHostingMigrationCard: "hideOverviewHostingMigrationCard", 14 | new_dot: "dismiss_new_dot" // Old feature badge but still 15 | }, 16 | language: "language", 17 | uptime_interval: "uptime_interval", 18 | whatsnew_last_seen: "whatsnew_last_seen", 19 | default_user_role: "default_user_role", 20 | renderWS: "renderWS", 21 | show_expanded_update_list: "show_expanded_update_list" 22 | }; 23 | ``` 24 | What I want to get from `allValues` is this kind of array that lists all the values in the object, even in the nested ones: 25 | ```js 26 | const AllValues = allValues(obj); 27 | console.log(AllValues) 28 | 29 | // Will print 30 | [ 31 | "dismiss_feature_popup_custom_labels", 32 | "dismiss_feature_popup_website_switcher", 33 | "dismiss_feature_popup_expand_view" 34 | "hideOverviewHostingMigrationCard", 35 | "dismiss_new_dot", 36 | "language", 37 | "uptime_interval", 38 | "whatsnew_last_seen", 39 | "default_user_role", 40 | "renderWS", 41 | "show_expanded_update_list", 42 | ]; 43 | ``` 44 | The function I came up with is: 45 | ```js 46 | function allValues(obj) { 47 | let allVals = []; 48 | 49 | for (let key of Object.keys(obj)) { 50 | typeof obj[key] === "object" 51 | ? allVals.push(...allValues(obj[key])) 52 | : allVals.push(obj[key]); 53 | } 54 | 55 | return allVals; 56 | } 57 | ``` -------------------------------------------------------------------------------- /JavaScript/Utility/isObject.md: -------------------------------------------------------------------------------- 1 | Checks if the passed argument is an object or not. 2 | ```js 3 | const isObject = (obj) => obj?.toString() === "[object Object]"; 4 | ``` -------------------------------------------------------------------------------- /JavaScript/Utility/isObjectEmpty.md: -------------------------------------------------------------------------------- 1 | Checks if an object contains any properties to define if it is empty or not. 2 | It uses the other [[isObject]] utility function to exit early in case the value passed as argument is not an object. 3 | ```js 4 | const isObjectEmpty = (obj) => { 5 | if (!isObject(obj)) return false; 6 | for (const prop in obj) { 7 | if (obj.hasOwnProperty(prop)) { 8 | return false; 9 | } 10 | } 11 | 12 | return true; 13 | }; 14 | ``` -------------------------------------------------------------------------------- /JavaScript/Utility/isSquare.md: -------------------------------------------------------------------------------- 1 | This function checks if the passed number is a square or not. 2 | ```js 3 | function isSquare(x) { 4 | return Math.sqrt(x) % 1 === 0; 5 | } 6 | ``` -------------------------------------------------------------------------------- /JavaScript/classNames.md: -------------------------------------------------------------------------------- 1 | `className` is the way that JavaScript let us write `class` for a DOM element. We need to do so because the keywork `class` is reserved in JavaScript and is used to create classes that describe objects. -------------------------------------------------------------------------------- /JavaScript/interpolate.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndreaBarghigiani/EpicReactNotes/32f54006c876cf66cc1e856626cc109813d97a1b/JavaScript/interpolate.md -------------------------------------------------------------------------------- /JavaScript/memoize.md: -------------------------------------------------------------------------------- 1 | Memoize something means that we want to cache some complex calculations in order to not run the same calcs a second time if the result is the same. 2 | 3 | The important thing to understand here is the fact that the fn used to memoize something needs to be [[Pure functions|pure]]. As a refresher a fn is defined *pure* if will return the same result if we pass the same parameters. 4 | 5 | Simple example of a [[Pure functions|pure fn]] that we can memoize will be `addTwo`: 6 | ```js 7 | function addTwo(input) { 8 | return input + 2 9 | } 10 | ``` 11 | This function is pure because no matter what it'll return the same value if we pass the same parameter. `addTwo` will always return `5` if we call like `addTwo(3)` or will always return `3` if we call like `addTwo(1)`. 12 | 13 | As stated before, `addTwo` is a pure fn because with the same arguments will return the same values. 14 | 15 | If we want to memoize it we need to leverage the scope above to store the values already calculated, this way we do not have to run again the entire function we just need to return the correct value. 16 | ```js 17 | const cache = {} 18 | function addTwo(input) { 19 | if (!cache.hasOwnProperty(input)) { 20 | cache[input] = input + 2 21 | } 22 | return cache[input] 23 | } 24 | ``` 25 | With this code we have produced a memoized version of our function `addTwo`. We leverage the `cache` object to store in it the calculations that we have already run. 26 | 27 | I understand that this seems silly if we just have to *add two* to a number but this is the basic concept as since we are leveraging a variable kept outside the scope of our function generally the memoization of a function happen inside a [[Closure|closure]] so we can keep the `cache` variable *private*. 28 | 29 | ### Not only same value, same reference too 30 | Something that not always is clear is the fact that if we store an object or an array inside the `cache` when we call it back not only we get the same value but **we also get the same reference**. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Andrea Barghigiani 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EpicReactNotes 2 | My personal notes taken while studing on [EpicReact](epicreact.dev) 3 | 4 | Those notes are taken with [Obsidian](https://obsidian.md/), a tool to develop your second brain that I advice you to use every day but since Obsidian creates **Markdown files** you do not need to use it to read my notes, you'll miss the internal linking and graph generating feature but you'll be able to access the content no matter what. 5 | 6 | Hope you'll find this notes as useful as been to me writing those. 7 | -------------------------------------------------------------------------------- /React/React Element.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndreaBarghigiani/EpicReactNotes/32f54006c876cf66cc1e856626cc109813d97a1b/React/React Element.md -------------------------------------------------------------------------------- /React/React.Fragment.md: -------------------------------------------------------------------------------- 1 | `React.Fragment` is a special component that comes built in within React that we can use instead of the common `
    ` wrapper that is commonly used to return multiple component. 2 | 3 | Adding a `
    ` is necessary because JSX does not allow us to return multiple elements. Write something like the following is wrong. 4 | ```js 5 | return ( 6 |
    Hello World
    7 |
    Goodbye World
    8 | ) 9 | ``` 10 | If we want to return multiple elements in JSX, before of `React.Fragment`, we had to do like: 11 | ```js 12 | return ( 13 |
    14 |
    Hello World
    15 |
    Goodbye World
    16 |
    17 | ) 18 | ``` 19 | This though will create an additional `
    ` that maybe we do not want to show in the resulting HTML. Since the advent of `React.Fragment` we can write something like: 20 | ```js 21 | return ( 22 | 23 |
    Hello World
    24 |
    Goodbye World
    25 |
    26 | ) 27 | ``` 28 | This will not create a new element in our HTML but will let us add additional elements into our JSX. 29 | 30 | Since `React.Fragment` is so commonly used, beside the ability to deconstruct it from the React main library itself, we have a shorthand way to use it: 31 | ```js 32 | return ( 33 | <> 34 |
    Hello World
    35 |
    Goodbye World
    36 | 37 | ) 38 | ``` -------------------------------------------------------------------------------- /React/React.createElement().md: -------------------------------------------------------------------------------- 1 | The `createElement()` method takes two params: 2 | * `element` - a string representing the HTML element that we want to create 3 | * `props` - a JS object that will keep all the information needed to render the element (id, class, text...) 4 | 5 | A basic creation of a React element is: 6 | ```js 7 | React.createElement( 'div', { 8 | className: 'container', 9 | children: 'Hello World' 10 | }) 11 | ``` 12 | 13 | The `children` are the content of the element, it could be a string or any other elements. 14 | 15 | Beside that the `children` is a special `prop` in React and you can pass it any number of children after the prop declaration, basically this snippet: 16 | ```js 17 | React.createElement( 'div', { 18 | className: 'container', 19 | children: ['Hello', ' ', 'World'] 20 | }) 21 | ``` 22 | Is equal to: 23 | ```js 24 | React.createElement( 25 | 'div', 26 | { className: 'container'}, 27 | 'Hello', ' ', 'World' 28 | ) 29 | ``` 30 | In both cases I am passing multiple elements within an array but in the first one it is explicit in the second one is React doing it's magic. If you `console.log()` the element you'll find out that both have the same structure. 31 | 32 | If I need to pass down more DOM elements I need to create them first: 33 | ```js 34 | // If I do not need props they could be null or empty obj 35 | const spanHelloElement = React.createElement( 'span', null, 'Hello') 36 | const spanWorldElement = React.createElement( 'span', {}, 'World') 37 | const reactElement = React.createElement( 38 | 'div', 39 | { className: "container" }, 40 | spanHelloElement, ' ', spanWorldElement 41 | ); 42 | 43 | ReactDOM.render( reactElement, rootElement ) -------------------------------------------------------------------------------- /React/React.useEffect().md: -------------------------------------------------------------------------------- 1 | `React.useEffect()` is the Hook way that let us run some code when some state/values/props have changed. At first, when Hooks have been introduced, this looked like the answer to the [[lifecycle methods]] that we were used to use in a React class component. 2 | 3 | > This is very similar to [[React.useLayoutEffect()|useLayoutEffect]] with the main difference that when we use the `useEffect` Hook it gets run **after** the component renders and ensures that your effect callback does not block browser painting. 4 | 5 | Anyway as we will dig deeper in this note `React.useEffect()` has many benefits that go over the standard lifecycle methods. 6 | Basic syntax: 7 | ```js 8 | React.useEffect( () => { 9 | // The code in here will run at each render 10 | }) 11 | ``` 12 | 13 | The `useEffect` Hook has a *dependency list*, an array that will contain the values that it needs to monitor in order to know if it has to fire or not. 14 | ```js 15 | React.useEffect( () => { 16 | // The code in here will run at first render 17 | }, [dependency, list]) 18 | ``` 19 | Most of the time the items that we add to the dependency list are just variables holding values **but**, as happen for the [[2. useCallback custom hooks|useCallback exercise]] sometimes *'looks like'* we need to pass in a fn. 20 | ```js 21 | // Custom Hook to define async operations 22 | function useAsync( asyncCallback, initialState ){ 23 | const [state, dispatch] = // state definition 24 | 25 | React.useEffect(() => { 26 | const promise = asyncCallback(); // <- this is a dependency 27 | // Other code not important 28 | }, 29 | [asyncCallback]); // <- WRONG!!! 30 | 31 | } 32 | ``` 33 | Maybe you're asking *"why should it be bad?"* 34 | 35 | I do not mean that is *always* a bad thing, the fact is that we will need to execute our `useEffect` at each render because the `asyncCallback` we're passing, in this case, will be a *'new fn at each render'*. 36 | ```js 37 | // How we use the custom Hook 38 | function PokemonInfo({pokemonName}) { 39 | const state = useAsync( 40 | () => { 41 | // the body of the asyncCallback 42 | }, 43 | // more code... 44 | ) 45 | 46 | ``` 47 | As you can see the `asyncCallback` is defined **inside the component** and from the React point of view it'll be a new function at each render and this is not a good solution since will let us wasting resources. 48 | 49 | In order to solve this issue we have to identify **what really changes** and pass it as a parameter to insert it in the dependencies list. 50 | 51 | In this specific case the thing that was changing was the `pokemonName` so we changed the [[custom Hook]] declaration and it's use: 52 | ```js 53 | // Custom Hook to define async operations 54 | function useAsync(asyncCallback, initialState, dependencies) { 55 | const [state, dispatch] = // state definition 56 | 57 | React.useEffect(() => { 58 | // more code... 59 | }, 60 | dependencies); // <- I am using the passed dependencies 61 | 62 | return state 63 | } 64 | 65 | function PokemonInfo({pokemonName}) { 66 | const state = useAsync( 67 | () => { 68 | // the body of the asyncCallback 69 | }, 70 | // more code... 71 | [pokemonName], // <- this is the dependendies we pass to our custom Hook 72 | ) 73 | ``` 74 | Probably you're using ESLint in your project, and btw congrats in doing so, and you saw the warning about dependencies of `useEffect`. To solve this you can just add a comment that will stop ESLint for the next line or use [[React.useCallback()|useCallback]] to create a [[memoize|memoized]] version of the `asyncCallback` fn. 75 | ```js 76 | // eslint-disable-next-line react-hooks/exhaustive-deps 77 | ``` 78 | ## Best benefits of `useEffect` 79 | ## Common behavior of `useEffect` 80 | ### Running at each render - without dependencies array 81 | ```js 82 | React.useEffect( () => { 83 | // The code in here will run at each render 84 | }) 85 | ``` 86 | ### Running after mount the component - with an empty dependencies array 87 | ```js 88 | React.useEffect( () => { 89 | // The code in here will run at first render 90 | }, []) 91 | ``` 92 | ### Run when a specific prop or state changes - with a dependencies array 93 | ```js 94 | React.useEffect( () => { 95 | // The code in here will run each time 'specific' changes 96 | }, [specific]) 97 | ``` 98 | 99 | ## Use examples 100 | ### Set a ref to a mounted DOM node 101 | ### Make an HTTP request 102 | ### Write on localStorage 103 | Here we are creating the [[custom Hook]] `useLocalStorageState` that is helping us save our state into the [[localStorage]] of our browser. You can see the `useEffect` Hook in action. 104 | ```js 105 | function useLocalStorageState(key, defaultValue = '') { 106 | const [state, setState] = React.useState( 107 | () => window.localStorage.getItem(key) || 108 | defaultValue, 109 | ) 110 | React.useEffect( () => { 111 | window.localStorage.setItem(key, state) 112 | }, [key, state] ) 113 | 114 | return [ state, setState ] 115 | } 116 | ``` 117 | The dependency array hold the values for `key` and `state`, as you know, each time those values are different the `useEffect` will run. 118 | 119 | In this example we are using the `useEffect` Hook only to save the value held in `state` inside the [[localStorage]]. -------------------------------------------------------------------------------- /React/React.useReducer().md: -------------------------------------------------------------------------------- 1 | Hook that let us handle the state of our application in a Redux-like way, basically we store the state and we edit via some actions that we dispatch in it. 2 | 3 | Will be back with an improved explanation but if you know Redux you're pretty close 😉 -------------------------------------------------------------------------------- /React/React.useRef()].md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndreaBarghigiani/EpicReactNotes/32f54006c876cf66cc1e856626cc109813d97a1b/React/React.useRef()].md -------------------------------------------------------------------------------- /React/React.useState().md: -------------------------------------------------------------------------------- 1 | Accept a value that will be used to set the initial state of our custom component, this functions returns two values in an array: 2 | 1. the first value will be the value of the state at the current time we're calling it 3 | 2. the second value is a function that will let us update the state 4 | 5 | React returns those two values in a form of an array and generally we [[destruct]] those to make simplier to access and use them later. 6 | ```js 7 | // Here I define a new state called state, to update it I'll use setState() 8 | // and it's initial value will be 0 9 | const [ state, setState ] = React.useState(0); 10 | ``` 11 | State can be defined as: data that changes over time. 12 | 13 | Each time the state of a component gets changed React will render the component to update the UI with the new information. 14 | 15 | ## Performance tip 16 | The `useState` Hook will get called each time a component render (and re-renders) and if we use some complex functions or if we have to rely on API to calculate the initial value of our state this could be a problem because the function that we use **get's called each time**. 17 | 18 | To avoid this instead of passing the function call that calculates the initial value for the state we can pass it within a function to let React call it only the first time a component gets rendered. 19 | 20 | ```js 21 | function someComplexCalcs(){ 22 | // Your complex calcs to generate the initial value 23 | } 24 | 25 | // We call someComplexCalcs() each time component renders or re-renders 26 | const [state, setState] = React.useState(someComplexCalcs()) 27 | 28 | // We execute someComplexCalcs() only once at first render 29 | const [state, setState] = React.useState( () => someComplexCalcs()) 30 | ``` 31 | This approach is called *lazy initialization* and will save us from bottlenecks in our app on sequential renders. -------------------------------------------------------------------------------- /React/ReactDOM.render().md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndreaBarghigiani/EpicReactNotes/32f54006c876cf66cc1e856626cc109813d97a1b/React/ReactDOM.render().md -------------------------------------------------------------------------------- /React/custom Hook.md: -------------------------------------------------------------------------------- 1 | A custom Hook is a normal JavaScript function that wrap shared logic between components. What makes a custom Hook a custom Hook is: 2 | * the fn name **must** start with `use`, this is just a best practice that if not followed will thrown an error 3 | * it **must** use other Hooks in it and this is what accounts for 'shared logic' 4 | 5 | Create a function is basic JavaScript stuff and something we do each single day, always remember that even if we're using React here a custom Hook is nothing less nothing more than a function. 6 | 7 | So the first thing we do to solve our problem is to create the function that will contain the shared logic: 8 | ```js 9 | function useLocalStorageState(){ 10 | // Shared logic 11 | } 12 | ``` 13 | Once we have the function we need to start thinking about the logic that we want to put in it. 14 | 15 | As highlighted a custom Hook has to use other build-in Hooks in order to be one. For example, if you need to wrap the logic that is getting a value and stores it in `localStorage` we can write something like this: 16 | ```js 17 | function useLocalStorageState(key, defaultValue = '') { 18 | const [state, setState] = React.useState( 19 | () => window.localStorage.getItem(key) || 20 | defaultValue, 21 | ) 22 | React.useEffect( () => { 23 | window.localStorage.setItem(key, state) 24 | }, [key, state] ) 25 | 26 | return [ state, setState ] 27 | } 28 | ``` 29 | As we can see this custom Hook `useLocalStorageState` gets two parameters: 30 | * a `key` that will identify the value stored in `localStorage` 31 | * a `defaultValue` that we initialize with an empty string and will identify the value that we need to store in `localStorage` for the specified string 32 | 33 | Note that this custom Hook is behaving similarly to the built-in [[React.useState()|useState]] Hook since is returning an array where the first value is the actual value of the state and the second one is the updater fn. 34 | 35 | Once this custom Hook is in place and imported within our component we can start to use it as follow: 36 | ```js 37 | // State with localStorage for the name 38 | const [name, setName] = useLocalStorageState('name', 'Andrea') 39 | // State with localStorage for the age 40 | const [age, setAge] = useLocalStorageState('name', 37) 41 | ``` 42 | ### Other examples 43 | ... -------------------------------------------------------------------------------- /React/key prop.md: -------------------------------------------------------------------------------- 1 | The `key` prop is a special functionality in React that helps the library understand what has changed in a generated JSX. I am talking about generated JSX because the following list are different even if the output HTML looks the same. 2 | ```js 3 | // Assume we are inside a component 4 | 5 | // I am returning a full list 6 | return ( 7 |
      8 |
    • One
    • 9 |
    • Two
    • 10 |
    • Three
    • 11 |
    12 | ); 13 | 14 | // I am generating the list of li elements with .map() that generates a new array 15 | const elements = [ 'One', 'Two', 'Three' ]; 16 |
      17 | {elements.map( el =>
    • {el}
    • )} 18 |
    19 | ``` 20 | The main difference here is that for React the first example is just a [[React.createElement()]] with HTML in it instead the second one is an array that generates an element, calls [[React.createElement()]], for each item. 21 | 22 | In order to help React know which element we are working with is always a best idea to add an unique `key` prop that also helps it increase the performances of our code, besides removing some silly errors when React guess badly 😉 23 | 24 | When using `key` prop is best practice **not use an index** because need to be consistent for the component for the component that React is tracking, the index of an array follows the position of an item and that's means that different value can have the same position between renders based on how we remove/add the data in the array. --------------------------------------------------------------------------------