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?
馃檹 鉂わ笍
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.
items
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
Most helpful comment
@gustavpursche
useEffect
andcomponentDidMount
both don't run during SSRuseEffect
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?