Repository: react
Issue: React 18 - Avoiding hydration errors, but initialize client-only state directly if possible
Labels: Type: Discussion,React 18

This question is about hydration errors and workarounds that are future-proof for React 18 partial hydration and concurrent mode.

React hydration rules say that the server rendered html needs to match the client rendered dom that is rendered during the initial render in `hydrate()`. Mismatches (=slight differences in dom output) can cause all kinds of weird behavior because React's virtual dom does not match the real dom. Such mismatches can happen when rendering based on information that is only available on the client side, but not on the server side, e.g. conditional rendering based on `typeof window !== 'undefined'`, or rendering based on data from `localStorage`.

So this component will cause a hydration error if it is contained in the initial sever-rendered html (case 1), but it would not cause a hydration error if it only appeared later after hydration (case 2):

```
const MyComponent1 = () => {
    const [viewState, setViewState] = useState(() => getViewStateFromLocalStorage())
    return <button onClick={() => setViewState(oldViewState => toggledViewState(oldViewState))>{viewState}</button>
}
```

A common workaround is to use `useEffect` to apply client data only after hydration:

```
const MyComponent2 = () => {
    const [viewState, setViewState] = useState('A')

    useEffect(()=>{
        setViewState(getViewStateFromLocalStorage())
    },[])

    return <button onClick={() => setViewState(oldViewState => toggledViewState(oldViewState))>{viewState}</button>
}
```

This workaround comes with a downside: for case 2 where the component only appears later after hydration, it would still flash from showing `"A"` first, and then the view state from `localStorage`. To make the component directly show the view state from `localStorage`, the code in `MyComponent1` would need to be used, but then the component can not be used in initial server renderings. So the component itself needs knowledge in which contexts it will be used, which is not ideal for modularity.

I currently know of no way to make the component work for both case 1 and case 2 and show the view state from `localStorage` directly in the initial render for case 2 without giving the component knowledge of its outer context.

I see 2 different theoretical approaches to solve this issue which are not yet possible in React AFAIK:

1. Signal to React that the component could cause hydration errors to make it compare the dom exactly and fix it accordingly:

```
const MyComponent: FunctionComponent = () => {
    const [viewState, setViewState] = useState(() => getViewStateFromLocalStorage())
    return <button onClick={() => setViewState(oldViewState => toggledViewState(oldViewState))>{viewState}</button>
}

MyComponent.gracefulHydrationErrors = true
```

2. Get the info from React if the current render of the component is currently rendering as part of the initial render during `hydrate()`:

```
const MyComponent = () => {
    const isHydrating = useIsHydrating()
    const [viewState, setViewState] = useState(() => isHydrating ? 'A' : getViewStateFromLocalStorage())

    useEffect(()=>{
        setViewState(getViewStateFromLocalStorage())
    },[])

    return <button onClick={() => setViewState(oldViewState => toggledViewState(oldViewState))>{viewState}</button>
}
```

Is there already a way to solve this issue properly with available apis? The solution also needs to work with React 18 partial hydration and concurrent mode.