Advanced React Hooks

Big Head
  • 👀 View the deployed code on Github.
  • Not happy with the solution? 🐞🐛 Suggest a change.
  • Grammar errors? ✏️ Edit this page.

Safely handling async operations

Summary: Create a reusable async hook to fetch a unique character from the Rick and Morty API. Don't perform state updates to unmounted components. Avoid unnecessary network calls.

  • This is a variation of KCD's exercise 2.3 of his Advanced React Hooks workshop. See his solution here.
  • Write a component that fetches a unique character from the Rick and Morty API given a user-supplied number (the character ID).
  • Add another button to fetch a random Rick and Morty character.
  • If the submitted number does not correspond to a character, show the error.
  • While fetching data, the input field, random button, and submit button should be disabled.
  • When the number currently in the input field has been submitted has been either resolved or rejected, disable the submit button unless the input the user changes it to a new value.
  • The user shouldn't be able to click the submit button if the character corresponding to the number in the input field is currently loaded (ie profile info shown on the screen).
  • Enable the user to mount and unmount this component (via a checkbox).
  • KCD's Implementation.

Important!

(The paragraph below is paraphrased from this page)

Consider the scenario where we send an http request, and before the request finishes, we change our mind and navigate to a different page (or uncheck the mount checkbox). In that case, the component would get removed from the page ("unmounted") and when the request finally does complete, because the component has been removed from the page, we’ll get this warning from React:

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.

⚠️❗❗⚠️ This warning should NOT pop up in our app. ⚠️❗❗⚠️

My Solution

Note: This solution doesn't use a library, but in most cases that you do. Use a library like Tanner Linsley's React Query to help do this instead of implementing everything on your own from scratch.

  • The RickAndMortyInfoCard in the code block below uses a useSafeAsync hook. It's responsible for managing the state, and fetching the data.
  • The useSafeAsync makes sure that the dispatch function would not run if the component is no longer mounted. The dispatch function returns the the data and state from the fetch function.
  • In other words, if the RickAndMortyInfoCard is no longer mounted, its state will no longer be updated (since it doesn't exist anymore)
  • The runFunction provided by useSafeAsync is the function that should run whenever data should be fetched. The runFunction takes in a promise and updates the state {data, status error}.
  • In our case,, the promise that we feed to the runFunction is the return value of the fetch function we call whenever we need to fetch something.
  • The useSafeAsync is a hook that optionally takes an initial state and returns { status, data, error, runFunction}. The state is just { status, data, error}.
  • The runFunction is a function that accepts a promise and runs a dispatch function to update the state { status, data, error }
  • This promise is assumed to be returned by the function you want to run. Example, you call it like this: runFunction(fetchSomething(...)) where fetchSomething(...) returns a promise
  • This dispatch function is safe, meaning that the function will not run if the component that called it is unmounted
  • Notice that useSafeAsync uses asyncReducer and useSafeDispatch which i will discuss next.

asyncReducer is a private function that is only available to useAsync.

⚠️❗❗⚠️ WARNING ⚠️❗❗⚠️: Be careful with this, you might want to write an asyncReducer that is more explicit like how KCD implemented it.

  • The useSafeDispatch takes a regular unsafeDispatchFunction and returns a function that guarantees that the dispatch function (which contains the fetch function) will not be called when the component is no longer mounted.
  • The trick is that we have an a reference that that keeps track where a component is mounted or not. If it is not mounted then the (safe) dispatch function will not do anything.
  • Bottomline:
    • useSafeDispatch takes an unsafeDispatchFunction and returns a safeDispatchFunction
    • The unsafeDispatchFunction is unsafe because it will run regardless of whether or not the component is mounted

Finally, the top level component