Closed Blitz2145 closed 3 years ago
Yea that's definitely an expected use case and we do it all the time. Can you paste some example code? Without seeing anything my guess is that there's no suspense boundary around the part of the page using the refetchable fragment. relay-nextjs
only adds a suspense boundary on client-side navigations to avoid rendering <Suspense>
on the server (a hard error in React 17).
To get around this I recommend using next/dynamic
with {ssr: false}
, but still spreading the fragment so the data is fetched on the server. This incurs a minor perf penalty that will go away once React 18 is out 😄
The next/dynamic
+ {ssr: false}
approach is detailed more in the lazy-loaded query docs, but you'll have to adapt it for useRefetchableFragment
.
Here's a code snippet with names redacted, general pattern is the same, rendering a list with refetchable fragment in the items.
import React from "react";
import { withRelay, RelayProps } from "relay-nextjs";
import {
graphql,
useFragment,
usePreloadedQuery,
useRefetchableFragment,
} from "react-relay/hooks";
import { getClientEnvironment } from "../lib/relay_client_environment";
import { styled } from "../stitches.config";
import { Shell } from "../components/Shell";
import { blahQuery } from "../__generated__/blahQuery.graphql";
import { blahLocationFragment_blah$key } from "../__generated__/blahLocationFragment_blah.graphql";
const BlahQuery = graphql`
query blahQuery {
blahs(first: 10) {
edges {
cursor
node {
id
pk
...blahLocationFragment_blah
}
}
}
}
`;
const Card = styled("div", {
display: "inline-grid",
gap: "2ch",
backgroundColor: "$surface2",
padding: "2ch",
borderRadius: "1ch",
boxShadow: "0 2px 5px rgba(0, 0, 0, .2)",
});
function Blahs({
preloadedQuery,
}: // eslint-disable-next-line @typescript-eslint/ban-types
RelayProps<{}, blahQuery>): JSX.Element {
const query = usePreloadedQuery(BlahQuery, preloadedQuery);
return (
<Shell>
<Card>
<h2>Blahs</h2>
<ul>
{query.blahs.edges.map((edge) => {
return (
<li key={edge.node.id}>
ID: ({edge.node.pk})
<BlahLocation blah={edge.node} />
</li>
);
})}
</ul>
</Card>
</Shell>
);
}
const blahLocationFragment = graphql`
fragment blahLocationFragment_blah on Blah
@refetchable(queryName: "BlahLocationRefetchQuery") {
location {
title
}
}
`;
interface BlahLocationProps {
blah: blahLocationFragment_blah$key;
}
function BlahLocation(props: BlahLocationProps) {
const [data, refetch] = useRefetchableFragment(
blahLocationFragment,
props.blah
);
return (
<div>
Hello: {data.location.title}
<button
onClick={() => {
refetch({}, { fetchPolicy: "network-only" });
}}
>
refetch
</button>
</div>
);
}
function Loading() {
return <div>Loading...</div>;
}
export default withRelay(Blahs, BlahQuery, {
fallback: <Loading />,
createClientEnvironment: () => getClientEnvironment()!,
createServerEnvironment: async () => {
const { createServerEnvironment } = await import(
"../lib/server/relay_server_environment"
);
return createServerEnvironment();
},
});
Yep, you'll need to add a <Suspense>
around usages of <BlahLocation>
. Unfortunately this means it can't be server-side rendered (React throws a hard error), so you can either do an isMounted
and conditionally render <BlahLocation>
or put the component in its own file. I mentioned the separate file approach above, but the isMounted
check looks like:
function BlahLocationContainer(props: BlahLocationProps) {
const [isMounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
if (!isMounted) return null;
return <Suspense fallback={<Spinner />}>
<BlahLocation {...props} />
</Suspense>;
}
and use BlahLocationContainer
everywhere instead of BlahLocation
.
@rrdelaney I see, thanks for the solutions, hopefully this will get easier in React 18. So withRelay
does not provide a suspense boundary that would catch this particular suspend?
Nope, even if it did it would do a full-page suspend which isn't great UX.
Makes sense now, thanks!
I'm getting an error and confusing stacktrace when trying to refetch a fragment inside of a withRelay wrapped page. Is this expected? I thought the suspense boundary on the page, would just show a loading and the refetch would succeed, maybe I've got the wrong mental model on what hooks can be used with this library.