epicweb-dev / advanced-react-apis Goto Github PK
View Code? Open in Web Editor NEWLearn Advanced React Hooks workshop
Home Page: https://advanced-react-apis.epicweb.dev
License: Other
Learn Advanced React Hooks workshop
Home Page: https://advanced-react-apis.epicweb.dev
License: Other
It doesn't seem to be working. Final version is slaggy.
Copied final version into sandbox also disabled Infinite Loop Protection:
https://codesandbox.io/s/optimistic-wave-rm42xw
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?
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?
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.
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],
);
Description:
In docs we have links pointing to old react site update them to new react site.
Solution:
Please review my PR if you want anything else to be added. Let me know.
https://github.com/kentcdodds/advanced-react-hooks/pull/209
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
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.
I came across the following warning when I opened src/excercise/06.js
in my editor.
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
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.
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.
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 ?
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
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
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
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?
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):
./scripts/remove-ts
.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!
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])
I suspect useDebugValue
is a noop in production deployments. Therefore the devtools don't show any labels for minified hook names.
Potential options:
You could mention it in the text instead, but I'm not sure that's a good option.
Currently the social bullet points in the README are not clickable, let's make them redirect to the desired locations!
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?
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
In the utils.js file
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?
Thank you very much in advance Kent for any kind of comment.
Jose
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
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.
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?
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:
npm run start
Box
componentYou'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.
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.
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!
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.
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
.
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:
[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 Runnpm 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
The same thing that happened with react-hooks workshop. In section two the pokemon API is used
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.
change import from '../final/06'
to '../final/06.extra-1'
++import App from '../final/06.extra-1'
--import App from '../final/06'
🚨 Make sure to call `useDebugValue` with the formatted value
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:
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.