Open wilbert-abreu opened 4 years ago
I'm an open source noob, so please let me know if I can ask this question in a better way! 😅
Hi @wilbert-abreu. I am not 100% sure what you mean by "update state during SSR".
It is unsafe to change an Atom's value during SSR. You would need to fetch the data before you SSR and then include that data again in the response so the client can access it.
I've taken the Redux tutorial on this and modified it for Recoil (haven't tested but should give an idea of how this can be done).
Server:
async function ssr() {
const data = await getData();
const html = ReactDOMServer.renderToString(
<RecoilRoot initializeState={initRecoilState(data)}>
<App />
</RecoilRoot>
);
return `
<!doctype html>
<body>
<div id="root">${html}</div>
<script>
// WARNING: See the following for security issues around embedding JSON in HTML:
// https://redux.js.org/recipes/server-rendering/#security-considerations
window.__PRELOADED_STATE__ = ${JSON.stringify(data).replace(
/</g,
"\u003c"
)};
</script>
<script src="/static/bundle.js"></script>
</body>
`;
}
Client:
function hydrate() {
const data = window.__PRELOADED_STATE__;
// Allow the passed state to be garbage-collected
delete window.__PRELOADED_STATE__;
hydrate(
<RecoilRoot initializeState={initRecoilState(data)}>
<App />
</RecoilRoot>,
document.getElementById("root")
);
}
Hey @acutmore, thanks for replying and giving that example. 💯 On the server, after renderToString
is run we'd have generated some internal recoil state tree on first pass. How do we then save that initial state generated on the server, and pass it to the client. For the redux docs you mentioned, I'm asking about the equivalent of store.getState()
below.
function ssr() {
const store = createStore(counterApp)
const html = ReactDOMServer.renderToString(
<Provider store={store}>
<App />
</Provider>
)
// Grab the initial state from our Redux store
const preloadedState = store.getState()
res.send(renderFullPage(html, preloadedState))
}
Hi @wilbert-abreu. No problem!
There is an unstable API for 'listening' to Recoil state changes (docs). Unfortunately this won't work with renderToString
as it uses useEffect
(see here) and this won't be triggered by ReactDOMServer
.
On the server, after renderToString is run we'd have generated some internal recoil state tree on first pass
Presumably any recoil state that has been created during the first pass are the Recoil Atom's default values? If so then the same defaults will be picked up by the client. If you could provide any more information about the type of state you are building that would be really useful 😀
Hmm @wilbert-abreu well you shouldn't be calling dispatch in a render function so maybe you're doing it from a constructor in a React class? Meanwhile, any actions you dispatch from a constructor wouldn't impact the props on that component anyway. Would actions be processed in the store before nested components render? I actually want to know. What's your use case? Or else, maybe close this.
Hey @morgs32 @acutmore, my use case was something similar to this (expanding on the example above)
function ssr() {
const store = createStore(counterApp)
const html = ReactDOMServer.renderToString(
<Provider store={store}>
<App />
</Provider>
)
const data = fetchData();
dispatch(LOAD_DATA, { payload: data })
if(isMobileUser) {
dispatch(USER_IS_MOBILE)
}
// Grab the initial state from our Redux store
const preloadedState = store.getState()
res.send(renderFullPage(html, preloadedState))
}
// client
const preloadedState = window.INITIAL_STATE;
const store = createStore(
createReducer(),
preloadedState,
);
hydrate(
<Provider store={store}>
<App />
</Provider>
, document.getElementById('root'));
@wilbert-abreu I think this is the operative line from your earlier post:
On the server, after
renderToString
is run we'd have generated some internal recoil state tree on first pass.
First off, redux isn't generating internal state from rendering. renderToString
of the component tree isn't generating/changing state (or it shouldn't https://github.com/facebookexperimental/Recoil/issues/131#issuecomment-642173003). It creates a state when you createStore
because it passes an action to all the reducers (@@INIT
I think). So store.getState()
should return the same thing before and after renderToString
. Maybe you can verify that?
But dispatch
will obviously change state. The dispatches you do after renderToString aren't going to impact the html you generated from renderToString
, right?
Anyway - since redux isn't generating internal state just from its initial render - I doubt recoil is. But I'm not sure! For example, I wonder if in this example from the docs if the set
property in the tempCelcius
selector didn't check for the defaultValue
on tempFahrenheit
, would it change the value of the tempFahrenheit
atom synchronously?
const tempFahrenheit = atom({
key: 'tempFahrenheit',
default: 32,
});
const tempCelcius = selector({
key: 'tempCelcius',
get: ({get}) => ((get(tempFahrenheit) - 32) * 5) / 9,
set: ({set}, newValue) =>
set(
tempFahrenheit,
newValue: 'GOTCHA'
),
});
function TempCelcius() {
const [tempC, setTempC] = useRecoilState(tempCelcius);
const [tempF, setTempF] = useRecoilState(tempFahrenheit);
}
I'd have to create a sandbox to check. In the above I set GOTCHA in the selector. And then I read from the selector first in TempCelsius
. So I wonder what tempF
will look like...
Follow up to #5, I understand
The only thing to be aware of is that Recoil is bound to a particular React root and hence a particular renderer.
, but does that mean that we'll never be able to update state during SSR?Specifically, let's say I fetch some data during SSR(but before the page is rendered) and I'd like to initialize this state on the client via RecoilRoot's initializeState prop.