ioof-holdings / redux-dynostore

These libraries provide tools for building dynamic Redux stores.
BSD 3-Clause "New" or "Revised" License
122 stars 15 forks source link

Since 3.1.1 SSR is not working anymore [@redux-dynostore/react-redux] #473

Closed jlowcs closed 3 years ago

jlowcs commented 3 years ago

Is it a bug, feature request or question?

Since I bumped to 3.1.1, any SSR component wrapped with dynamic simply does not render anymore.

Which package(s) does this involve?

Probably @redux-dynostore/react-redux

Input Code

export default dynamic('demo', attachReducer(reducer))(
    () => {
        return <>Hello</>;
    },
)

Expected Behavior

When rendered through SSR, Hello should be part of the resulting HTML.

Current Behavior

When rendered through SSR, Hello is not part of the resulting HTML.

Possible Solution

The issue probably comes from the use of useEffect in https://github.com/ioof-holdings/redux-dynostore/commit/b1b64de737bb4205ffe52477133375c6254d9e2a#diff-c823af5b397268b51748e78b703dc3c6c0cf3e6ef10392c6b028b1b577d68ec5R29, which is not run when rendering on the server.

I tried changing it to:

return EnhancedComponent ? <EnhancedComponent identifier={identifier} {...props} ref={ref} /> : <Component {...props} />;

But I'm ending up with a missing property in the store, even if I initialised the store with that property. It seems that it is removed by dynostore for some reason.

Context

This makes any of my SSR pages that use dynostore into CSR pages...

Your Setup

package version(s)
redux 4.0.4
redux-dynostore 3.1.1
react 16.13.1
jlowcs commented 3 years ago

Apparently we get the warning only when we're trying to attach a reducer after the first app render, while we need to synchronously attach the reducer when rendering server-side, or when hydrating. Those are actually compatible if we detect the first render and only do the async reducer attachment when not on the first render.

So, I tried the following:

const [EnhancedComponent, setEnhancedComponent] = useState(typeof window !== 'undefined' && window.afterFirstRender ? null : () => {
        setEnhancedComponent(() => dynamicEnhancer(store)(Component))
})

With this at the root at the app:

    useEffect(() => {
        window.afterFirstRender = true;
    }, []);

And that fixes both issues (the warning fixed by 3.1.1 and the SSR not rendering).

Obviously that code is not a great solution, but it does confirm that my hypothesis is right.

Any idea on how to do something similar in a better way?

jlowcs commented 3 years ago

Another approach would be to only switch to async reducer attachment when on client (ie window is present), and when not hydrating (which I don't know how to detect).

mpeyper commented 3 years ago

Hi @jlowcs,

yes, I think your hypothesis is right, and honestly SSR was not considered when making the change (we've never actively supported it, but never tried to prevent it either).

I'll take a quick look today, but I'd consider taking a hacky fix to keep you moving if it takes longer than that to find a cleaner fix.

Thanks for the investigation effort as well. I wish more issues gave me this much help finding the source of the problem.

jlowcs commented 3 years ago

I already implemented a temporary fix in our app by copy/pasting the dynamic function and using a useIsAppFirstRender() hook that I already had available.

Not great, but that'll do the job until we find a better and cleaner fix.