React
Created: 2015-08-26 17:07:23 -0700 Modified: 2023-06-01 22:29:08 -0700
Background
Section titled BackgroundThe original notes were written over the course of three years, and my knowledge of React has changed a lot in that time, so I’m going to keep new notes here.
The most important thing I’ve learned is that when you return to React after not using it for a while (months/years), spend time remembering how rendering works. I’ve been bitten so many times by components rendering too often or not rendering when I expect. It really helps to split components up to make each individual piece easier to understand.
Basics
Section titled Basics- Avoid getDerivedStateFromProps in almost all cases (reference)
- If a parent component is re-rendered, the render functions of all children will be called, but that doesn’t necessarily mean that the DOM will change (great article here explaining this). Still, you should generally structure components so that they’re not being updated unless they should actually result in a rerender or effect.
<ComponentRerenderingConstantly>
<ComponentLiterallyJustOutputtingHelloWorld/> ← this component’s render function is called frequently
</ComponentRerenderingConstantly>
React.createElement
Section titled React.createElementRemember that every single piece of JSX boils down to a React.createElement call (reference). This is helpful if you ever need to construct an element out of variables and can’t for some reason. For example, suppose you want to render either SomeReactElement or a div based on a condition. You can’t do this:
Instead, you need to use React.CreateElement:
Functional components
Section titled Functional componentsIf you ever find yourself needing a function that you’d typically need “.bind” on from a functional component, then you need to switch to a class for that component to avoid rebinding on every render.
Simple example follows:
Basics
Section titled Basics- Hooks let you use state and other React features without needing a class. They allow you to reuse stateful logic without changing your component hierarchy. You can split components based on behaviors (e.g. setting up a subscription or fetching data) rather than lifecycle methods.
- They made an FAQ about hooks here.
- You can create your own Hooks. Here’s a large collection of them: https://github.com/rehooks/awesome-react-hooks
- ⚠ The states of reused Hooks are completely independent from one another (reference).
- You typically name custom hooks starting with “use”.
- Hooks can go in their own files for easy usage; look at this simple example (or really any hook you can find online).
- Rules around Hooks (reference)
- Only call them at the top level, not from loops, conditions, or nested functions
- Only call them from React function components, not from regular JavaScript functions.
Gotchas
Section titled Gotchas- Any of the Hooks with dependencies (useEffect, useMemo, useCallback, useImperativeHandle) should be careful about how functions are called (reference). In general, this is because of JavaScript closures. Read the FAQ linked for a full example.
useState (AKA the “State Hook”) (reference)
Section titled useState (AKA the “State Hook”) (reference)- Simple example (reference):
- The Hook returns the current state and a function to set the state. It only takes one argument: the starting state (which is used during the first render). You can have this be of any type that you want. However, if it’s a function, then it represents lazy initialization of the value and will only be executed on the initial render (reference).
- This means that if you’re trying to store a function in the state, you’ll need to do something like this:
- Option #1: a function that returns a function
- Option #2: store your function in an object or array
- You have to call useState in the same order during every render, otherwise it won’t know which pieces of state to return.
- Compared to setState (which is not a Hook), with useState, you have to set the entire state every time rather than just the subset of it that changed.
useEffect (AKA the “Effect hook”) (reference)
Section titled useEffect (AKA the “Effect hook”) (reference)- This is the Hook for handling side effects, which React just calls effects. Those would be something like modifying the DOM, setting up a subscription, etc.
- This hook is called after every render, including the first, so it’s like componentDidMount and componentDidUpdate.
- You can return a function from the effect which essentially is a cleanup function like componentWillUnmount.
- useEffect has two arguments:
- First: the function that you want to call whenever a render happens
- Second (optional): an array of properties.
- If specified…
- If any of these have changed, the effect will be called.
- If you specify an empty array, then the effect will only be called once at the initial render.
- If you don’t provide this at all, then the effect will run on every render.
- You can provide props if you want for this argument.
- If specified…
- Using effects correctly is very difficult once you start having actual logic in your application. Dan Abramov wrote a long article about how to use them correctly.
Other hooks (reference)
Section titled Other hooks (reference)- useContext - this lets you subscribe to the React context without introducing nesting. It’s used to share data between components without a store. If rerendering becomes expensive, you can memoize, e.g. with React.memo or useMemo.
- useReducer - lets you manage state of components using a reducer
- useRef - it’s like useState, but it gives you a mutable value without having to explicitly call a setter (reference). Also, you access that value via “.current”.
- useMemo - memoize a value based on dependencies (passed as the second argument)
- useCallback - return a memoized function based on dependencies (passed as the second argument)
- Note: the function you provide to useCallback can take arguments as shown here.
- [useRef + useEffect] vs. useCallback: I wanted to select the text of an <input/> on mount, but not every time it was clicked. I thought I’d use a ref and then call ref.current.select() in useEffect, but it didn’t work. The React FAQ mentions how to do this (see this Twitter thread):
- useCallback - return a memoized function based on dependencies (passed as the second argument)
- useMemo vs. useCallback (reference): useMemo is for caching the return value of a function; useCallback is for caching the function itself.
Tips and tricks
Section titled Tips and tricksThe “key” prop is required for elements in a list so that the elements can be uniquely identified (reference). However, a neat tip is that you can use keys to force recreation of components, e.g. by changing <User key=“iteration-1”> to <User key=“iteration-2”>.
I don’t have a specific example of where this might help, but general examples are:
- Wanting to re-run some on-mount effects
- Wanting to clear animation state (e.g. your component animates from a “start” state to an “end” state, but there’s no smooth transition from “end” → “start”, so you simply recreate the component)
Helpful libraries
Section titled Helpful libraries- Framer Motion for animations
Troubleshooting
Section titled TroubleshootingContext.Provider and new instances (reference)
Section titled Context.Provider and new instances (reference)I’m adding this to troubleshooting because this didn’t work how I expected, but it’s not a bug with React or anything.
Summary: I expected a Context.Provider to be a singleton, meaning anywhere I did something like <AuthProvider>, it would always have the same value given to it. That’s not how it works.
I had this code:
Then, in a component somewhere down the tree, I had this:
In my AuthProvider itself, I was keeping track of authentication state, so it was in charge of calling a “login” function on the server. I noticed that the function was being called multiple times. It was because each <AuthProvider> is its own instance as opposed to something like a singleton. The documentation says this: “Providers can be nested to override values deeper within the tree.”. To fix this, I just passed information that would have been provided by AuthProvider directly to <AccountPopup>, that way I didn’t need two AuthProviders.
Stale data from a hook
Section titled Stale data from a hookThere are tons of potential reasons for this, but just like my issue with contexts/providers (reference), hooks do not share state!