Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Updated Next.js with Relay (still using getInitialProps)
import React, { useRef } from "react";
import { ReactRelayContext } from "react-relay";
import { default as NextApp } from "next/app";
import createRelayEnvironment from "../relay/createRelayEnvironment";
export default function App({ Component, pageProps }) {
const environmentRef = useRef(
createRelayEnvironment({
records: pageProps && pageProps.queryRecords,
request: null,
})
);
return (
<ReactRelayContext.Provider
value={{
environment: environmentRef.current,
}}
>
<Component {...pageProps} />
</ReactRelayContext.Provider>
);
}
import React from "react";
import { fetchQuery } from "react-relay";
import { useQuery } from "relay-hooks"; // this it to make it simpler than using a QueryRenderer, also gives us store-only
import createRelayEnvironment from "./createRelayEnvironment";
export default function (ComposedComponent, options = {}) {
const Container = (pageProps) => {
const { props } = useQuery(options.query, pageProps.queryVariables, {
fetchPolicy: "store-only",
});
return <ComposedComponent {...pageProps} {...props} />;
};
Container.displayName = `PageContainer(${ComposedComponent.displayName})`;
Container.getInitialProps = async (ctx) => {
const environment = createRelayEnvironment({
request: ctx.req || null,
});
// Evaluate the composed component's getInitialProps()
let composedInitialProps = {};
if (ComposedComponent.getInitialProps) {
composedInitialProps = await ComposedComponent.getInitialProps(ctx);
}
let queryProps = {};
let queryRecords = {};
let variables = {};
let pageHeaders;
if (options.query) {
variables = options.getVariables ? options.getVariables(ctx) : {};
queryProps = await fetchQuery(environment, options.query, variables);
queryRecords = environment.getStore().getSource().toJSON();
}
const props = {
...composedInitialProps,
queryVariables: variables,
queryRecords,
};
if (options.getHeaders) {
const headers = options.getHeaders({ ...props, ...queryProps });
if (headers) {
pageHeaders = headers;
}
}
if (ctx.res) {
// you can skip this if you don't want to mess with cache headers for you page
ctx.res.setHeader(
"Cache-Control",
"s-maxage=1, stale-while-revalidate=59"
);
return { ...props, pageHeaders };
}
return {
queryVariables: variables,
};
};
return Container;
}
import { Environment, Network, RecordSource, Store } from "relay-runtime";
import fetch from "isomorphic-unfetch";
let relayEnvironment = null;
function getHeadersFactory(request) {
return () => {
const headers = {
Accept: "application/json",
"Content-Type": "application/json",
};
return headers;
};
}
function getFetchQuery(apiUrl, getHeaders) {
// I'm using persisted queries which is why I have operation.id here, you'll want to tweak this method if you're not
function fetchQuery(operation, variables, cacheConfig, uploadables) {
return fetch(apiUrl, {
method: "POST",
headers: getHeaders(),
body: JSON.stringify({
documentId: operation.id,
variables,
}),
}).then(async (response) => response.json());
}
return fetchQuery;
}
export default function createRelayEnviroment({
records = {},
request = null,
} = {}) {
// Constructing your API URL is likely to be different to me, so tweak as needed.
const baseUrl = request
? `${request.headers["x-forwarded-proto"] || "http"}://${
request.headers.host
}`
: "";
const apiUrl = `${baseUrl}/api/graphql`;
// Create a network layer from the fetch function
const network = Network.create(
getFetchQuery(apiUrl, getHeadersFactory(request))
);
const store = new Store(new RecordSource(records));
if (!process.browser) {
return new Environment({
network,
store,
});
}
// reuse Relay environment on client-side
if (!relayEnvironment) {
relayEnvironment = new Environment({
network,
store,
});
}
return relayEnvironment;
}
import React from "react";
import { graphql } from "react-relay";
import createPageContainer from "../relay/createPageContainer";
import PostLayout from "../components/PostLayout";
import Header from "../components/Header";
import Shell from "../layouts/Shell";
function PostDetail({ config, post }) {
return (
<Shell
title={post.title}
>
<Header />
<PostLayout config={config} post={post} />
</Shell>
);
}
export default createPageContainer(PostDetail, {
query: graphql`
query PostDetailQuery($slug: String!) {
config {
...PostLayout_config
}
post: postBySlug(slug: $slug) {
title
slug
...PostLayout_post
}
}
`,
getVariables: ({ query: { slug } }) => ({ slug }),
});
@fi0

This comment has been minimized.

Copy link

@fi0 fi0 commented Dec 17, 2020

Is slug a variable in the dynamic route?

@AndrewIngram

This comment has been minimized.

Copy link
Owner Author

@AndrewIngram AndrewIngram commented Mar 18, 2021

yeah

@skk2142

This comment has been minimized.

Copy link

@skk2142 skk2142 commented Apr 8, 2021

Hi! It seems like it breaks for relay runtime v11. I've updated it and it seems to work. My only concern is I'm using toPromise which relay documentation asks explicitly to avoid (https://relay.dev/docs/api-reference/fetch-query/#behavior-with-topromise)

export function createPageContainer<T extends OperationType>(
  ComposedComponent: NextPage<{
    renderProps: RenderProps<T>;
    queryVariables: T['variables'];
  }>,
  options: {
    query: GraphQLTaggedNode;
    getVariables: (ctx: NextPageContext) => T['variables'];
  },
) {
  const Container = (
    pageProps: { queryVariables: Variables } & {
      children?: ReactNode;
      // data: T['response'];
    },
  ) => {
    const res = useQuery<T>(options.query, pageProps.queryVariables, {
      fetchPolicy: 'store-only',
    });
    return (
      <ComposedComponent
        {...pageProps}
        renderProps={res}
        queryVariables={pageProps.queryVariables}
      />
    );
  };

  Container.displayName = `PageContainer(${ComposedComponent.displayName})`;

  Container.getInitialProps = async (ctx: NextPageContext) => {
    // const environment = createRelayEnvironment({
    //   request: ctx.req || null,
    // });

    // Evaluate the composed component's getInitialProps()
    let composedInitialProps = {};
    if (ComposedComponent.getInitialProps) {
      composedInitialProps = await ComposedComponent.getInitialProps(ctx);
    }
    // let queryProps = {};
    // const queryRecords = {};
    let variables = {};
    // let pageHeaders;
    if (options.query) {
      variables = options.getVariables ? options.getVariables(ctx) : {};
      const r = await fetchQuery(ctx.environment, options.query, variables, {
        fetchPolicy: 'store-or-network',
        networkCacheConfig: {
          force: false,
        },
      }).toPromise();
      // console.log(
      //   `fetched records`,
      //   ctx.environment
      //     .getStore()
      //     .getSource()
      //     .toJSON(),
      // );
      // queryRecords = ctx.environment.getStore().getSource().toJSON();
    }

    // console.log(queryProps);

    const props = {
      ...composedInitialProps,
      queryVariables: variables,
      // queryRecords,
    };
    // if (options.getHeaders) {
    //   const headers = options.getHeaders({ ...props, ...queryProps });
    //   if (headers) {
    //     pageHeaders = headers;
    //   }
    // }

    if (ctx.res) {
      // // you can skip this if you don't want to mess with cache headers for you page
      // ctx.res.setHeader(
      //   'Cache-Control',
      //   's-maxage=1, stale-while-revalidate=59',
      // );
      return { ...props };
    }
    return {
      queryVariables: variables,
    };
  };

  return Container;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment