Gatsby: Render componente server side, using useEffect with window side effects

Created on 9 May 2019  路  9Comments  路  Source: gatsbyjs/gatsby

Summary

Hi 馃憪

Thanks again for the amazing work you do with Gatsby. I'm having an issue at the moment and would like to seek an advice:

I'm having a component, which uses the useEffect hook, to keep a state in sync with the URL to persist filter params:

const Page = () => {
  const INITIAL_STATE = {
    authors: getFilterFromUrl('authors') || []
  };
   const [filter, setFilter] = useState({
    ...INITIAL_STATE
  });

  useEffect(() => {
    setUrlForFilter('authors', state.authors);
  }, [filter]);

  return /* ... */;
}
const setUrlForFilter = (name, value) => {
  const url = new URL(window.location.href);
  url.searchParams.set(name, value);
  window.history.pushState('', '', url);
};

However, this code would not render server side. I then tried to wrap useEffect in the recommended conditional

if (typeof window !== `undefined`) {
  useEffect(() => {
    setUrlForFilter('authors', state.authors);
  }, [filter]);
}

no difference. The same if I extract all the logic into another component and wrap the conditional around it.

So, I'm wondering: is there a way to make this possible without componentDidMount()? What else could I try?

馃檹 鉂わ笍

question or discussion

Most helpful comment

@gustavpursche useEffect and componentDidMount both don't run during SSR

useEffect will be a no-op for server side rendering.

Set the initial state to all items and in useEffect (which is only run client side after hydration) check the url and update state?

All 9 comments

I don't know if it will fix your actual problem but hooks should not be inside conditionals.
https://reactjs.org/docs/hooks-rules.html

Don鈥檛 call Hooks inside loops, conditions, or nested functions.

So given that, I would write it:

  useEffect(() => {
    if (typeof window !== `undefined`) {
      setUrlForFilter('authors', state.authors);
    }
  }, [filter]);

componentDidMount also will not run during SSR.

It seems to me that your issue is not so much with the effect and the call to setUrlForFilter, but rather is with the INITIAL_STATE... I'm guessing that you are wanting your SSR HTML output to default to displaying authors, but your call to getFilterFromUrl() is not returning anything in the SSR scenario (due to Url being unavailable on the server).

You said this code would not render server side. What do you mean by this? Is it giving error when you run gatsby build? I guess you are getting an error since getFilterFromUrl would likely use window object, which doesn't exist on the server? Do you mind sharing the code for getFilterFromUrl?
Also, what are you expecting the server-rendered output to be?

@Kevin-Hamilton @universse Ah, sorry I wasn't very precise on that: I want the code to return all items from the redux store during server rendering, and only filter the list, once window and a URL parameter is present.

  • While SSR: return every item and just display a static list containing all items
  • While CSR: check if the URL contains a parameter and filter down items

In pseudo code the scenario might look like:


const Page = ({ data: { items } }) => {
  const [state, setState] = useState({
   items: items.filter(item => {

     /* check if window is in context (client side) and the URL contains a filter parameter */
     if (typeof window !== 'undefined' && urlHasParam()) {
       /* in case the URL contains a parameter, filter the list */
       return item.param === urlParam && item;
    }

    /* otherwise (window is not present, SSR), return every item */
    return item;
    })
  });

  useEffect(() => {
    if (typeof window !== 'undefined') {
     /* sync state with the URL */
    }
  }, [state]);

  return (
    <>
      state.items.map()
    </>
  );
}

Is this understandable? :)

@williamtstanley Thanks for the hint - you are absolutely right, but unfortunately this doesn't fix the problem.

@gustavpursche useEffect and componentDidMount both don't run during SSR

useEffect will be a no-op for server side rendering.

Set the initial state to all items and in useEffect (which is only run client side after hydration) check the url and update state?

@sidharthachatterjee Setting the state within useEffect is a great idea. Will try that! 馃憤

Wohoo, that worked. 馃殌 Thank you so much @sidharthachatterjee and everyone else!

@sidharthachatterjee seeing as useEffect doesnt run during SSR, is it still required to do checks for window and document inside of it? 馃

No need for that

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jimfilippou picture jimfilippou  路  3Comments

brandonmp picture brandonmp  路  3Comments

rossPatton picture rossPatton  路  3Comments

signalwerk picture signalwerk  路  3Comments

KyleAMathews picture KyleAMathews  路  3Comments