镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

useEventListener does not check to see if event target is valid, which can break SSR #978

Closed

Description

@JoeDuncko

Prior Issues

Are there any existing issues or PRs that relate to this problem? If so, link them here.

What is the current behavior?

As reported by damntv on Discord, useEventListener currently does not check the event target is valid, which can cause an error if you are trying to target window, document, etc. during SSR.

Steps to Reproduce

useEventListener(window, "resize", () => {});

Throws an error when server side rendered because the window is not defined on the server.

What is the expected behavior?

@react-hookz/web should check to make sure the event target is valid, at least on server side, before trying to set the event.

Environment Details

  • @react-hookz/web version: 16.0.0
  • react version:
  • react-dom version:
  • typescript version:
  • OS:
  • Browser:
  • Did this work in previous versions? no

Activity

  1. ArttuOll commented on Oct 29, 2022

    @ArttuOll
    Contributor

    How should we go about fixing this?

    Could it be just a simple isBrowser check before adding the event listener or maybe even setting the target parameter to null if isBrowser is false?

    I attempted this, but found that I couldn't write a test which would reveal this bug, since in the SSR tests window can't be referenced at all. In fact, I launched a fresh NextJS project and learned that it won't even build if I give window to useEventListener directly. All of this makes sense in a SSR setting. The workaround suggested in the Discord by damntv works in the tests and NextJS: useEventListener(isBrowser ? window : null, ..., ...).

    This begs the question: how would we move the isBrowser check inside of the hook in order for the users to be able to just do this in SSR:

    useEventListener(window, ...);

    Is this even possible (disclosure: I have little experience with SSR)?

  2. JoeDuncko commented on Nov 2, 2022

    @JoeDuncko
    ContributorAuthor

    I attempted this, but found that I couldn't write a test which would reveal this bug, since in the SSR tests window can't be referenced at all.

    Doesn't that actually test the bug exactly? Assuming we don't have window when running SSR tests, that should be a great test case as that's the specific error we are trying to avoid not get during SSR.

    My question is, do we have window provided during our not-SSR tests? If so, then I think that we could have this test in both test suites - and make sure the hook does not run but does not error during SSR, and does run client side.

  3. ArttuOll commented on Nov 4, 2022

    @ArttuOll
    Contributor

    My question is, do we have window provided during our not-SSR tests? If so, then I think that we could have this test in both test suites - and make sure the hook does not run but does not error during SSR, and does run client side.

    We provide null during tests. Providing window makes the tests fail, because window cannot be referenced server-side, which is why it cannot even be passed to useEventListener server-side. useEventListener(isBrowser ? window : null, ..., ...) works, because if isBrowser === false then window is not evaluated. If we moved the check inside the hook and passed window directly to the hook, window would be evaluated and error thrown.

    The hook itself uses the provided event target only inside of an useEffect which runs only client-side.

    Seems to me there is no bug here at all - or I am totally wrong and learn a lesson on SSR!

  4. ArttuOll commented on Nov 4, 2022

    @ArttuOll
    Contributor

    This makes me wonder, should we then perhaps support giving globalThis as the target as suggested in #961 ? Then users could just useEventListener(globalThis, ...) and everything should work. Currently the type of the target parameter does not allow this.

  5. ArttuOll commented on Nov 26, 2022

    @ArttuOll
    Contributor

    Any more thoughts on this? If not, I think this issue could be closed. Would love to see the issue list shrink a bit more!

  6. xobotyi commented on Dec 18, 2022

    @xobotyi
    Contributor

    As figured - types has changed and there is no more issues with global this, but useEventListener still not guarded against improper during SSR.
    Im planning to address this issue by changing the way eventlistener added to our internal on and off methods that has target check. (on/off methods been introduced later that useEventListener)

  7. xobotyi commented on May 2, 2023

    @xobotyi
    Contributor

    It has to be done by user, since user evaluates the window.
    isClient() ? window : null works because isClient uses typeof window under the hood and thus it wont break.

    Making target optional and placed as last call parameter will break arguments inferrence and is kinda weird.

    nextjs users shall use globalThis to avoid issues during SSR since we have nothing to do with evaluation order - no matter where you'll place target parameter - attempt to evaluate window during SSR will case a reference error.

  8. ArttuOll commented on May 2, 2023

    @ArttuOll
    Contributor

    Just to note, this hook could also probably be reimplemented with React 18's useSyncExternalStore hook, which allows us to separately define the server-side behavior. But this goes back to the discussion and problems in #1023 .

  9. xobotyi commented on May 2, 2023

    @xobotyi
    Contributor

    @ArttuOll yes, but that does not solve the issue with attempt to pass window object during SSR.
    we literally can do nothing since issue lays within user's code.

  10. ArttuOll commented on May 2, 2023

    @ArttuOll
    Contributor

    @ArttuOll yes, but that does not solve the issue with attempt to pass window object during SSR. we literally can do nothing since issue lays within user's code.

    Yeah, you're totally right. That was just a quick thought.

  11. xobotyi commented on May 3, 2023

    @xobotyi
    Contributor

    Alternatively we can change the logic to: if target is null, set target to window

    This is bad bad design with inobvious logic, we definely wont follow react-use's way.

    Even if there is no change being made in the function signature (which seems necessary to fix this)

    It is not necessary. useEventListener accepts EventTarget along with null as first argument. Hook designed to be similar with addEventListener (except acceptance of null target).

    can we at least add the information that we should set target as globalThis for ssr to work properly in both useEventListener docs and the migration guide?

    Not all SSR engines have globalThis support. I really don't understand why you blaming hook for reference error that is caused by consumer's (hence your's) code. It seemed to me that SSR users shall be cautios with window evaluation and understand the execution vlow and how to overcome the need of it's referencing.
    We physically unable to cover docs with all usecases that are possible within SSR (surprise - nextjs ist the only).
    Attempt to evaluate window on server will cause a reference error and it is beyond hook library competence, as most SSR engines has it's own way to reference the window object safely (most of them leaning toward globalThis nowadays but not all).

    The reason react-use assumed that by default developers want to add listeners to window - a big question to streamich.
    We've never targeted to make things compatible with react-use, the target is to provide more obvious and more predictable hooks withut silly defaults and assumptions hiddent behind hook's facade.

  12. xobotyi commented on May 3, 2023

    @xobotyi
    Contributor

    The only thing we can do is to add notification that SSR users have to consult the docs of their SSR engine to safely reference the window object (it is complete surprise for me someone don't understand that though).
    @ArttuOll could you please address it within migration guide, maybe?

  13. ArttuOll commented on May 5, 2023

    @ArttuOll
    Contributor

    The only thing we can do is to add notification that SSR users have to consult the docs of their SSR engine to safely reference the window object (it is complete surprise for me someone don't understand that though). @ArttuOll could you please address it within migration guide, maybe?

    I might have time for this next week.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    🪲 bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions