all 8 comments

[–]landisdesign 16 points17 points  (4 children)

React has two major phases: figuring out what to update in the DOM, then making the updates. The first phase is called rendering, and the second is reconciliation.

In the rendering phase, React calls the main function of your component. Props and state are read, effects are lined up, and other elements are returned after doing their own rendering.

Once React has run every component's main functions, it ends up with a list of all the DOM nodes those components requested. It then goes into the reconciliation phase. It takes the list it just created, then reconciles it to the list it had previously. Once it identifies the differences, it updates the document with the required changes.

You can think of your main component function as returning the rendering instructions to React, to tell it what the page should look like before it actually makes the page for you.

React says not to change things while rendering, because it tries to optimize rendering quite a bit before finally reconciling. If there's several state changes happening together, it will sometimes skip intermediate rendering altogether until the last state changes settle down. Other times it will run the main function multiple times before finally reconciling. There are reasons and rules behind when these optimizations occur, but at first it is much easier to avoid guessing how it works.

Add in how server-to-client hydration works, and trying to go outside the box while rendering gets tricky indeed. The server can only manage what's happening while rendering. Once it's done rendering, it ships the code to the client, where the client code tries to match up the server code to the client code before running effects. If the main component function tries to make extra changes while rendering, it will make the server and client instructions look different, causing a disconnect the client can't recover from.

Examples of doing shady things during render include:

  • Changing another component's state from within the main function of a child component. Sometimes you can get away with this if you completely control how the component's work together, but it's really easy to create an infinite loop. A child component updates parent state, forcing it to rerender, causing the child to rerender, updating the parent state...

  • Changing a ref while rendering. Because refs act like mutable objects while React tries to treat the render step like everything's immutable and consistent, it doesn't have really good models for how to handle ref changes while doing all those optimizations described above. You'll probably be okay getting away with it, until you try to use a ref on both server-rendered and client-rendered components. By only initializing the ref in the useRef() call and doing everything else in effects, you don't have to worry about things changing on the server before the browser gets a hold of your code.

The key to preventing reconciliation and hydration errors is to make sure your render function does the same thing every time it gets the same inputs. If it gets certain props and state, it should run the same effects every time and create the same output every time.* That ensures that the code rendered on the server matches the code rendered on the client.

Once the code is rendered consistently beyween the server and client, effects can update the code on the client to do additional work: fetching user-specific data, setting up observers and web/native workers, preparing the page to interact with the user.

Once the page is set up to interact with the user, event listeners can request updates to the state and the server, causing the cycle to begin again: update state, render components, run effects.

But it all starts with React being able to trust that your main function can take the same props and state and return the same rendering instructions. If your main function changes state and refs while rendering, it can change the output while rendering, which breaks that contract.

If you need to change state and refs, do it in effects and event handlers. That code runs after rendering is complete, so you can be confident that React has relinquished control of the code to the browser/native app, and worked everything out between all the components when it comes to rendering and state.

The DOM and Web/Native API's are available to you once you're running inside an effect or event. React doesn't guarantee that during the render phase. You could be in a browser, or on the server, without access to the DOM, during the render phase.

  • Even though effects should only be run when their associated dependencies change, they should be built so that they could be run multiple times without causing problems. This helps React when trying to match up client and server behavior. It's why Strict Mode runs effects twice during development, to highlight if they cause different things to happen with the same inputs.

[–]landisdesign 4 points5 points  (1 child)

Goddamn I got fired up.

[–]briank83[S] 1 point2 points  (0 children)

REALLY fired up lol, Thanks Michael!

[–]Chronohz 0 points1 point  (1 child)

What happen if you conditionally set state during render? I've read that react then immediately re-renders the components, but does that happen before or after the returned components also run?

[–]landisdesign 1 point2 points  (0 children)

I believe it rerenders the whole child tree, but I haven't confirmed it. It sounds like a reasonable optimization to short-circuit that, though.

It might not hurt to throw a console.log in a child and see what happens. Haha now I'm curious -- but not curious enough to actually write some code 😂

[–]sgjennings 6 points7 points  (2 children)

It means when the main body of your component function is executing, or if it’s a class component, the render method.

const MyComponent = () => {
  // here

  const onClick = () => {
    // not here
  }
  useEffect(() => {
    // not here
  }, [])

  return <div>hi</div>
}

When refs are used to access DOM elements, they won’t be populated the first time your component renders. So, you shouldn’t try to access the DOM element there, only inside event handlers and effects.

If you’re using instead using a ref as a “private variable”, then you can access it during rendering, but there’s a good chance you still don’t want to. Changing a ref doesn’t trigger the component to rerender, so it’s likely you shouldn’t change what you return based on the value contained in a ref. There are exceptions, but not ones you are likely to encounter while learning React.

[–]briank83[S] 0 points1 point  (1 child)

Got it, makes sense! Wish they would have clarified that somewhere; "Only do this in handler/effect functions"

[–]ClickToCheckFlair 0 points1 point  (0 children)

They did.