Deep React
đ tl;dr: Notes on React Deepdives
-
Either wrap a method dependecy in a
useCallback
or move the method within the body of the effect: -
If youâre writing a
custom Hook
, itâs recommended to wrap any functions that it returns intouseCallback
. This ensures that the consumers of your Hook can optimize their own code when needed. -
If you forget the dependency array,
useCallback
will return a new function every time. -
The
context
from auseContext
itself does not hold the information, it only represents the kind of information you can provide or read from components. -
React automatically re-renders components that read some context if it changes.
-
useContext()
always looks for the closest provider above the component that calls it. It searches upwards and does not consider providers in the component from which youâre callinguseContext().
-
Extract Contexts into Reducers and Hooks to expose the methods to scale them:
Moving the
useContext
into a hook doesnât change the behavior in any way, but it lets you later split these contexts further or add some logic to these functions. Now all of the context and reducer wiring is in TasksContext.js. This keeps the components clean and uncluttered, focused on what they display rather than where they get the data. -
Wrapping
Context
values inuseCallback
anduseMemo
will help optimize the values in larger applications: -
Inline style properties are written in camelCase. For example, HTML
<ul style="background-color: black">
would be written as<ul style={{ backgroundColor: 'black' }}>
in your component. -
useEffect
is a Hook, so you can only call it at the top level of your component or your own Hooks. You canât call it inside loops or conditions. If you need that, extract a new component and move the state into it. -
If youâre not trying to
synchronize
with some external system, you probably donât need an Effect. -
You donât need Effects to transform data for rendering. For example, letâs say you want to filter a list before displaying it. You might feel tempted to write an Effect that updates a state variable when the list changes. However, this is inefficient. When you update the state, React will first call your component functions to calculate what should be on the screen. Then React will âcommitâ these changes to the DOM, updating the screen. Then React will run your Effects. If your Effect also immediately updates the state, this restarts the whole process from scratch! To avoid the unnecessary render passes, transform all the data at the top level of your components. That code will automatically re-run whenever your props or state change.
-
You donât need Effects to handle user events. For example, letâs say you want to send an
/api/buy POST
request and show a notification when the user buys a product. In the Buy button click event handler, you know exactly what happened. By the time an Effect runs, you donât know what the user did (for example, which button was clicked). This is why youâll usually handle user events in the corresponding event handlers. -
When something can be calculated from the existing props or state, donât put it in state. Instead, calculate it during rendering.
-
Try to write every
Effect
as an independent process and think about a single setup/cleanup cycle at a time. It shouldnât matter whether your component is mounting, updating, or unmounting. When your cleanup logic correctly âmirrorsâ the setup logic, yourEffect
is resilient to running setup and cleanup as often as needed. - If youâre not connecting to any external system, you probably donât need an Effect.
-
Effects are an escape hatch: you use them when you need to step outside React and when there is no better built-in solution for your use case.
-
Downsides of
effects
tofetch
data:- The run only on client: This means the Server rendered app will not have the data and hence will need to run the full JS on Client side again
- Fetching directly in Effects makes it easy to create
network waterfalls
. - Fetching directly in Effects usually means you donât preload or cache data. For example, if the component unmounts and then mounts again, it would have to fetch the data again.
-
You canât âchooseâ the dependencies of your Effect. Every reactive value used by your Effectâs code must be declared as a dependency.
-
Using
updater methods
while setting state removes the inifinite render cycle:
-
Avoid using a function or an object created during rendering as a dependency. Instead, declare it inside the Effect.
-
Effect Events
are not reactive and must always be omitted from dependencies of yourEffect
. The code below means that only on change ofurl
will we re-reun the effect but not on change ofshoppingCart
:function Page({ url, shoppingCart }) { const onVisit = useEffectEvent(visitedUrl => { logVisit(visitedUrl, shoppingCart.length) }); useEffect(() => { onVisit(url); }, [url]); // â All dependencies declared // ... }
-
This is what lets you put non-reactive code (where you can read the latest value of some props and state) inside of them.
-
If you have cleanup code without corresponding setup code, itâs usually a code smell.
-
The primary benefit of
useId
is thatReact
ensures that it works with server rendering. During server rendering, your components generate HTML output. Later, on the client, hydration attaches your event handlers to the generated HTML. For hydration to work, the client output must match the server HTML. -
JSX
is a syntax extension, whileReact
is a JavaScript library. -
JSX
looks like HTML, but under the hood it is transformed into plainJavaScript
objects. -
props
are immutable - Donât put numbers on the left side of &&.
-
Even though event handlers are defined inside your component, they donât run during rendering! So event handlers donât need to be pure.
-
e.stopPropagation()
prevents the event from bubbling further. -
If we want to capture events even if they have stopped propagation say for analytics:
<div onClickCapture={() => { /* this runs first */ }}> <button onClick={e => e.stopPropagation()} /> <button onClick={e => e.stopPropagation()} /> </div>
Capture
events are useful for code like routers or analytics -
Event handlers are the best place for side effects.
-
Local variables donât persist between renders and change to local variables do not trigger renders.
-
Unlike props, state is fully private to the component declaring it.
-
Updating your componentâs state automatically queues a render.
-
On initial render, React will call the root component.
-
For subsequent renders, React will call the function component whose state update triggered the render.
- The default behavior of rendering all components nested within the updated component is not optimal for performance if the updated component is very high in the tree.
-
A state variableâs value never changes within a render, even if its event handlerâs code is asynchronous.
-
You can mentally substitute state in event handlers, similarly to how you think about the rendered JSX.
-
Each state is snapshotted to a particular time with the value of that state.
-
React queues updater methods hence we are able to use those to trigger Batched updates.
- For Something like below:
The problem is that if the parent component passes a different value of messageColor later (for example, âredâ instead of âblueâ), the color state variable would not be updated! The state is only initialized during the first render.function Message({ messageColor }) { const [color, setColor] = useState(messageColor); }
- A component is âcontrolledâ when the important information in it is driven by props rather than its own local state
- Advice on writing good reducers: Each action describes a single user interaction, even if that leads to multiple changes in the data.
- When you want a component to ârememberâ some information, but you donât want that information to trigger new renders, you can use a ref.
-
// Inside of React function useRef(initialValue) { const [ref, unused] = useState({ current: initialValue }); return ref; }
- If much of your application logic and data flow relies on refs, you might want to rethink your approach.
- We can use
console.time
to time operations:console.time('filter array'); const visibleTodos = filterTodos(todos, tab); console.timeEnd('filter array');
useMemo
wonât make the first render faster. It only helps you skip unnecessary work on updates.- Even
jsx
can be memoized because it is an object in the end:const children = useMemo(() => <List items={visibleTodos} />, [visibleTodos]);
memo
is preferred in this case because it makes the code more readable usually.- Code like below defeats memoization as the object will be created again and again
function Dropdown({ allItems, text }) { const searchOptions = { matchMode: 'whole-word', text }; const visibleItems = useMemo(() => { return searchItems(allItems, searchOptions); }, [allItems, searchOptions]); // đ© Caution: Dependency on an object created in the component body // ...
- Moving
searchOptions
down within theuseMemo
makes for a nice solution:function Dropdown({ allItems, text }) { const visibleItems = useMemo(() => { const searchOptions = { matchMode: 'whole-word', text }; return searchItems(allItems, searchOptions); }, [allItems, text]); // â Only changes when allItems or text changes // ...
- Just as
{}
creates a different object, function declarations likefunction() {}
and expressions like() => {}
produce a different function on every re-render. - In the rare case that you need to force
React
to update the screen earlier, for example to access the DOM, you can useflushSync
. - Always return new objects from your reducer.
- Do not write or read
ref.current
during rendering. - You can read or write
refs
fromevent handlers
oreffects
instead:function MyComponent() { // ... useEffect(() => { // â You can read or write refs in effects myRef.current = 123; }); // ... function handleClick() { // â You can read or write refs in event handlers doSomething(myOtherRef.current); } // ... }
- To create a portal, call
createPortal
, passing someJSX
, and theDOM
node where it should be rendered:import { createPortal } from 'react-dom'; <div> <p>This child is placed in the parent div.</p> {createPortal( <p>This child is placed in the document body.</p>, document.body )} </div>
- Call
createRoot
to create a React root for displaying content inside a browser DOM element:import { createRoot } from 'react-dom/client'; const domNode = document.getElementById('root'); const root = createRoot(domNode);