-
-
Notifications
You must be signed in to change notification settings - Fork 614
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
SSR example (w/ Next.js) #15
Comments
@znk Thanks for opening up the issue. I'll be honest on this. It's not close at all. But, we would like to somehow support it, both for Jotai and Zustand pmndrs/zustand#182. My experience in SSR is limited. I'd need to learn technical details at first. Help appreciated. Thanks! |
@dai-shi React SSR doesn't support side-effects and error-boundaries. Suspense is similar to an Error Boundary that is solely tasked to catch Promises. One of the problems that is blocking for SSR support is how In SSR patterns, usually you wouldn't throw a Promise immediately, as you would check first if function SuspensefulComponent({ fallback }) {
// Render fallback immediately for SSR
if (typeof window === 'undefined') {
return <>{ fallback }</>;
}
// Allow data-fetching for clients
const data = loadSuspendedData();
return <AsyncComponent data={data} />;
} There are two solutions:
|
@lxsmnsyc Thanks for the notes! So, this is an issue with async behavior. Do you see any issues if we don't use async operations at all? I'd expect React will eventually support Suspense in server. (Actually, I did once tried catching promise and retying render in server, in one of my experimental projects, which is totally a hack.) We'd need to somehow support hydration. That's another issue, right? |
There's not much of an issue. If you separate suspended states from normal states, developers would be aware about the existence of suspended data, giving them the responsibility to handle SSR. Normal states, on the other hand, wouldn't be an issue. Consider this example:
There's not much to this but you wouldn't be aware if
I am not sure with the exact details but as I am aware, SSR skips side-effects. As for now, I only use the window check to prevent Suspense from happening during the SSR process.
I am still unsure how jotai maps states but generally initial states are recalled on the client-side, so it isn't much of an issue. I think a good reference for this to consider is Vercel's SWR, as their API supports both effect-ful and suspended data fetching. |
Could initial (or persisted) state on SSR, followed by async on hydrate, resolve this? |
Hello everyone! I finally made my first nextjs example with jotai, based on https://github.com/vercel/next.js/tree/canary/examples/with-mobx https://codesandbox.io/s/nextjs-with-jotai-5ylrj This has home-made hydration mechanism. It doesn't deal with async actions yet. |
Hey! I made another example with async get. https://codesandbox.io/s/nextjs-with-jotai-async-pm0l8?file=/store.js I see at least two issues, which I hope someone can help.
|
if (isSSR) {
const id = INITIAL_POST_ID;
// how can we guarantee the following is completed before ssr?
(async () => {
prefetchedPostData[id] = await fetchData(id);
})();
} There's no guarantee. If you want to prefetch data on server-side, you can guarantee the hydration by fetching the data through export async function getServerSideProps() {
return {
props: {
initialData: await fetchData(INITIAL_POST_ID),
},
};
}
export default function IndexPage({ initialData }) {
// should synchronously hydrate atom?
return <Index initialData={initialData} />;
}
|
@lxsmnsyc Thanks for your help!
In my example, this is avoided with
I'm not sure if I understand this one. |
@dai-shi So far, looks great. I'm sorry about the other comment, disregard what I said regarding the getInitialProps. I was actually viewing the index page and I didn't notice it, I thought the App component didn't catch the initialState props. Also, I think an example using the export async function getServerSideProps(context) {
return {
props: {
initialData: await fetchPostData(context.query.id || INITIAL_POST_ID),
},
};
} |
Thinking about this a bit, the server part shouldn't be difficult, but I'm not sure how to do it on client. As we keep the id in an atom state, we need to sync it somehow in the browser url. I expected shallow routing may help, but so far it doesn't work as expected. |
I'm getting an |
@garretteklof Hmm, not sure what causes the error. Would it be possible for you to create a small repro with codesandbox? |
@dai-shi thanks for the quick reply! It looks like it's occurring with the official Next.js starter |
Thanks for the sandbox. And, I'm totally confused. My example which worked fine no longer works after I modified package.json. Now reverting the jotai version doesn't help. Or, I don't know how to revert. |
@dai-shi I would like to help but I can't build the |
I've never seen |
@dai-shi It seems that the .ts files aside from 'utils' are not being resolved by What I did is just run |
...but the published package is the result of what I run |
@dai-shi I tried re-cloning my fork repo, ran |
dai-shi/use-context-selector#24 |
That sounds interesting... There must be some differences between your env and my env. |
https://codesandbox.io/s/nextjs-with-jotai-5ylrj @lxsmnsyc Thanks again. @garretteklof It should work if you re-install the package. |
Updated my example with jotai v0.10.4 which supports export default function App({ Component, pageProps }) {
const { initialState } = pageProps;
return (
<Provider initialValues={initialState && [[clockAtom, initialState]]}>
<Component {...pageProps} />
</Provider>
);
} |
Hi @dai-shi, Is the |
You can skip it. It's optional. |
Hi @dai-shi I'm currently using |
Here's a quick codesandbox -> https://codesandbox.io/s/nextjs-with-jotai-forked-kxesh It has to do with the nested initialState props .. for example if If I'm doing something inanely silly (quite possible) - please forgive me. I've been cranking away on a deliverable and my mind is pretty 🌁 |
https://codesandbox.io/s/nextjs-with-jotai-forked-kxesh |
State does not get updated in the new page without a refresh. The expected behavior would be the To mimic, make sure you start on |
Admittedly, I'm very new to Next.js but it seems to me part of the 'magic' of Next and their router is that it acts as a client-side transition, but reruns their data fetching functions. My confusion is the correct data indeed reaching So maybe this is in fact what was intended here, but I don't think it's what was intended for Next. |
@garretteklof Thanks for your explanation. b) would be something like this. export default function Static(pageProps) {
return (
<>
<Index />
<Color {...pageProps} />
</>
);
} const Color = ({ initialState }) => {
const [{ color }, setColor] = useAtom(colorAtom);
const art = initialState && initialState.art;
useEffect(() => {
if (art) {
setColor(art);
}
}, [art]);
return <div>{color}</div>;
}; I'm not very familiar with nextjs either, so it's not certain if this is idiomatic. |
@dai-shi cool - thank you for your follow-up. It definitely makes sense that those would be truly 'initial' values, and I realized this was far more Next.js architecture than an implementation with |
Could you share a code snippet of your solution? |
Sure. For right now, I just have a function that I pass into the jotai <Provider initialValues={init(initialState)}> The const init = (initialState = {}) => {
let values = []
const { user, map, settings } = initialState
if (user) values.push([userAtom, user])
if (map) values.push([mapAtom, { ...defaults.map, ...map }])
if (settings) values.push([settingsAtom, { ...defaults.settings, ...settings }])
return values
} Currently I'm hosting all static state defaults in a json file (that's the Then for client-side transitioning (not SSR) there's another wrapper component which is essentially the const Hydrator = ({ initial = {}, children }) => {
const [, setUser] = useImmerAtom(userAtom)
const [, setMap] = useImmerAtom(mapAtom)
const [, setSettings] = useImmerAtom(settingsAtom)
const { user, map, settings } = initial
useEffect(() => {
if (user) setUser(() => user)
if (map) setMap(() => map)
if (settings) setSettings(() => settings)
}, [user, map, settings, setUser, setMap, setSettings])
return children
} (Note that these results don't currently match - I'm overriding all state here. Also it might be cleaner to create a single 'hydrate' atom that updates all relevant state with a write update, but I'm not sure how to achieve that same pattern with Immer) And then in <Provider initialValues={init(initialState)}>
<Hydrator initial={initialState}>
...other globals
<Component {...pageProps} />
...other globals
</Hydrator>
</Provider> Another option as stated by @dai-shi is to move the jotai |
Closing this as resolved. Please open a new one if someone wants to raise a discussion. |
Hey guys. A tricky workaround to make the initial state follow the changes. It's useful for NextJS
import React, { useContext } from 'react';
import { Atom, SECRET_INTERNAL_getScopeContext } from 'jotai';
import { Scope } from 'jotai/core/atom';
const RESTORE_ATOMS = 'h';
export const HydratableJotaiProvider = ({
initialValues,
children
}: {
initialValues: [Atom<unknown>, unknown][];
children: React.ReactNode;
}) => {
useEnhancedHydrateAtoms(initialValues);
return <>{children}</>;
};
function useEnhancedHydrateAtoms(
values: Iterable<readonly [Atom<unknown>, unknown]>,
scope?: Scope
) {
const ScopeContext = SECRET_INTERNAL_getScopeContext(scope);
const scopeContainer = useContext(ScopeContext);
const store = scopeContainer.s;
if (values) {
store[RESTORE_ATOMS](values);
}
} It works just like the useHydrateAtoms <HydratableJotaiProvider initialValues={[[currentTabAtom, currentTab]]}>
{children}
</HydratableStoreProvider> |
It's a refreshing and beautiful take on state management, fitting perfectly my usecase!
Going through the source code, I don't see any re/hydration mechanism.
@dai-shi : If Jotai is SSR-ready or close to, could you set up an example? Or point out how?
Thanks!
The text was updated successfully, but these errors were encountered: