How to get client from wrapper in next.js getServerSideProps()? - reactjs

I'm trying to use next.js with apollo graphql for server-side rendering. I know that to do that i need to run the necessary queries inside getServerSideProps(), which then will pass the props into the main component, where i will be able to render the results.
I created a provider to make sure all components in the tree get the same client object.
import withApollo from "next-with-apollo";
import { ApolloClient, InMemoryCache } from "#apollo/client";
import { ApolloProvider } from "#apollo/react-hooks";
export default withApollo(
() => {
return new ApolloClient({
ssrMode: true,
uri: "https://my.api/graphql",
cache: new InMemoryCache()
});
},
{
render: ({ Page, props }) => {
return (
<ApolloProvider client={props.apollo}>
<Page {...props} />
</ApolloProvider>
);
}
}
);
but how can i get this client inside the getServerSideProps() function if it's not being wrapped by withApollo()?
import gql from "graphql-tag";
import { useQuery } from "#apollo/react-hooks";
import { ApolloClient } from "#apollo/client";
import withApollo from "next-with-apollo";
const MY_QUERY = gql`
query MyQuery {
myQuery {
name
}
}
`;
function MyComponent(props) {
return (
<div className="landing-section__topcontainer ph-lg-8 ph-3">
<div className="overflow-list-container">
<div className="landing-horizontal-list">
{props.res.map(q => {
return (
<div className="tag-tile__title">{q.name}</div>
);
})}
</div>
</div>
</div>
);
}
export async function getServerSideProps() {
// Fetch data from external API
const apolloClient = getApolloClient();
const { data } = await apolloClient.query({
query: MY_QUERY
});
const res = data.myQuery;
return { props: { res } };
}
export default withApollo(MyComponent);

Related

how to not fetch fragment data until component renders react GraphQL

I thought that relay modern implemented a system whereby it would not try to fetch data until it was rendering the component that declared it. I am talking about fragment components. I have tried to test this but it is fetching all the data.
import React from "react";
import { Environment, Network, RecordSource, Store } from "relay-runtime";
import {
RelayEnvironmentProvider,
} from "react-relay/hooks";
import "./App.css";
import QueryLoaderComponent from "./QueryLoaderComponent";
import QueryComponent from "./QueryComponent";
async function fetchGraphQL(text: string, variables: Record<any, any>) {
// Fetch data from GitHub's GraphQL API:
const response = await fetch("https://countries.trevorblades.com/", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
query: text,
variables,
}),
});
// Get the response as JSON
return await response.json();
}
async function fetchRelay(params: any, variables: any) {
console.log(
`fetching query ${params.name} with ${JSON.stringify(variables)}`
);
return fetchGraphQL(params.text, variables);
}
// Export a singleton instance of Relay Environment configured with our network function:
const environment = new Environment({
network: Network.create(fetchRelay),
store: new Store(new RecordSource()),
});
function App() {
return (
<RelayEnvironmentProvider environment={environment}>
{/* <QueryLoaderComponent /> */}
<QueryComponent />
</RelayEnvironmentProvider>
);
}
export default App;
import { useState } from "react";
// #ts-ignore
import graphql from "babel-plugin-relay/macro";
import { QueryComponentQuery } from "./__generated__/QueryComponentQuery.graphql";
import { PreloadedQuery, useLazyLoadQuery, usePreloadedQuery } from "react-relay";
// import FragmentComponent from "./FragmentComponent";
const query = graphql`
query QueryComponentQuery($id: ID!) {
country(code: $id) {
name
...FragmentComponent_country
}
}
`;
interface Props {
// queryRef: PreloadedQuery<QueryComponentQuery>;
}
const QueryComponent = ({
// queryRef
}: Props) => {
const data = useLazyLoadQuery<QueryComponentQuery>(query, { id: "US"});
const [showContinent, setShowContinent] = useState(false);
return (
<div>
<button onClick={() => setShowContinent(!showContinent)}>
{showContinent ? "Hide" : "Show"} continent
</button>
<h1>{data.country?.name}</h1>
{/* <ul>
{data.countries.map((country: any) => (
<li key={country.name}>
{country.name}{" "}
{showContinent && <FragmentComponent country={country} />}
</li>
))}
</ul> */}
</div>
);
};
export default QueryComponent;
import { useFragment } from "react-relay";
// #ts-ignore
import graphql from "babel-plugin-relay/macro";
import { FragmentComponent_country$key } from "./__generated__/FragmentComponent_country.graphql";
export const fragment = graphql`
fragment FragmentComponent_country on Country {
continent {
name
}
}
`;
interface Props {
country: FragmentComponent_country$key;
}
const FragmentComponent = ({ country }: Props) => {
const data = useFragment(fragment, country);
return <div>{data.continent.name}</div>;
};
export default FragmentComponent;
this is fetching the data for the fragment component even though it is not rendering the fragment component. is there a way to defer it until it is rendering the component?
use
React Suspense
on the fragment or anywhere where fetching happens as wrapper

getStaticProps returns an empty object

I'm using nextJS V9.5.5 with wp-graphql and apolloClient to get data from WordPress. Everything works fine, but when I try to return context (in the purpose of getting query) from getStaticProps() like it's described in docs, it returns an empty object.
Custom App:
import React from "react";
import getConfig from "next/config";
import LayoutOuter from "../components/LayoutOuter";
import "bootstrap/dist/css/bootstrap.css";
import { ApolloProvider } from "#apollo/client";
import { useApollo } from "../lib/apolloClient";
import { initializeApollo } from "../lib/apolloClient";
import { gql } from "#apollo/client";
const { serverRuntimeConfig, publicRuntimeConfig } = getConfig();
const { DOMAIN } = publicRuntimeConfig;
function CustomApp({ pageProps, Component, props }) {
const apolloClient = useApollo(pageProps.initialApolloState);
return (
<ApolloProvider client={apolloClient}>
{console.log("_app", props)}
<LayoutOuter>
<Component {...pageProps} />
</LayoutOuter>
</ApolloProvider>
);
}
CustomApp.getInitialProps = async (ctx) => {
const apolloClient = initializeApollo();
await apolloClient.query({
query: gql`
{
// my graphql query here
}
`,
});
return {
props: {
initialApolloState: apolloClient.cache.extract(),
ctx: JSON.stringify(ctx),
},
};
};
export default CustomApp;
One of the page:
import React, { Component, useEffect, useState } from "react";
import getConfig from "next/config";
import { NextSeo } from "next-seo";
const { serverRuntimeConfig, publicRuntimeConfig } = getConfig();
const { DOMAIN, SITENAME } = publicRuntimeConfig;
import { initializeApollo } from "../lib/apolloClient";
import { gql } from "#apollo/client";
import "./services.module.scss";
const Home = (props) => {
let currentPage = Object.values(props.initialApolloState.ROOT_QUERY)[1];
const {
title,
metadesc,
metaRobotsNoindex,
metaRobotsNofollow,
metaRobotsAdv,
opengraphTitle,
opengraphDescription,
opengraphImage,
twitterTitle,
twitterDescription,
twitterImage,
} = currentPage.seo;
return (
<>
{console.log("project", props)}
<NextSeo
noindex={metaRobotsNoindex}
nofollow={metaRobotsNofollow}
title={title != "" ? title : `${props.data.pagetitle} - ${SITENAME}`}
description={metadesc}
canonical={DOMAIN}
openGraph={{
url: DOMAIN,
title:
opengraphTitle != ""
? opengraphTitle
: `${props.data.pagetitle} - Garrison Collection`,
description: opengraphDescription,
images: [
{
url: opengraphImage,
width: 800,
height: 600,
alt: { SITENAME },
},
],
site_name: { SITENAME },
}}
/>
<p>works</p>
</>
);
};
export async function getStaticProps(context) {
const apolloClient = initializeApollo();
await apolloClient.query({
query: gql`
{
project(id: "ca-souls", idType: SLUG) {
seo {
canonical
metaDesc
metaKeywords
metaRobotsNofollow
metaRobotsNoindex
opengraphAuthor
opengraphDescription
opengraphModifiedTime
opengraphPublishedTime
opengraphPublisher
opengraphSiteName
opengraphTitle
opengraphType
opengraphUrl
title
twitterDescription
twitterTitle
}
}
}
`,
});
return {
props: {
initialApolloState: apolloClient.cache.extract(),
context: JSON.stringify(context) || null,
},
revalidate: 1,
};
}
export default Home;
Here is the log result:
How could I get context.query?
The context parameter includes previewData contains the preview data set by setPreviewData. This means including function, therefore, unable to serealize. Take values out from context.params.

NextJS: How to add screen loading for production build?

I want add screen loading in next js project. And I tried to do that with the Router component in next/router.
This is my _app.js in next.js project:
import {CookiesProvider} from 'react-cookie';
import App from 'next/app'
import React from 'react'
import {Provider} from 'react-redux'
import withRedux from 'next-redux-wrapper'
import withReduxSaga from 'next-redux-saga'
import createStore from '../src/redux/store'
import Router from "next/router";
import {Loaded, Loading} from "../src/util/Utils";
class MyApp extends App {
static async getInitialProps({Component, ctx}) {
let pageProps = {};
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps({ctx})
}
return {pageProps}
}
render() {
Router.onRouteChangeStart = () => {
Loading()
};
Router.onRouteChangeComplete = () => {
Loaded()
};
Router.onRouteChangeError = () => {
Loaded()
};
const {Component, pageProps, store} = this.props;
return (
<CookiesProvider>
<Provider store={store}>
<Component {...pageProps} />
</Provider>
</CookiesProvider>
)
}
}
export default withRedux(createStore)(withReduxSaga(MyApp))
This is Loaded() and Loading() functions:
export const Loaded = () => {
setTimeout(() => {
let loading = 'has-loading';
document.body.classList.remove(loading);
}, 100);
};
export const Loading = () => {
let loading = 'has-loading';
document.body.classList.add(loading);
};
The code works well when the project is under development mode. But when the project is built, the loading won't disappear.
Do you know solution of this issue or are you suggesting another solution?
Using apollo client and react hooks you could do as follow.
Example:
import { useQuery } from '#apollo/react-hooks';
import gql from 'graphql-tag';
import { withApollo } from '../lib/apollo';
import UserCard from '../components/UserCard';
export const USER_INFO_QUERY = gql`
query getUser ($login: String!) {
user(login: $login) {
name
bio
avatarUrl
url
}
}
`;
const Index = () => {
const { query } = useRouter();
const { login = 'default' } = query;
const { loading, error, data } = useQuery(USER_INFO_QUERY, {
variables: { login },
});
if (loading) return 'Loading...'; // Loading component
if (error) return `Error! ${error.message}`; // Error component
const { user } = data;
return (
<UserCard
float
href={user.url}
headerImg="example.jpg"
avatarImg={user.avatarUrl}
name={user.name}
bio={user.bio}
/>
);
};
export default withApollo({ ssr: true })(Index);
More info here: https://github.com/zeit/next.js/tree/canary/examples/with-apollo
I added the following codes to a wrapper component and the problem was resolved.
componentDidMount() {
Loaded();
}
componentWillUnmount() {
Loading();
}

Next.js redirect inside of GraphQL mutation

In my Next.js component I made a mutation request to GraphQL server and after it successfully done I need to redirect to another page. How I do it now:
import React, { Component } from 'react';
import Router from 'next/router';
import { Mutation } from 'react-apollo';
import { gql } from 'apollo-boost';
const signInMutation = gql`
mutation signIn($accessToken: String!) {
signIn(accessToken: $accessToken)
}
`;
export default class extends Component {
static async getInitialProps({ query: { accessToken } }) {
return { accessToken };
}
render() {
const { accessToken } = this.props;
return (
<Mutation mutation={signInMutation} ignoreResults>
{signIn => {
signIn({ variables: { accessToken } }).then(() => {
Router.push({
pathname: '/user'
});
});
return null;
}}
</Mutation>
);
}
}
It works fine but Next.js throws an error: You should only use "next/router" inside the client side of your app.. So, what is the best way to fix the error?
Your signIn mutation is executed on render, and when NextJS renders your app on the server side, executes your mutation.
You should render a button and only trigger the mutation on click:
import React, { Component } from 'react';
import Router from 'next/router';
import { Mutation } from 'react-apollo';
import { gql } from 'apollo-boost';
const signInMutation = gql`
mutation signIn($accessToken: String!) {
signIn(accessToken: $accessToken)
}
`;
export default class extends Component {
static async getInitialProps({ query: { accessToken } }) {
return { accessToken };
}
render() {
const { accessToken } = this.props;
return (
<Mutation mutation={signInMutation} ignoreResults>
{signIn => {
return (
<button onClick={async () => {
await signIn({ variables: { accessToken } })
Router.push({ pathname: '/user' })
}}>Login</button>
)
}}
</Mutation>
);
}
}

Mocking apollo link state

I am trying to mock a query #client and I am not getting.
I mocked the query from graphql server correctly and it's working.
import React from 'react';
import renderer from 'react-test-renderer';
import wait from 'waait';
import ExchangeRates from './ExchangeRates';
import { MockedProvider } from 'react-apollo/test-utils';
import { sucessMockrates, errorMockrates } from '../../mocks/exchangeRatesMock';
describe('ExchangeRates', () => {
it('should render rate', async () => {
const component = renderer.create(
<MockedProvider mocks={[sucessMockrates]} addTypename={false}>
<ExchangeRates />
</MockedProvider>
);
await wait(0);
const p = component.root.findByType('p');
expect(p.children).toContain('AED: 3.67');
});
it('should render loading state initially', () => {
const component = renderer.create(
<MockedProvider mocks={[]}>
<ExchangeRates />
</MockedProvider>
);
const tree = component.toJSON();
expect(tree.children).toContain('Loading...');
});
it('should show error UI', async () => {
const component = renderer.create(
<MockedProvider mocks={[errorMockrates]} addTypename={false}>
<ExchangeRates />
</MockedProvider>
);
await wait(0);
const tree = component.toJSON();
expect(tree.children).toContain('Error!');
});
});
I am using the graphql server link from apollo tutorial
But when I tried to test the apollo query with local state I got an error.
My query:
import gql from 'graphql-tag';
export default gql`
query {
allocations #client {
list
}
}
`;
and my apollo client setup:
const cache = new InMemoryCache();
const defaultState = {
allocations: {
__typename: 'Allocations',
list: [],
},
};
const listQuery = gql`
query getAllocations {
allocations #client {
list
}
}
`;
const stateLink = withClientState({
cache,
defaults: defaultState,
resolvers: {
addAllocation: (
_,
{ userName },
{ cache }
) => {
const previousState = cache.readQuery({ query: listQuery });
const { list } = previousState.allocations;
const data = {
...previousState,
allocations: {
...previousState.allocations,
list: [
...list,
{
userName
},
],
},
};
cache.writeQuery({ query: listQuery, data });
return data.allocations;
},
},
},
});
const client = new ApolloClient({
link: ApolloLink.from([
stateLink,
new HttpLink({
uri: 'https://w5xlvm3vzz.lp.gql.zone/graphql',
}),
]),
cache,
});
My test with apollo local state:
import React from 'react';
import renderer from 'react-test-renderer';
import AllocationListPage from './AllocationListPage';
import { MockedProvider } from 'react-apollo/test-utils';
import { sucessMockAllocations } from '../../../mocks/allocationListMock';
describe('AllocationListPage', () => {
it('should render list of allocations', () => {
renderer.create(
<MockedProvider mocks={[sucessMockAllocations]} addTypename={false}>
<AllocationListPage />
</MockedProvider>
);
});
});
The error I got: TypeError:
Cannot destructure property list of 'undefined' or 'null'.
I need to mock the initial state of apollo local state, and I don't know how.
Thanks in advance.
I got setup my apollo link state with this component:
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { ApolloProvider } from 'react-apollo';
import { makeExecutableSchema, addMockFunctionsToSchema } from 'graphql-tools';
import { ApolloClient } from 'apollo-client';
import { stateLink, cache } from '../graphql/stateLink';
import { ApolloLink } from 'apollo-link';
import { SchemaLink } from 'apollo-link-schema';
const setupClient = mocks => {
const typeDefs = `
type Query {
test: String!
}
`;
const schema = makeExecutableSchema({ typeDefs });
addMockFunctionsToSchema({
schema,
mocks,
preserveResolvers: false,
});
return new ApolloClient({
cache,
link: ApolloLink.from([stateLink, new SchemaLink({ schema })]),
});
};
class ApolloLinkStateSetup extends PureComponent {
render() {
return (
<ApolloProvider client={setupClient(this.props.mocks)}>
{this.props.children}
</ApolloProvider>
);
}
}
ApolloLinkStateSetup.defaultProps = {
mocks: {},
};
ApolloLinkStateSetup.propTypes = {
children: PropTypes.object.isRequired,
mocks: PropTypes.object,
};
export default ApolloLinkStateSetup;
You can mock the graphql queries with makeExecutableSchema and addMockFunctionsToSchema from graphql-tools. This mock can be useful to create the front-end side without the back-end side.

Resources