A Hero Image for the blog post

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: useCallback removal of method deps

  • If you’re writing a custom Hook, it’s recommended to wrap any functions that it returns into useCallback. 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 a useContext 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 calling useContext().

  • 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 in useCallback and useMemo will help optimize the values in larger applications: Optimized context usage

  • 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, your Effect 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 to fetch 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 your Effect. The code below means that only on change of url will we re-reun the effect but not on change of shoppingCart:

    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 that React 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, while React is a JavaScript library.

  • JSX looks like HTML, but under the hood it is transformed into plain JavaScript 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:
    function Message({ messageColor }) {
        const [color, setColor] = useState(messageColor); }
    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.
  • 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 the useMemo 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 like function() {} 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 use flushSync.
  • Always return new objects from your reducer.
  • Do not write or read ref.current during rendering.
  • You can read or write refs from event handlers or effects 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 some JSX, and the DOM 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);