Skip to content

React

Created: 2015-08-26 17:07:23 -0700 Modified: 2023-06-01 22:29:08 -0700

The 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.

  • 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>

Remember 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:

const ElementToRender = condition ? SomeReactElement : 'div';
return <ElementToRender>; // this fails

Instead, you need to use React.CreateElement:

const elementToRender = condition ? SomeReactElement : 'div';
return React.createElement(
elementToRender,
{ propsGoHere: 'like this'},
childrenGoHere
);

If 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:

class RefExample extends Component {
constructor(props) {
super(props);
this.modalRef = React.createRef();
}
exampleFunctionUsingTheRef() {
if (!_.isNil(this.modalRef.current)) {
this.modalRef.current.getBoundingClientRect();
}
}
render() {
return (
<div ref={this.modalRef} />
);
}
}
  • 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.
  • 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.
export default function App() {
// Declare a new state variable, which we'll call "count"
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
  • 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
const [someFn, setSomeFn] = useState(() => _.noop);
setSomeFn(() => () => {/*new functionality here*/});
  • Option #2: store your function in an object or array
const [someFn, setSomeFn] = useState({myFn});
  • 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.
  • 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.
  • 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.
  • 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):
const inputRef = useCallback((node) => {
if (!_.isNil(node)) {
node.select();
}
}, []);
[…]
<input ref={inputRef} />
  • useMemo vs. useCallback (reference): useMemo is for caching the return value of a function; useCallback is for caching the function itself.

The “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)

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:

// (this was in NextJS, but the concept is the same basically anywhere)
class MyApp extends App {
render() {
const { Component, pageProps } = this.props;
return (
<div id="root" className="text-gray-800 bg-gray-100">
<AuthProvider>
<Component {...pageProps} />
</AuthProvider>
</div>
);
}
}

Then, in a component somewhere down the tree, I had this:

<Tooltip
html={
<AuthProvider>
<AccountPopup onClose={closeAccountPopup} />
</AuthProvider>
}
>

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.

There are tons of potential reasons for this, but just like my issue with contexts/providers (reference), hooks do not share state!