React-Advanced react hooks: Learn Advanced React Hooks workshop

Advanced React Hooks

? hi there! My name is Kent C. Dodds and this is the source material for Advanced React Hooks!

Build Status AppVeyor Build Status Code Coverage GPL 3.0 License All Contributors PRs Welcome Code of Conduct

Pre-Workshop Instructions/Requirements

In order for us to maximize our efforts during the workshop, please complete the following things to prepare.

  • ? only necessary if the workshop is remote via Zoom

  • ? specific to the material for this workshop

  • ? Setup the project (follow the setup instructions below) (~5 minutes)

  • ? Install and setup Zoom on the computer you will be using (~5 minutes)

  • ? Watch Use Zoom for KCD Workshops (~8 minutes).

  • Watch Setup and Logistics for KCD Workshops (~24 minutes). Please do NOT skip this step.

  • ? Watch The Beginner's Guide to React (available free on Egghead.io), or have the equivalent experience (77 minutes)

  • ? Watch my talk Why React Hooks (35 minutes)

  • ? Go through my Learn React Hooks Workshop, or have the equivalent basic experience of using hooks. You should be experienced with useState, useEffect, and useRef.

The more prepared you are for the workshop, the better it will go for you.

Workshop Topics

You will be learning and using the following hooks: useReducer, useContext,useCallback, useLayoutEffect, useImperativeHandle, and useDebugValue.

System Requirements

All of these must be available in your PATH. To verify things are set up properly, you can run this:

git --version
node --version
npm --version

If you have trouble with any of these, learn more about the PATH environment variable and how to fix it here for windows or mac/linux.

Setup

After you've made sure to have the correct things (and versions) installed, you should be able to just run a few commands to get set up:

git clone https://github.com/kentcdodds/advanced-react-hooks.git
cd advanced-react-hooks
npm run setup --silent

This may take a few minutes. It will ask you for your email. This is optional and just automatically adds your email to the links in the project to make filling out some forms easier. If you get any errors, please read through them and see if you can find out what the problem is. If you can't work it out on your own then please file an issue and provide all the output from the commands you ran (even if it's a lot).

You may be able to work through the entire workshop in the browser. Go to this codesandbox and you should be good to go. Note that sometimes people have trouble with codesandbox not working quite right with tests, but you should be able to work around that. If you're concerned, then it would probably be better to just set things up locally.

Running the app

To get the app up and running (and really see if it worked), run:

npm start

This should start up your browser. If you're familiar, this is a standard react-scripts application.

You can also open the deployment of the app on Netlify.

Running the tests

npm test

This will start Jest in watch mode. Read the output and play around with it. The tests are there to help you reach the final version, however sometimes you can accomplish the task and the tests still fail if you implement things differently than I do in my solution, so don't look to them as a complete authority.

Advanced React Hooks

Time to get serious ? ?

? I'm Kent C. Dodds

  • ? Utah
  • ? ? ? ? ? ?
  • ? kentcdodds.com
  • ? / ? @kentcdodds
  • ? testingjavascript.com
  • ? kcd.im/egghead
  • ? kcd.im/fem
  • ? kcd.im/news
  • ? kcd.im/blog
  • ? kcd.im/devtips
  • ? kcd.im/coding
  • ? kcd.im/youtube
  • ? kcd.im/3-mins
  • kcd.im/ama

Schedule

  • ? Logistics
  • ? 01. useReducer: simple Counter
  • ? 10 Minutes
  • ? 02. useCallback: custom hooks
  • ? 30 Minutes
  • ? 03. useContext: simple Counter
  • ? 10 Minutes
  • ? 04. useLayoutEffect: auto-growing textarea
  • ? 05. useImperativeHandle: scroll to top/bottom
  • ? 10 Minutes
  • ? 06. useDebugValue: useMedia
  • Q&A

Questions

Please do ask! Interrupt me. If you have an unrelated question, please ask on my AMA.

Zoom (for remote workshops)

  • Help us make this more human by keeping your video on if possible
  • Keep microphone muted unless speaking
  • Breakout rooms

Exercises

  • src/exercise/00.md: Background, Exercise Instructions, Extra Credit
  • src/exercise/00.js: Exercise with Emoji helpers
  • src/__tests__/00.js: Tests
  • src/final/00.js: Final version
  • src/final/00.extra-0.js: Final version of extra credit

The purpose of the exercise is not for you to work through all the material. It's intended to get your brain thinking about the right questions to ask me as I walk through the material.

Helpful Emoji ? ? ? ? ? ? ? ? ?‍? ?

Each exercise has comments in it to help you get through the exercise. These fun emoji characters are here to help you.

  • Kody the Koala Bear ? will tell you when there's something specific you should do
  • Matthew the Muscle ? will indicate what you're working with an exercise
  • Chuck the Checkered Flag ? will indicate that you're working with a final version
  • Marty the Money Bag ? will give you specific tips (and sometimes code) along the way
  • Hannah the Hundred ? will give you extra challenges you can do if you finish the exercises early.
  • Olivia the Owl ? will give you useful tidbits/best practice notes and a link for elaboration and feedback.
  • Dominic the Document ? will give you links to useful documentation
  • Berry the Bomb ? will be hanging around anywhere you need to blow stuff up (delete code)
  • Peter the Product Manager ?‍? helps us know what our users want
  • Alfred the Alert ? will occasionally show up in the test failures with potential explanations for why the tests are failing.

Workshop Feedback

Each exercise has an Elaboration and Feedback link. Please fill that out after the exercise and instruction.

At the end of the workshop, please go to this URL to give overall feedback. Thank you! https://kcd.im/arh-ws-feedback

Contributors

Thanks goes to these wonderful people (emoji key):


Kent C. Dodds

? ? ? ⚠️

Frank Calise

?

Zara603

?

Michael Friedman

?

Brandon Newton

? ?

Jonathan Bruce

?

Łukasz Gandecki

?

Justin Dorfman

?

Oluwaseun Oyebade

?

This project follows the all-contributors specification. Contributions of any kind welcome!

License

This material is available for private, non-commercial use under the GPL version 3. If you would like to use this material to conduct your own workshop, please contact me at [email protected]

Comments

  • MediaQueryList.addListener Deprecated
    MediaQueryList.addListener Deprecated

    Nov 9, 2020

    I came across the following warning when I opened src/excercise/06.js in my editor.

    image

    After a bit of googling, I found the following guideline on MDN.

    This method exists primarily for backward compatibility; if possible, you should instead use addEventListener() to watch for the change event.

    https://developer.mozilla.org/en-US/docs/Web/API/MediaQueryList

    Reply
  • Another potential exercise 2 extra credit
    Another potential exercise 2 extra credit

    Mar 23, 2021

    I noticed that when you click the pokemon names in rapid succession, it will load their info into view one by one as the requests resolve, which isn't great UX. So I came up with a way where only the most recent fetch matters, while earlier ones are ignored. This both improves the UX as well as handles race conditions.

      const safeDispatch = useSafeDispatch(dispatch);
    
      const mostRecentPromise = React.useRef(null);
    
      const run = React.useCallback(
        promise => {
          safeDispatch({ type: 'pending' });
          mostRecentPromise.current = promise;
          let action = {};
          promise
            .then(
              data => {
                action = { type: 'resolved', data };
              },
              error => {
                action = { type: 'rejected', error };
              },
            )
            .finally(() => {
              if (promise === mostRecentPromise.current) {
                safeDispatch(action);
              }
            });
        },
        [safeDispatch],
      );
    
    enhancement 
    Reply
  • Broken reference link in the documentation
    Broken reference link in the documentation

    Jul 17, 2021

    02.md file has a reference to https://whatthefork.is/closure regarding the closure, but the website is either down or no more available. Maybe add a different resource?

    Reply
  • React team has removed the warning on setting `setState` on an unmounted component
    React team has removed the warning on setting `setState` on an unmounted component

    Aug 19, 2021

    Hi @kentcdodds

    I just noticed this today that react team removed the warning on setState on unmounted components - https://github.com/facebook/react/pull/22114

    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.

    In the workshop material, (Exercise: 02, Extra credit 03) we may need to highlight this and give a link to this pull request Remove the warning for setState on unmounted components as it claims these are not really memory leak.

    Just opening this here as a feedback.

    enhancement 
    Reply
  • Changelog
    Changelog

    Sep 16, 2021

    Here's what's going to be different in the next version of the advanced-react-hooks workshop (you can find all these changes in the next branch until the videos are re-recorded):

    • Everything is TypeScript. There's a new script you can run to remove the TypeScript automatically if you want: ./scripts/remove-ts.
    • General improvements to the workshop content based on feedback from learners
    • Exercise 2 (useCallback) has removed the "safeDispatch" extra credit because it's unnecessary: https://github.com/facebook/react/pull/22114
    • Exercise 2 (useCallback) has been improved quite a bit based on feedback from learners. Turns out it was more difficult that it should be due primarily to assumptions I made on people's understanding of concepts like memoization and the instructions weren't completely clear on why we were making the changes we did. So the instructions have been clarified and added explainers on memoization as well.
    • Exercise 2 (useCallback) has two new extra credits for handling race conditions and using abort controller to cancel requests.
    • Exercise 3 (context) has a new extra credit for removing context to illustrate further why you might not need context in most situations.
    • Exercise 4 (useLayoutEffect) is dead simple and it can stay that way...
    • Exercise 5 (useImperativeHandle) and Exercise 6 (useDebugValue) both received some minor updates to the instructions
    Reply
  • useLayoutEffect: auto-growing textarea should have a different name
    useLayoutEffect: auto-growing textarea should have a different name

    Oct 17, 2020

    In Exercise 4 the exercise name doesn't correspond the exercise content. The exercise should have a name like "auto scrolling chat". Currently there is no "growing" action and technically no "textarea" (as it's used in HTML).

    Growing textarea would be something like what's enabled by this package: https://github.com/buildo/react-autosize-textarea.

    Reply
  • Wrap in act() to avoid extraneous console.error
    Wrap in act() to avoid extraneous console.error

    Apr 20, 2021

    I was working through exercise 2-extra 3, and was having a hard time getting the tests to pass, even though my implementation seemed to work fine in the browser, without throwing an error.

    I added a useState in useAsync to track whether or not the fetch should be considered cancelled, and added a useEffect in the component with a cleanup method to call the setter for that state. The test failed on the last step, and when I replaced the alfredTip with a plain expect(console.error).not.toHaveBeenCalled() I could see this console error:

    expect(jest.fn()).not.toHaveBeenCalled()
    
    Expected number of calls: 0
    Received number of calls: 1
    
    1: "Warning: An update to %s inside a test was not wrapped in act(...).·
    When testing, code that causes React state updates should be wrapped into act(...):·
    act(() => {
      /* fire events that update state */
    });
    /* assert on the output */·
    ...
    

    Wrapping an act() around the click and the unmount removes the warning and lets the test pass. This PR also wraps the type, just because that feels cleaner for some reason.

    I know my solution wasn't the same as what KCD shows in the video (and it's not as good), but he did leave the instructions vague on purpose, so it seems like the tests should accept anything that works. 😉

    Reply
  • Jest timeout error during project validation
    Jest timeout error during project validation

    Oct 10, 2020

    Just downloaded this repo and ran npm run setup --silent but I'm getting this error in the project validation step:

    FAIL  src/__tests__/03.extra-2.js (9.934 s)
      ● displays the pokemon
    
        thrown: "Exceeded timeout of 5000 ms for a test.
        Use jest.setTimeout(newTimeout) to increase the timeout value, if this is a long-running test."
    
          15 | })
          16 |
        > 17 | test('displays the pokemon', async () => {
             | ^
          18 |   render(<App />)
          19 |   const input = screen.getByLabelText(/pokemon/i)
          20 |   const submit = screen.getByText(/^submit$/i)
    
          at Object.<anonymous> (src/__tests__/03.extra-2.js:17:1)
    

    Not sure if I should create the timeout mentioned there or if the Pokemon API is down. Were checking discord and previous issues, and seems uncommon.

    Reply
  • update the final file for extra credit to match video
    update the final file for extra credit to match video

    Oct 6, 2020

    also adds in comments about why you would want to use useLayoutEffect for a more general custom hook.

    Reply
  • Pokemon API is Down
    Pokemon API is Down

    Aug 30, 2020

    The same thing that happened with react-hooks workshop. In section two the pokemon API is used

    Reply
  • Start the TypeScript-fication process by creating a `ts/main` branch
    Start the TypeScript-fication process by creating a `ts/main` branch

    Jan 17, 2021

    Hi @kentcdodds, could you create a ts/main branch from HEAD main as you did for the other workshop ex (https://github.com/kentcdodds/react-fundamentals/tree/ts/main), so that we could contribute to the typescript examples?

    Reply
  • useDebugValue: inconsistency in format
    useDebugValue: inconsistency in format

    Oct 20, 2020

    Marty the Money Bag 💰 says: image

    Should be in the end (according to the after image): image

    It seems it should be useMedia(\`${query}\`) => ${state}

    Reply
  • Fix setup error by adding Node 15
    Fix setup error by adding Node 15

    Nov 14, 2020

    This also adds Node 15 to the GitHub workflow.

    Reply
  • Exercise 4 enhancement
    Exercise 4 enhancement

    Mar 24, 2021

    I was struggling to find resources online on what exactly happens when a state update occurs in useLayoutEffect. I was only able to find one React Github issue that was related (https://github.com/facebook/react/issues/17334). Not sure why this behavior is so poorly documented.

    My understanding after reading that issue: Updating state in useLayoutEffect will FLUSH (aka call) all useLayoutEffect AND useEffect callbacks and skip painting for that update cycle. Thus, updating state in useLayoutEffect could be very bad for performance since you'd have to go through useLayoutEffects (cycle 1) + useEffects (cycle 1) + useLayoutEffects (cycle2) before DOM is painted.

    Is my understanding correct? If it is, I think it's a very important behavior and is worth mentioning.

    Also, even the documentation for useLayoutEffect() does not explain this behavior properly.

    Reply
  • Cannot read property 'isolatedPath' of undefined
    Cannot read property 'isolatedPath' of undefined

    Nov 18, 2020

    I've been working with the advance-react-hooks for a couple of days. I went through the first 2 exercises with no problems. Today I did npm run which worked fine but after restarting my PC unintentionally I couldn't run it anymore. The error message is the following

    image

    console

    image

    this is my Package.json :

    "engines": {
        "node": "^10.13 || 12 || 14 || 15",
        "npm": ">=6"
      },
      "dependencies": {
        "@kentcdodds/react-workshop-app": "^2.19.10",
        "@testing-library/react": "^11.1.2",
        "@testing-library/user-event": "^12.2.2",
        "chalk": "^4.1.0",
        "codegen.macro": "^4.0.0",
        "mq-polyfill": "^1.1.8",
        "react": "^16.14.0",
        "react-dom": "^16.14.0",
        "react-error-boundary": "^3.0.2"
      },
      "devDependencies": {
        "@types/react": "^16.9.56",
        "@types/react-dom": "^16.9.9",
        "husky": "^4.3.0",
        "npm-run-all": "^4.1.5",
        "prettier": "^2.1.2",
        "react-scripts": "^4.0.0"
      },
    

    It should be noted that when navigating to an exercise (localhost:3000/1) the page renders properly. Let me know If there's anything else I can post here. Thanks

    Reply