GithubHelp home page GithubHelp logo

epicweb-dev / advanced-react-hooks Goto Github PK

View Code? Open in Web Editor NEW
2.0K 25.0 1.3K 5.3 MB

Learn Advanced React Hooks workshop

Home Page: https://advanced-react-apis.epicweb.dev

License: Other

JavaScript 4.97% CSS 18.78% Dockerfile 0.30% TypeScript 74.50% MDX 1.27% Shell 0.18%
kcd-edu react hooks epicreact-dev

advanced-react-hooks's Introduction

Learn the more advanced React APIs and different use cases to enable great user experiences.

We’ll look at some of the more advanced hooks and APIs and ways they can be used to optimize your components and custom hooks. We'll consider each API with common use cases you'll run into in your applications.



Build Status GPL 3.0 License Code of Conduct

Prerequisites

  • You should be experienced with useState, useEffect, and useRef.

Pre-workshop Resources

Here are some resources you can read before taking the workshop to get you up to speed on some of the tools and concepts we'll be covering:

System Requirements

  • git v2.18 or greater
  • NodeJS v18 or greater
  • npm v8 or greater

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

This is a pretty large project (it's actually many apps in one) so it can take several minutes to get everything set up the first time. Please have a strong network connection before running the setup and grab a snack.

Follow these steps to get this set up:

git clone --depth 1 https://github.com/epicweb-dev/advanced-react-apis.git
cd advanced-react-apis
npm run setup

If you experience errors here, please open an issue with as many details as you can offer.

The Workshop App

Learn all about the workshop app on the Epic Web Getting Started Guide.

Kent with the workshop app in the background

advanced-react-hooks's People

Contributors

alexfertel avatar alexsurelee avatar allcontributors[bot] avatar anabellaspinelli avatar aosante avatar aprillion avatar bobbywarner avatar btnwtn avatar daleseo avatar diegotc86 avatar dominicchapman avatar emzoumpo avatar frankcalise avatar gavinosborn avatar giancarlobrusca avatar gregalia avatar hillsie avatar huuums avatar ianvs avatar icyjoseph avatar jacobparis avatar jmagrippis avatar jonathanbruce avatar jrozbicki avatar jsparkdev avatar jwm0 avatar kentcdodds avatar rbusquet avatar rphuber avatar snaptags avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

advanced-react-hooks's Issues

Another potential exercise 2 extra credit

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],
  );

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

Hi @kentcdodds

I just noticed this today that react team removed the warning on setState on unmounted components - facebook/react#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.

Could not reproduce error in Part 2 extra 3, is it due to React 17 ?

Hello,
I tried to replicated the error demonstrated in part 2 (useCallback) extra 3. I could not get the expected error message when unmounting the component before the invocation of the dispatch invoked by the run asynchronous callback:

 const run = React.useCallback(promise => {
    dispatch({type: 'pending'})
    promise.then(
      data => {
        dispatch({type: 'resolved', data}) <=== Should throw the React error when component is unmounted
      },
      error => {
        dispatch({type: 'rejected', error}) <=== Should throw the React error when component is unmounted
      },
    )
  }, [])

The error expected by the tutorial:
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.

I tried to search in the changelog of React 17 but could not find if it was related. Is this error now prevented by React or just a weird thing in my local setup ?

Pokemon API is Down

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

Node 17

I see that package.json is updated, to warn against node v17.
Unfortunately, README.md was not updated.
Consider adding a .nvmrc and volta.sh code to package.json to help guide users.

useContext gotchas

Hi, I believe it would be helpful to note somewhere this context caveat.

For example in advanced-react-hooks/src/final/03.js:

function CountProvider(props) {
  const [count, setCount] = React.useState(0)
  const value = [count, setCount]
  // could also do it like this:
  // const value = React.useState(0)
  return <CountContext.Provider value={value} {...props} />
}

The code will re-render all consumers every time the Provider re-renders because a new object is always created for value.

Promise resolving question

Hi Kent,

I hope you are doing well despite the situation.

Β Regarding the exercise,Β ./src/exercise/03.extra-2.js,Β caching in a context provider, I just wanted to ask you about this chunk of code:

In the 03.extra-2.js file

image

In the utils.js file

image

When the data information about one particular pokemon is not in the cache, then the run function is called. As far as I can understand, and this is my doubt, it seems as if we are resolving the same promise twice, I think in order to sync the async call of the API and then store the information in cache. Are we technically resolving the promise twice? If this is the case, could we have any kind of issue by doing that?

image

image

Thank you very much in advance Kent for any kind of comment.
Jose

Tests doesn't pass for `extra-1` in exercise 6

Case:

change import from '../final/06' to '../final/06.extra-1'

++import App from '../final/06.extra-1'
--import App from '../final/06'

Error:

🚨 Make sure to call `useDebugValue` with the formatted value

Info

I was confused, as I did extra credit and tests wasn't passing.

When I checked @kentcdodds implementation, it wasn't different much from mine.

I checked the test against '../final/06.extra-1' and it failed.

Tests should pass. as useDebugValue is working correctly, as you can see here:

image

Jest timeout error during project validation

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.

Exercise 4 enhancement

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 (facebook/react#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.

Cannot read property 'isolatedPath' of undefined

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

useDebugValue - production deploys don't show anything

I suspect useDebugValue is a noop in production deployments. Therefore the devtools don't show any labels for minified hook names.

Potential options:

  1. Remove links to production deployments for this exercise
  2. Build development versions for production deployments

You could mention it in the text instead, but I'm not sure that's a good option.

Markdown ref-style links aren't rendered on website

Not sure where to report it but there's a tiny problem with reference-style links on the epicreact.dev website.

They just render as normal text instead of links like [git][git] v2 or greater for all workshops; all README.md have a couple of those at the beginning. The source seems to be the respective workshop repos but I assume the actual website repo isn't public. So can't fix it but I hope it helps somehow.

Example: https://epicreact.dev/modules/advanced-react-hooks/advanced-react-hooks-welcome

Errors on initial validation

I just cloned the repo, started the setup and I got this two errors:

 FAIL  src/__tests__/01.js
  ● using useReducer

    TypeError: Cannot read property 'toString' of undefined

      30 |   React.createElement = createElement
      31 |
    > 32 |   const lines = counterFn.toString().split('\n')
         |                           ^
      33 |   const useReducerLine = lines.find(line => {
      34 |     return !line.trim().startsWith('//') && line.includes('useReducer(')
      35 |   })

      at Object.<anonymous> (src/__tests__/01.js:32:27)

 FAIL  src/__tests__/03.js
  ● CountProvider is rendering a context provider with the right value

    expect(received).toEqual(expected) // deep equality

    Expected: [0, Any<Function>]
    Received: undefined

      37 |   render(<App />)
      38 |
    > 39 |   expect(providerProps.value).toEqual([0, expect.any(Function)])
         |                               ^
      40 |
      41 |   act(() => {
      42 |     providerProps.value[1](1) // lol

      at Object.<anonymous> (src/__tests__/03.js:39:31)

Obviously it is a fresh copy of the repo, nothing changed.

Please tell why did you say its still valuable to apply useSafeDispatch() function in your material even after react 18?

In your workshop material you mentioned "Also notice that while what we're doing here is still useful and you'll learn valuable skills"
function useSafeDispatch(dispatch) {
const mountedRef = React.useRef(false)

React.useEffect(() => {
mountedRef.current = true
return () => {
mountedRef.current = false
}
}, [])

return React.useCallback(
(...args) => (mountedRef.current ? dispatch(...args) : void 0),
[dispatch],
)
}

But As mentioned on the link reactwg/react-18#82
"The workaround is worse than the original problem
The workaround above is very common. But not only is it unnecessary, it also makes things a bit worse:

In the future, we'd like to offer a feature that lets you preserve DOM and state even when the component is not visible, but disconnect its effects. With this feature, the code above doesn't work well. You'll "miss" the setPending(false) call because isMountedRef.current is false while the component is in a hidden tab. So once you return to the hidden tab and make it visible again, it will look as if your mutation hasn't "come through". By trying to evade the warning, we've made the behavior worse.

In addition, we've seen that this warning pushes people towards moving mutations (like POST) into effects just because effects offer an easy way to "ignore" something using the cleanup function and a flag. However, this also usually makes the code worse. The user action is associated with a particular event β€” not with the component being displayed β€” and so moving this code into effect introduces extra fragility. Such as the risk of the effect re-firing when some related data changes. So the warning against a problematic pattern ends up pushing people towards more problematic patterns though the original code was actually fine."

Please tell what should we do?

React Developer Tools hooks weird rendering

Hi, was going through these amazing examples and noticed an issue with the React Developer Tools when working on exercise/06.js.

Steps to reproduce:

  1. npm run start
  2. Navigate to http://localhost:3000/6
  3. Open the React Developer Tools and inspect the Box component

You'll see that subsequent hooks calls are nested (even though that's not what the code is doing). If you click on the Copy to Clipboard button, you'll see the same in the JSON.
Screen Shot 2020-04-11 at 6 26 49 PM

  • I added a React.useState('red') call to see if this behavior was unique to multiple calls to the same hook, but the nesting seems to happen on all hooks.

However, if we run the exercise code isolated (http://localhost:3000/isolated/exercise/06.js), the hooks appear correctly.
Screen Shot 2020-04-11 at 6 30 28 PM

Perhaps this issue should go to the React team, but since it only happens in one scenario, maybe there's something in the wrapper code that's triggering it.

Cheers!

I would like to contribute to this repo

Hi @kentcdodds,

I saw a little improvement for this repo and I would like to contribute if possible.

How can I push my branch so that I can open a PR?
I tried to follow the steps here: https://github.com/kentcdodds/advanced-react-hooks/blob/main/CONTRIBUTING.md
But I got this error:
unable to access 'https://github.com/kentcdodds/advanced-react-hooks.git/': The requested URL returned error: 403

My contribution is simple, just to use the step prop in the extra-4 of exercise 1, so that the extra-4 case is more compatible with the previous cases:

01.md

const [state, dispatch] = React.useReducer(countReducer, {
  count: initialCount,
})
const {count} = state
const increment = () => dispatch({type: 'INCREMENT', step})

src/final/01.extra-4.js

function countReducer(state, action) {
  const {type, step} = action
  switch (type) {
    case 'increment': {
      return {count: state.count + step}
    }
    default: {
      throw new Error(`Unsupported action type: ${action.type}`)
    }
  }
}

function Counter({initialCount = 0, step = 1}) {
  const [state, dispatch] = React.useReducer(countReducer, {
    count: initialCount,
  })
  const {count} = state
  const increment = () => dispatch({type: 'increment', step})
  return <button onClick={increment}>{count}</button>
}

Could you please give me a hand to push my code to this repo?

Broken link to `How to contribute to open source`

The link to egghead.io series requires updating.

Searched and wasn't able to find an open or close issue on this.

It's night time in the US, so I'm guessing you're all winding down for the day. I'll follow up with a PR shortly, to reduce the round trip communication time, if that's Okay?

Docker compose for advanced-react-hooks fails

I've had to use docker for all my modules thus far since node setup fails (I had an earlier ticket and it was mentioned as a bug within npm even though I've used nvm to try multiple node versions). The error I get with docker compose is:

➜ advanced-react-hooks git:(main) βœ— dcom up -d
Building node
[+] Building 9.9s (8/8) FINISHED
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 36B 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [internal] load metadata for docker.io/library/node:16 1.6s
=> [1/4] FROM docker.io/library/node:16@sha256:0c672d547405fe64808ea28b49c5772b1026f81b3b716ff44c10c96abf177d6a 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 8.10kB 0.0s
=> CACHED [2/4] WORKDIR /app 0.0s
=> CACHED [3/4] COPY . . 0.0s
=> ERROR [4/4] RUN NO_EMAIL_AUTOFILL=true node setup 8.3s

[4/4] RUN NO_EMAIL_AUTOFILL=true node setup:
#8 0.137 ▢️ Starting workshop setup...
#8 0.337 Running the following command: npx "https://gist.github.com/kentcdodds/bb452ffe53a5caa3600197e1d8005733" -q
#8 8.218 /tmp/npx-22540488.sh: 1: /tmp/npx-22540488.sh: workshop-setup: not found
#8 8.220 npm notice
#8 8.220 npm notice New minor version of npm available! 8.15.0 -> 8.19.1
#8 8.220 npm notice Changelog: https://github.com/npm/cli/releases/tag/v8.19.1
#8 8.221 npm notice Run npm install -g [email protected] to update!
#8 8.221 npm notice


executor failed running [/bin/sh -c NO_EMAIL_AUTOFILL=true node setup]: exit code: 127
ERROR: Service 'node' failed to build : Build failed

Notes: dcom is an alias for docker-compose

Warning not triggering from exercise 2, extra credit 3

Hi, I don't know if this is the right place to ask this, I also posted this in the discord server... if this is the wrong
place, I apologize in advance! Please let me know right away and I'll delete this issue! :)

For some reason, no matter how many times I try, I can't trigger the warning:

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.

I made sure that my console output is showing the output from the iframe by selecting the right context....

What could I be doing wrong? Thanks!

cant-trigger-warning-small.mov

React DevTools only works on isolated page

Because the main exercise view renders the exercise in an iframe, React DevTools doesn't include those contents. This is a known limitation: facebook/react#18945. It's a small issue in this context but slightly confusing, especially on exercise 6, useDebugValue: useMedia which explicitly requires the use of the DevTools.

There's a simple-enough fix where you place the following in the <head> of public/index.html:

<script type="text/javascript">
  if (window.parent) {
    window.__REACT_DEVTOOLS_GLOBAL_HOOK__ = window.parent.__REACT_DEVTOOLS_GLOBAL_HOOK__
  }
</script>

I couldn't find a good reason for not including this and, for me at least, it solved a problem which distracted me from the learning experience. Would it be worth updating the repos with this fix?

If there is a good reason for not including this, then perhaps some clarification in the instructions would help people like me foolishly doing this in the morning without enough coffee.

Exercise 4 (useLayoutEffect: auto-scrolling textarea) -> no discernible difference between useEffect and useLayoutEffect in React 18

Working through Exercise 4 (useLayoutEffect: auto-scrolling textarea) and I couldn't see the difference between useEffect and useLayoutEffect. I know there was a recent update to React 18, so I rolled back to the version with React 17. At that point, there is a discernible difference between useEffect and useLayoutEffect.

You can verify if you look at the "Production" verison of the exercise and final here:
Exercise
Final

Changelog

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: facebook/react#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

useDebugValue: useMedia (Advanced React Hooks)

Hello, @kentcdodds! I have one question left about the Advanced React Hooks section (excercise 6). I want to make clear the use case of mounted variable in the code below. Query value never changes, so on unmount we just remove the change event listener added to matchMedia, because of that onChange function won't be called if the component gets unmounted.
what's the exact reason for checking mounted inside matchMedia onChange handler function? (mounted variable is declared and initialized inside useEffect callback, so because of that the use case is confusing for me). Thanks in advance!)

 React.useEffect(() => {
    let mounted = true // <--
    const mql = window.matchMedia(query)
    function onChange() {
      if (!mounted) {
        return
      }
      setState(Boolean(mql.matches))
    }

    mql.addEventListener('change', onChange)
    setState(mql.matches)

    return () => {
      mounted = false
      mql.removeEventListener('change', onChange)
    }
  }, [query])

Checking promise is missing from 02.extra-2.js

Hi,
I have just completed the training for the second extra credit for part 2 and I noticed that final version is missing the following from the run function:

if (!promise) { return }

Is this correct?
This would make the run function look like this:

const run = React.useCallback(promise => { if (!promise) { return } dispatch({type: 'pending'}) promise.then( data => { dispatch({type: 'resolved', data}) }, error => { dispatch({type: 'rejected', error}) }, )

Thanks

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.