Skip to content

Name of useIsBrowser hook and ExecutionEnvironment is misleading and the example given in the documentation needs a note added #8678

@nynevi

Description

@nynevi

Have you read the Contributing Guidelines on issues?

Description

Hello,

Thanks for the amazing library.

The comment here states that useIsBrowser returns true only if the first hydration has completed.

But opening this Stackblits Example and checking the console, you see logs printed for the below code:

  const isBrowser = useIsBrowser();
  const traditionalIsBrowser = typeof window !== 'undefined';
  const testUrl = isBrowser ? new URL(window.location.href) : undefined;

  console.log('Has completed first hydration?', isBrowser);
  console.log('Are we actually on browser?', traditionalIsBrowser);
  console.log('Test URL:', testUrl);

Outputs before hydration, on first render:

Has completed first hydration? false
Are we actually on browser? true
Test URL: undefined

Outputs after hydration:

Has completed first hydration? true
Are we actually on browser? true
Test URL: URL {origin: 'https://githubev3fmt-xvco--3000.local-credentialless.webcontainer.io', protocol: 'https:', username: '', password: '', host: 'githubev3fmt-xvco--3000.local-credentialless.webcontainer.io', …}

So the name of useIsBrowser actually should be along the lines of useHasHydrationCompleted.

When there are a lot of window references in a component, and you try to build the application, you get this error:

It looks like you are using code that should run on the client-side only.
To get around it, try using `<BrowserOnly>` (https://docusaurus.io/docs/docusaurus-core/#browseronly) or `ExecutionEnvironment` (https://docusaurus.io/docs/docusaurus-core/#executionenvironment).
It might also require to wrap your client code in `useEffect` hook and/or import a third-party library dynamically (if any).


ReferenceError: window is not defined

So naturally your first instinct as a developer is to add <BrowserOnly> and useIsBrowser to everywhere you see window references unless you also read about ExecutionEnvironment, which I did much much later after I've already wasted tons of time.

I did not read ExecutionEnvironment up until creating this ticket because the ExecutionEnvironment did not sound like something I might need (I am burnt out enough to skip reading about it even though the error basically tells you to read it) however it's name is also misleading, I understand that it has canUseDOM for such use case but maybe it should rather be something along the lines of CanUse with:

  • CanUse.DOM
  • CanUse.IntersectionObserver
  • CanUse.Viewport

What ExecutionEnvironment.canUseDOM does is actually what useIsBrowser should be doing therefore maybe these can be extracted into several hooks with appropriate names.

As a rule of thumb while naming things, I tend to name them according to what they are doing. So these would be:

  • useIsWindowDefined
  • useAreEventsInWindow
  • useIsIntersectionObserverInWindow
  • useIsScreenInWindow

When you have code like this:
const testUrl = new URL(window.location.href),
you end up making it (that is also what useIsBrowser documentation tells you to do)
const testUrl = isBrowser ? new URL(window.location.href) : undefined

But because you just added isBrowser and make it purposefully return something else such asundefined, the code dependent starts acting as it should not. Then if you have tons of code that relies on testUrl and you suddenly return undefined with it, you end up modifying the entire stack of code to compensate for testUrl being undefined.

Now on the same Stackblitz example change the URL to include ?params=test123. You will see in the page under "Docusaurus Tutorial - 5min" area, the value rendered is "defaultValue" which is wrong (actually correct in terms of how React works with useState however wrong in terms of the end business goal). The correct would be test123

You start digging into what might be the reason and come to realization that you actually don't need to put isBrowser ? there at all because window.location.href in the browser is actually defined because after all literally the log you see is being printed in browser's console so why useIsBrowser is false, you start to wonder. When you remove isBrowser ? the code starts working but then the build fails with the above message again. Then you either go back to the documentation to read about that non-bold part easy to miss or dig into the source code of Docusaurus like I did to learn about Perils of Hydration.

As it is very cumbersome that you have to not only compensate now for the value being undefined, you also need to compensate for the fact that it may have falled back to another value. You start adding isBrowser checks for those parts too like const someQueryParam = isBrowser ? useState(isBrowser ? then it starts to not feel the right thing to do and look for another solution. I ended up doing this Stackblitz example with a proxy. Include ?params=test123 in the URL and the value rendered is now the expected value. Just to remind you that I did not read about ExecutionEnvironment yet at this point but even if I did, I would still be forced to do this proxy implementation with ExecutionEnvironment.canUseDom instead of traditionalIsBrowser in that code.

I would normally suggest changing the implementation of useIsBrowser to typeof window !== 'undefined' because as the developer, the goal of yours is to have your app build successfully by bypassing window being undefined. You don't care about perils of hydration, you just want to get your app built. But I've run into the problem of mismatching rendered HTML tons of times in NextJS and I know how painful it is to debug that so it makes sense to keep the best practice and don't change the implementation.

So an alternative we can do is to change the name of useIsBrowser to useHasHydrationCompleted but that would lead to many dependent applications breaking on the next version. Then we could add another hook useIsWindowDefined that does typeof window !== 'undefined' and then document it together with hasHydrationCompleted so people that are just getting started could use them together. Existing applications could maybe utilize codemods, and on next upgrade of an existing application, the codemod could run automatically and rename it and if it is possible that the codemod can add, then also add the useIsWindowDefined automatically. We can also link in the documentation simplified and shorter version of The perils of Rehydration article to help them explain why both of them need to be used.

Alternatively, if we don't change any names and leave it misleading as is, I have another suggestion in addition to having new hook called useIsWindowDefined (as changing name of ExecutionEnvironment, is yet another set of breaking changes and codemodes to write):

  1. improve the error message printed when build fails due to window problems to also document useIsBrowser linking to the documentation

It looks like you are using code that should run on the client-side only.
To get around it, try using one of:

It might also require to wrap your client code in useEffect hook and/or import a third-party library dynamically (if any).

  1. improve useIsBrowser section that explains after first client render part a bit more clearly:

You can also use the useIsBrowser() hook to test if the component has completed it's hydration. It returns false in SSR and true is CSR but only after first client render. If your build fails because window is undefined, and you actually need to check if you are in browser environment, you should combine this hook with useWindowIsDefined hook because this hook will return false when actually in browser environment and hydration has not completed and this may result in some of your code client side code that also rely on window not working properly. The example below kind of indicates that window.location.href is only available after first client render has completed, however it is actually already available when typeof window is defined, even before the first hydration has completed.

Use this hook if you only need to perform certain conditional operations on client-side, but not render an entirely different UI.

I can have a PR for this, but I am not a native English speaker thus the wording would require a review on the PR and also I just need confirmation which path to take, as what I think "may be right" could be wrong for someone else thus we should come up with an optimal solution as the community then create a PR for it

Self-service

  • I'd be willing to address this documentation request myself.

Metadata

Metadata

Assignees

No one assigned

    Labels

    documentationThe issue is related to the documentation of Docusaurus

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions