{"id":21890852,"url":"https://github.com/chantastic/concurrent-mode-in-30","last_synced_at":"2026-05-15T23:33:11.942Z","repository":{"id":38990757,"uuid":"266204723","full_name":"chantastic/concurrent-mode-in-30","owner":"chantastic","description":null,"archived":false,"fork":false,"pushed_at":"2023-01-07T15:52:32.000Z","size":2632,"stargazers_count":2,"open_issues_count":12,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-02-02T22:29:23.707Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/chantastic.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-05-22T20:49:08.000Z","updated_at":"2021-12-09T16:39:44.000Z","dependencies_parsed_at":"2023-02-07T14:16:04.053Z","dependency_job_id":null,"html_url":"https://github.com/chantastic/concurrent-mode-in-30","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chantastic%2Fconcurrent-mode-in-30","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chantastic%2Fconcurrent-mode-in-30/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chantastic%2Fconcurrent-mode-in-30/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chantastic%2Fconcurrent-mode-in-30/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/chantastic","download_url":"https://codeload.github.com/chantastic/concurrent-mode-in-30/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244898458,"owners_count":20528341,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-28T12:17:40.883Z","updated_at":"2026-05-15T23:33:06.916Z","avatar_url":"https://github.com/chantastic.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"## GOALS\n\n- What and why is it?\n- \"boundary\"\n- \"fallback\"\n- \"transition\"\n- `Suspense` and `SuspenseList`\n- How it impacts components\n\n## NEW APIs\n\n[docs](https://reactjs.org/docs/concurrent-mode-reference.html#usedeferredvalue)\n\n- [`createRoot`](https://reactjs.org/docs/concurrent-mode-reference.html#createroot)\n- [`createBlockingRoot`](https://reactjs.org/docs/concurrent-mode-reference.html#createblockingroot)\n- [`Suspense`](https://reactjs.org/docs/concurrent-mode-reference.html#suspensecomponent)\n  - `fallback`\n- [`SuspenseList`](https://reactjs.org/docs/concurrent-mode-reference.html#suspenselist)\n  - `revealOrder`: `forwards|backwards|together`\n- [`useTransition`](https://reactjs.org/docs/concurrent-mode-reference.html#usetransition)\n- [`useDeferredValue`](https://reactjs.org/docs/concurrent-mode-reference.html#usedeferredvalue)\n\n## Lessons\n\n### 1. Import `PokemonDetail` dynamically\n\n- Use dynamic `import()` to import the `PokemonDetail` module\n- Wrap it in `React.lazy` so `React.Suspense` knows what to do with it\n- The app breaks because it loads with the component it needs to render. this means it's working\n\n### 2. Catch async errors with an `ErrorBoundary`\n\n- Use an [error boundary component](https://reactjs.org/docs/error-boundaries.html) to catch errors and provide a fallback\n- Now, read and fix the error!\n\n### 3. Wrap in `React.Suspense` boundary with fallback\n\n- Add a `React.Suspense` component around async component\n- Provide a fallback prop with any renderable `fallback={\u003cdiv\u003e...fetching pokemon\u003c/div\u003e}`\n\n### 4. Wrap promised data in suspensify\n\n- As an experiment, wrap the `initialPokemon` object in a `Promise.resolve`\n- Like `React.lazy`, promises need a wrapper to communicate with `Suspense` boundaries. Import the small `suspensify` utility into the app. `import { suspensify } from \"./api\";`\n- Wrap our `initialPokemon` resolved promise in `suspensify`\n- Call the `read()` function in the consuming component (`PokemonDetail`)\n\nPlay with all the options...\n\n- `promise.resolve({ ... })`\n- `Promise.reject()`\n- `new Promise(() =\u003e {})`\n- `fetchPokemon(1)`\n\n- Finally, use `fetchPokemon` (also provided as a named export from `api.js`)\n\n### 5. Put `initialPokemon` in state, so can update it\n\n- `let [pokemon] = React.useState(initialPokemon)`\n- Note that we're putting the whole pokemon here, not just the number\n- Note also that the `useState` hook is totally fine holding an unresolved promise\n\n### 6. Create a button to fetch the next pokemon\n\n- Take the updater function from `useState`\n  - `let [pokemon, updatePokemon] = React.useState(initialPokemon);`\n- ...`updatePokemon(suspensify(updatePokemon(2)))`\n\n### 7. Make the button dynamic\n\n- Try `suspensify(updatePokemon(pokemon.id + 1))`\n- It doesn't work because `pokemon` isn't a resolved value in this scope\n- we can only use it where the component calls `.read()`. remember .read()?\n- this will impact how you write code.\n- let's update the component and then teach it some composability\n- - (we have a short ammount of time so i'll show you how i solved it)\n- - (move the button in first.)\n\n### 8. `useTransition`\n\n- startTransition w/timeout to set how long until flash off fallback\n- unstable_createRoot to turn it on\n- isPending to show some loading state\n- isPending to show change color to gray\n  (1. useDeferredValue _but_ short on time)\n\n### 9. More Suspense boundaries\n\n- use oversimplified PokemonList componenent that does all of it's own data-fetching\n- we're slowing this call down to 2 seconds\n- if we put it in the same error boundary, they load together\n- we can do better. we'd like to load the first available\n- use different suspense boundaries\n- we can still do layout\n\n### 10. `SuspenseList`\n\n- `revealOrder: \"forwards\" | \"backwards\" | \"together\"`\n- `tail: \"collapsed\" | \"hidden\"`\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchantastic%2Fconcurrent-mode-in-30","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fchantastic%2Fconcurrent-mode-in-30","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchantastic%2Fconcurrent-mode-in-30/lists"}