Undefined data after client-side rendering with Next.js and WPGraphQL - next.js

I have created an app using Next.js, Apollo, and I fetch data with WPGraphQL. So far I have been able to fetch data using getStaticProps() and getServerSideProps() on pages files.
However it doesn't work with client-side rendering in Components as it either keep loading or logs data as "undefined". The weird thing is the same implementation works when I fetch data from a non-WPGraphQL url.
Here is my code.
_app.js
import 'bootstrap/dist/css/bootstrap.css';
import '../public/css/styles.css';
import Header from '../components/Header';
import Footer from '../components/Footer';
import { ApolloProvider } from "#apollo/client";
import client from "../apollo-client";
function MyApp({ Component, pageProps }) {
return (
<>
<ApolloProvider client={client}>
<Header />
<Component {...pageProps} />
<Footer />
</ApolloProvider>
</>
);
}
export default MyApp;
./apollo-client.js
import { ApolloClient, InMemoryCache } from "#apollo/client";
const client = new ApolloClient({
uri: process.env.WP_API_URL,
cache: new InMemoryCache(),
headers: {
authorization: process.env.WP_AUTHORIZATION,
},
});
export default client;
./components/ClientOnly.js
import { useEffect, useState } from "react";
export default function ClientOnly({ children, ...delegated }) {
const [hasMounted, setHasMounted] = useState(false);
useEffect(() => {
setHasMounted(true);
}, []);
if (!hasMounted) {
return null;
}
return <div {...delegated}>{children}</div>;
}
.components/Articles.js
import { useQuery, gql } from "#apollo/client";
const QUERY = gql`
query Articles {
posts(where: { categoryName: "articles", status: PUBLISH}, last: 3) {
edges {
node {
author {
node {
name
}
}
featuredImage {
node {
sourceUrl
srcSet
}
}
title
slug
}
}
}
}
`;
export default function Articles() {
const { data, loading, error } = useQuery(QUERY);
if (loading) {
return <h2>Loading...</h2>;
}
if (error) {
console.error(error);
return null;
}
const posts = data.posts.edges.map(({ node }) => node);
console.log('THis is DATA',data)
return (
<div>
...

Related

getServerSideProps not initialising client side state (next-redux-wrapper, redux/toolkit, redux-saga) Next.JS

I am doing a NextJS project using next-redux-wrapper, redux/toolkit and redux-saga.
This is my setup:
store/enquiry/slice.ts:
import { PayloadAction, createSlice } from '#reduxjs/toolkit'
import { HYDRATE } from 'next-redux-wrapper';
export enum LoadingTracker {
NotStarted,
Loading,
Success,
Failed
}
type EnquiryState = {
enquiries: Enquiry[],
loadingTracker: LoadingTracker
}
const initialState: EnquiryState = {
enquiries: [],
loadingTracker: LoadingTracker.NotStarted
}
export const enquirySlice = createSlice({
name: 'enquiries',
initialState,
reducers: {
//#ts-ignore
loadEnquiries: (state, action: PayloadAction<Enquiry[]>) => {
state.enquiries = action.payload;
state.loadingTracker = LoadingTracker.Success
},
failedLoadEnquiries: (state) => {
state.loadingTracker = LoadingTracker.Failed;
},
startedLoadingEnquiries: (state) => {
state.loadingTracker = LoadingTracker.Loading;
}
},
extraReducers:{
[HYDRATE]:(state, action) => {
return{
...state,
...action.payload.enquiry
};
}
},
});
export const { loadEnquiries, failedLoadEnquiries, startedLoadingEnquiries } =
enquirySlice.actions;
export default enquirySlice.reducer;
This is my enquiry saga setup: sagas/enquiry/saga.ts
import { call, put, takeLatest } from "redux-saga/effects";
import { loadEnquiries, startedLoadingEnquiries, failedLoadEnquiries } from
"../../store/enquiry/slice";
export function* loadEnquiriesSaga() {
try {
//#ts-ignore
let response = yield call(() =>
fetch("https://xxxxx686888wwg326et2.mockapi.io/enqueries")
);
if (response.ok) {
//#ts-ignore
let result = yield call(() => response.json());
yield put(loadEnquiries(result));
} else {
yield put(failedLoadEnquiries());
}
} catch (e) {
yield put(failedLoadEnquiries());
}
}
export function* loadEnquiriesWatcher() {
const { type } = startedLoadingEnquiries();
yield takeLatest(type, loadEnquiriesSaga);
}
This is my root saga setup: sagas/index.js
import { all } from 'redux-saga/effects';
import { loadEnquiriesWatcher } from './enquiry/saga';
export default function* rootSaga() {
yield all([
loadEnquiriesWatcher()
])
}
this is my store setup: store/index.js
import { configureStore, getDefaultMiddleware } from '#reduxjs/toolkit'
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'
import createSagaMiddleware from "redux-saga";
import enquiryReducer from '../store/enquiry/slice';
import { createWrapper } from 'next-redux-wrapper';
import rootSaga from '../sagas/index';
const makeStore = () => {
const sagaMiddleware = createSagaMiddleware();
const store = configureStore({
reducer: {
enquiry: enquiryReducer,
},
middleware: [...getDefaultMiddleware({ thunk: false }), sagaMiddleware],
devTools: true
})
//#ts-ignore
store.sagaTask = sagaMiddleware.run(rootSaga);
return store;
}
const store = makeStore();
export type AppStore = ReturnType<typeof makeStore>;
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch
export const useAppDispatch: () => AppDispatch = useDispatch
This is _app.ts file
import React, { useEffect } from "react";
import '../styles/globals.css'
import type { AppProps } from 'next/app'
import { QueryClient, QueryClientProvider } from "react-query";
import { ReactQueryDevtools } from "react-query/devtools";
import { Provider } from 'react-redux'
import { wrapper } from '../store/index';
const App = ({ Component, ...rest }: AppProps) => {
const [queryClient] = React.useState(() => new QueryClient());
const { store, props } = wrapper.useWrappedStore(rest);
const { pageProps } = props;
return (
<Provider store={store}>
<QueryClientProvider client={queryClient}>
<Component {...pageProps} />
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
</Provider>
);
}
export default App;
I am running a getServersideProps function in pages/index.js file.
import Head from 'next/head'
import { Inter } from '#next/font/google'
import utilStyles from '../styles/Utils.module.css';
import Layout from '../components/layout';
import { wrapper } from '../store';
import { startUserLogin } from '../store/user/slice';
import { END } from 'redux-saga';
import { GetServerSideProps } from 'next';
import { startedLoadingEnquiries } from '../store/enquiry/slice';
const inter = Inter({ subsets: ['latin'] })
export const getServerSideProps: GetServerSideProps = wrapper.getServerSideProps(
//#ts-ignore
(store) => async ({ req }) => {
await store.dispatch(startedLoadingEnquiries());
store.dispatch(END);
await (store as any).sagaTask.toPromise();
return {
props:{
status:1
}
}
})
export default function Home(props:any) {
return (
<Layout home>
<Head>
<title>Holiday Experts</title>
</Head>
<section className={utilStyles.headingMd}>
<p>Introduction to Holiday Exers blah blah blah............. status:
{props.status}</p>
</section>
<section className={`${utilStyles.headingMd} ${utilStyles.padding1px}`}>
<h2 className={utilStyles.headingLg}>To Do: List of Top Destinatons as
cards</h2>
</section>
</Layout>
)
}
When I got to the home page localhost:3000/, I see all the state values are populated. I go to localhost:3000/enquiries and i see a list of enquiries as well, but when I refresh, the list is empty because, client side state is not set. If I open Redux dev tools, I see enquiry state to be empty all the time.
I think there is something missing during the Hydration of the state. Not sure what it is.Can somebody help me with it?
Here are my references that I followed:
https://romandatsiuk.com/en/blog/post-next-redux-saga/
https://github.com/kirill-konshin/next-redux-wrapper

Use Auth0's hook to get token and set header in Apollo client

Hello I'm trying to get auth0 token and set it in the request with next.js, however I haven't been able to set it.
I think the cause is that the function "authLink" in apollo-client.tsx is not called.
I would be very grateful if someone could point me in the right direction.
apollo-client.tsx
import { createHttpLink,ApolloClient, InMemoryCache} from "#apollo/client";
import { setContext } from "#apollo/link-context";
import { Auth0ContextInterface } from "#auth0/auth0-react";
function createClient(
getAccessTokenSilently: Auth0ContextInterface["getAccessTokenSilently"],
){
const endpointUri ="http://localhost:8080/v1/graphql"
console.log("hoge")
const authLink = setContext(async (_, { headers }) => {
const accessToken = await getAccessTokenSilently();
console.log(accessToken)
return {
headers: {
...headers,
authorization: `Bearer ${accessToken}`,
},
};
});
const httpLink = createHttpLink({
uri: endpointUri,
});
return new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache(),
});
}
export {createClient};
_app.tsx
import '../styles/globals.css'
import Head from 'next/head'
import type { AppProps } from 'next/app'
import { ApolloProvider } from '#apollo/client'
import { createClient } from './api/apollo-client'
import { ThemeProvider } from '#mui/material/styles'
import { CacheProvider, EmotionCache } from '#emotion/react';
import theme from '../src/theme'
import createEmotionCache from '../src/createEmotionCache'
import { Auth0Provider,useAuth0 } from '#auth0/auth0-react'
import { FC,ReactNode } from 'react'
const clientSideEmotionCache = createEmotionCache();
interface MyAppProps extends AppProps {
emotionCache?: EmotionCache;
}
const AuthApolloProvider: FC<{ children: ReactNode }> = ({ children }) => {
const { getAccessTokenSilently } = useAuth0();
const client = createClient(getAccessTokenSilently);
return <ApolloProvider client={client}>{children}</ApolloProvider>;
};
function MyApp({ Component, emotionCache = clientSideEmotionCache, pageProps }: MyAppProps) {
return (
<Auth0Provider
domain={process.env.NEXT_PUBLIC_AUTH0_DOMAIN||""}
clientId={process.env.NEXT_PUBLIC_AUTH0_CLIENT_ID||""}
redirectUri={`${process.env.NEXT_PUBLIC_APP_URL}/`}
audience={process.env.NEXT_PUBLIC_AUTH0_AUDIENCE||""}
>
<CacheProvider value={emotionCache}>
<Head>
<meta name="viewport" content="initial-scale=1, width=device-width" />
</Head>
<ThemeProvider theme={theme}>
<AuthApolloProvider>
<Component {...pageProps} />
</AuthApolloProvider>
</ThemeProvider>
</CacheProvider>
</Auth0Provider>
)
}
export default MyApp
I wanted to call the function authLink to get toke and set it in the request header but it didn't work.
Issue is that your code is not waiting for the function getAccessTokenSilently to return token as parent function's callback is async but you are not awaiting for it.
Try below:
apollo-client.tsx
import { createHttpLink,ApolloClient, InMemoryCache} from "#apollo/client";
import { setContext } from "#apollo/link-context";
import { Auth0ContextInterface } from "#auth0/auth0-react";
// Made it async
async function createClient(
getAccessTokenSilently: Auth0ContextInterface["getAccessTokenSilently"],
){
const endpointUri ="http://localhost:8080/v1/graphql"
console.log("hoge");
// Moved it out of set context
const accessToken = await getAccessTokenSilently();
// removed async from setContext as its no more needed
const authLink = setContext((_, { headers }) => {
console.log(accessToken)
return {
headers: {
...headers,
authorization: `Bearer ${accessToken}`,
},
};
});
const httpLink = createHttpLink({
uri: endpointUri,
});
return new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache(),
});
}
export {createClient};
_app.tsx
import '../styles/globals.css'
import Head from 'next/head'
import type { AppProps } from 'next/app'
import { ApolloProvider } from '#apollo/client'
import { createClient } from './api/apollo-client'
import { ThemeProvider } from '#mui/material/styles'
import { CacheProvider, EmotionCache } from '#emotion/react';
import theme from '../src/theme'
import createEmotionCache from '../src/createEmotionCache'
import { Auth0Provider,useAuth0 } from '#auth0/auth0-react'
import { FC,ReactNode } from 'react'
const clientSideEmotionCache = createEmotionCache();
interface MyAppProps extends AppProps {
emotionCache?: EmotionCache;
}
const AuthApolloProvider: FC<{ children: ReactNode }> = ({ children }) => {
const { getAccessTokenSilently } = useAuth0();
// You cannot directly await in JSX function so use useState and useEffect
// useState to store client
const [client, setClient] = React.useState();
// UseEffect to load client
React.useEffect(()=>{
// useEffect will not allow await, so create a function and use it to await
const generateClient = async () => {
const generatedClient = await createClient(getAccessTokenSilently);
setClient(generatedClient);
}
generateClient();
},[])
return <ApolloProvider client={client}>{children}</ApolloProvider>;
};
function MyApp({ Component, emotionCache = clientSideEmotionCache, pageProps }: MyAppProps) {
return (
<Auth0Provider
domain={process.env.NEXT_PUBLIC_AUTH0_DOMAIN||""}
clientId={process.env.NEXT_PUBLIC_AUTH0_CLIENT_ID||""}
redirectUri={`${process.env.NEXT_PUBLIC_APP_URL}/`}
audience={process.env.NEXT_PUBLIC_AUTH0_AUDIENCE||""}
>
<CacheProvider value={emotionCache}>
<Head>
<meta name="viewport" content="initial-scale=1, width=device-width" />
</Head>
<ThemeProvider theme={theme}>
<AuthApolloProvider>
<Component {...pageProps} />
</AuthApolloProvider>
</ThemeProvider>
</CacheProvider>
</Auth0Provider>
)
}
export default MyApp;

Unable to set up Apollo Client to handle setting Authorization token in NextJS

I've been looking at this for days, and simply can't figure this out!
My apolloClient is set up nearly identical to the one found in this article. Rather than add the token stored in the browser cookies for every single request I make throughout my app, I'd like to set it during initialization of ApolloClient.
What I'm seeing however, is a graphQL error being sent from my server telling me that I haven't supplied a JWT, and when I look at the headers for the request, Authorization is blank. Ultimately, I need this to be able to work for both server- and client-side queries and mutations.
pages/_app.tsx
// third party
import type { AppProps } from 'next/app';
import Head from 'next/head';
import { ApolloProvider } from '#apollo/client';
import { MuiThemeProvider, StylesProvider } from '#material-ui/core/styles';
import CssBaseline from '#material-ui/core/CssBaseline';
import { ThemeProvider } from 'styled-components';
// custom
import Page from 'components/Page';
import theme from 'styles/theme';
import GlobalStyle from 'styles/globals';
import { useApollo } from 'lib/apolloClient';
function App({ Component, pageProps, router }: AppProps): JSX.Element {
const client = useApollo(pageProps);
return (
<>
<Head>
<title>My App</title>
</Head>
<ApolloProvider client={client}>
<MuiThemeProvider theme={theme}>
<ThemeProvider theme={theme}>
<StylesProvider>
<CssBaseline />
<GlobalStyle />
<Page>
<Component {...pageProps} />
</Page>
</StylesProvider>
</ThemeProvider>
</MuiThemeProvider>
</ApolloProvider>
</>
);
}
export default App;
pages/index.tsx
/* eslint-disable #typescript-eslint/explicit-module-boundary-types */
import { useEffect, useState } from 'react';
import Head from 'next/head';
import { GetServerSidePropsContext, NextPageContext } from 'next';
import { Box, Grid, Typography } from '#material-ui/core';
import Widget from 'components/common/Widget';
import Loading from 'components/common/Loading';
import { addApolloState, initializeApollo } from 'lib/apolloClient';
import { MeDocument } from 'generated/types';
const Home: React.FC = () => {
return (
<>
<Head>
<title>Dashboard</title>
</Head>
<Grid container spacing={3}>
... other rendered components
</Grid>
</>
);
};
export const getServerSideProps = async (
context: GetServerSidePropsContext
) => {
const client = initializeApollo({ headers: context?.req?.headers });
try {
await client.query({ query: MeDocument });
return addApolloState(client, {
props: {},
});
} catch (err) {
// return redirectToLogin(true);
console.error(err);
return { props: {} };
}
};
export default Home;
lib/apolloClient.ts
import { useMemo } from 'react';
import {
ApolloClient,
ApolloLink,
InMemoryCache,
NormalizedCacheObject,
} from '#apollo/client';
import { onError } from '#apollo/link-error';
import { createUploadLink } from 'apollo-upload-client';
import merge from 'deepmerge';
import { IncomingHttpHeaders } from 'http';
import fetch from 'isomorphic-unfetch';
import isEqual from 'lodash/isEqual';
import type { AppProps } from 'next/app';
import getConfig from 'next/config';
const { publicRuntimeConfig } = getConfig();
const APOLLO_STATE_PROP_NAME = '__APOLLO_STATE__';
let apolloClient: ApolloClient<NormalizedCacheObject> | undefined;
const createApolloClient = (headers: IncomingHttpHeaders | null = null) => {
console.log('createApolloClient headers: ', headers);
// isomorphic fetch for passing the cookies along with each GraphQL request
const enhancedFetch = async (url: RequestInfo, init: RequestInit) => {
console.log('enhancedFetch headers: ', init.headers);
const response = await fetch(url, {
...init,
headers: {
...init.headers,
'Access-Control-Allow-Origin': '*',
// here we pass the cookie along for each request
Cookie: headers?.cookie ?? '',
},
});
return response;
};
return new ApolloClient({
// SSR only for Node.js
ssrMode: typeof window === 'undefined',
link: ApolloLink.from([
onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors)
graphQLErrors.forEach(({ message, locations, path }) =>
console.log(
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`
)
);
if (networkError)
console.log(
`[Network error]: ${networkError}. Backend is unreachable. Is it running?`
);
}),
// this uses apollo-link-http under the hood, so all the options here come from that package
createUploadLink({
uri: publicRuntimeConfig.graphqlEndpoint,
// Make sure that CORS and cookies work
fetchOptions: {
mode: 'cors',
},
credentials: 'include',
fetch: enhancedFetch,
}),
]),
cache: new InMemoryCache(),
});
};
type InitialState = NormalizedCacheObject | undefined;
type ReturnType = ApolloClient<NormalizedCacheObject>;
interface IInitializeApollo {
headers?: IncomingHttpHeaders | null;
initialState?: InitialState | null;
}
export const initializeApollo = (
{ headers, initialState }: IInitializeApollo = {
headers: null,
initialState: null,
}
): ReturnType => {
console.log('initializeApollo headers: ', headers);
const _apolloClient = apolloClient ?? createApolloClient(headers);
// If your page has Next.js data fetching methods that use Apollo Client, the initial state
// get hydrated here
if (initialState) {
// Get existing cache, loaded during client side data fetching
const existingCache = _apolloClient.extract();
// Merge the existing cache into data passed from getStaticProps/getServerSideProps
const data = merge(initialState, existingCache, {
// combine arrays using object equality (like in sets)
arrayMerge: (destinationArray, sourceArray) => [
...sourceArray,
...destinationArray.filter(d => sourceArray.every(s => !isEqual(d, s))),
],
});
// Restore the cache with the merged data
_apolloClient.cache.restore(data);
}
// For SSG and SSR always create a new Apollo Client
if (typeof window === 'undefined') return _apolloClient;
// Create the Apollo Client once in the client
if (!apolloClient) apolloClient = _apolloClient;
return _apolloClient;
};
export const addApolloState = (
client: ApolloClient<NormalizedCacheObject>,
pageProps: AppProps['pageProps']
): ReturnType => {
if (pageProps?.props) {
pageProps.props[APOLLO_STATE_PROP_NAME] = client.cache.extract();
}
return pageProps;
};
export function useApollo(pageProps: AppProps['pageProps']): ReturnType {
const state = pageProps[APOLLO_STATE_PROP_NAME];
const store = useMemo(
() => initializeApollo({ initialState: state }),
[state]
);
return store;
}
I've got three console logs inside of the apolloClient.ts file. When I reload the page, the initializeApollo headers and createApolloClient headers console logs both return full (and correct) headers, including the cookie I'm after. enhancedFetch headers ONLY has { accept: '*/*', 'content-type': 'application/json'}. So that's the first oddity. I then receive my server error about a missing JWT, and all three console logs are then run twice more: initializeApollo headers is undefined, createApolloClient headers is null, and enhancedFetch headers remains the same.
My gut tells me that it's something to do with the server not receiving the header on its first go, so it messes up the following console logs (which I suppose could be client-side?). I'm kind of at a loss at this point. What am I missing??

NEXTJS: getServerSideProps not working into components

Below is the code located at "Pages/home.js". // localhost:3000/home
import axios from 'axios';
import Section1 from '../components/home-sections/section-1';
const Homepage = ({ show }) => {
const Html = JSON.parse(show.response.DesktopHTML);
const renderSection = () => {
return Html.map((itemData,index)=>{
return(<div key={index}>{itemData.DisplayName}</div>)
})
}
return(
<div>
{ renderSection()}
<Section1 />
</div>
)
}
export const getServerSideProps = async ({ query }) => {
try {
const response = await axios.get(
`https://api.example.com/getHomeSection?title=Section 1`
);
return {
props: {
show: response.data,
},
};
} catch (error) {
return {
props: {
error: error.error,
},
};
}
};
export default Homepage;
Now same code I added into section-1.js and this file is located to "components/home-sections/section-1.js"
Now getServerSideProps is working fine in home.js, but in section-1.js it is not working.
Error: TypeError: show is undefined in section-1.js
You cannot use getServerSideProps in non-page components. You can either pass the prop from Home to HomeSection or create a context so the value can be available globally from the component tree
getServerSideProps can only be exported from a page. You can’t export
it from non-page files.
https://nextjs.org/docs/basic-features/data-fetching#only-allowed-in-a-page-2
getServerSideProps can only be exported from Page components. It will not be run on components imported into a page.
However, you could export a function from the component that returns the props, and call that function from the page's getServerSideProps function.
Create a getServerSideProps function on the component.
// #components/MyComponent.tsx
import { GetServerSidePropsContext } from 'next';
function MyComponent(props: IMyComponentProps) {
return (<div>MyComponent</div>;)
}
MyComponent.getServerSideProps = async (context: GetServerSidePropsContext): Promise<{ props: IMyComponentProps }> => {
return { props: { ... } };
}
export default MyComponent;
In your page's getServerSideProps function, call the component's getServerSideProps function and merge the props from the component with the props from the page.
// mypage.tsx
import MyComponent from '#components/MyComponent';
const Page: NextPageWithLayout = (props: IIndexPageProps) => {
return <MyComponent />;
}
export async function getServerSideProps(context: GetServerSidePropsContext): Promise<{ props: IIndexPageProps }> {
let componentServerSideProps = await MyComponent.getServerSideProps(context);
let otherServerSideProps = { props: { ... } };
return {
props: {
...componentServerSideProps.props,
...otherServerSideProps.props
}
};
}

"Cannot read property of undefined" being thrown by react-apollo used with React-NextJS

My NextJS project has been giving me grief over Graph QL these days. I've been trying to implement an Apollo client solution to retrieve data from a remote GraphQL server into a custom component. But no matter which solution I try, I always end up with this error. Here's my current react-apollo implementation:
// /lib/with-apollo-client.js
import React from "react";
import Head from "next/head";
import { getDataFromTree } from "react-apollo";
import initApollo from "./init-apollo";
export default App => {
return class WithData extends React.Component {
static displayName = `WithData(${App.displayName})`;
static async getInitialProps(ctx) {
const { Component, router } = ctx;
const apollo = initApollo({});
ctx.ctx.apolloClient = apollo;
let appProps = {};
if (App.getInitialProps) {
appProps = await App.getInitialProps(ctx);
}
// Run all GraphQL queries in the component tree
// and extract the resulting data
if (!process.browser) {
try {
// Run all GraphQL queries
await getDataFromTree(
<App
{...appProps}
Component={Component}
router={router}
apolloClient={apollo}
/>
);
} catch (error) {
console.error("Error while running `getDataFromTree`", error);
}
// getDataFromTree does not call componentWillUnmount
// head side effect therefore need to be cleared manually
Head.rewind();
}
// Extract query data from the Apollo store
const apolloState = apollo.cache.extract();
return {
...appProps,
apolloState
};
}
constructor(props) {
super(props);
this.apolloClient = initApollo(props.apolloState);
}
render() {
return <App {...this.props} apolloClient={this.apolloClient} />;
}
};
};
// /lib/init-apollo.js
import { ApolloClient, InMemoryCache, HttpLink } from 'apollo-boost'
import fetch from 'isomorphic-unfetch'
let apolloClient = null
// Polyfill fetch() on the server (used by apollo-client)
if (!process.browser) {
global.fetch = fetch
}
function create (initialState) {
// Check out https://github.com/zeit/next.js/pull/4611 if you want to use the AWSAppSyncClient
return new ApolloClient({
connectToDevTools: process.browser,
ssrMode: !process.browser, // Disables forceFetch on the server (so queries are only run once)
link: new HttpLink({
uri: 'https://api.graph.cool/simple/v1/cixmkt2ul01q00122mksg82pn', // Server URL (must be absolute)
credentials: 'same-origin' // Additional fetch() options like `credentials` or `headers`
}),
cache: new InMemoryCache().restore(initialState || {})
})
}
export default function initApollo (initialState) {
// Make sure to create a new client for every server-side request so that data
// isn't shared between connections (which would be bad)
if (!process.browser) {
return create(initialState)
}
// Reuse client on the client-side
if (!apolloClient) {
apolloClient = create(initialState)
}
return apolloClient
}
The component I'm retrieving data into looks like this:
// /components/PostsList2.jsx
import { Query } from 'react-apollo'
import gql from 'graphql-tag'
export const allUsersQuery = gql`
query allUsers($first: Int!, $skip: Int!) {
allUsers(orderBy: createdAt_DESC, first: $first, skip: $skip) {
id
firstName
createdAt
}
_allUsersMeta {
count
}
}
`
export const allUsersQueryVars = {
skip: 0,
first: 10
}
export default function PostsList2 () {
return (
<Query query={allUsersQuery} variables={allUsersQueryVars}>
{({ loading, error, data: { allUsers, _allUsersMeta }, fetchMore }) => {
if (error) return <aside>Error loading users!</aside>
if (loading) return <div>Loading</div>
const areMorePosts = allUsers.length < _allUsersMeta.count
return (
<section>
<ul>
{allUsers.map((user, index) => (
<li key={user.id}>
<div>
<span>{index + 1}. </span>
<div>{user.firstName}</div>
</div>
</li>
))}
</ul>
{areMorePosts ? (
<button onClick={() => loadMorePosts(allUsers, fetchMore)}>
{' '}
{loading ? 'Loading...' : 'Show More'}{' '}
</button>
) : (
''
)}
</section>
)
}}
</Query>
)
}
function loadMorePosts (allUsers, fetchMore) {
fetchMore({
variables: {
skip: allUsers.length
},
updateQuery: (previousResult, { fetchMoreResult }) => {
if (!fetchMoreResult) {
return previousResult
}
return Object.assign({}, previousResult, {
// Append the new users results to the old one
allUsers: [...previousResult.allUsers, ...fetchMoreResult.allUsers]
})
}
})
}
Since this is a NextJS project, there's also an _app.jsx that I've wrapped in a special provider component:
// /pages._app.jsx
/* eslint-disable max-len */
import '../static/styles/fonts.scss';
import '../static/styles/style.scss';
import '../static/styles/some.css';
import CssBaseline from '#material-ui/core/CssBaseline';
import { ThemeProvider } from '#material-ui/styles';
import jwt from 'jsonwebtoken';
import withRedux from 'next-redux-wrapper';
import App, {
Container,
} from 'next/app';
import Head from 'next/head';
import React from 'react';
import { Provider } from 'react-redux';
import makeStore from '../reducers';
import mainTheme from '../themes/main-theme';
import getSessIDFromCookies from '../utils/get-sessid-from-cookies';
import getLanguageFromCookies from '../utils/get-language-from-cookies';
import getUserTokenFromCookies from '../utils/get-user-token-from-cookies';
import removeFbHash from '../utils/remove-fb-hash';
import withApolloClient from '../lib/with-apollo-client'
import { ApolloProvider } from 'react-apollo'
class MyApp extends App {
static async getInitialProps({ Component, ctx }) {
let userToken;
let sessID;
let language;
if (ctx.isServer) {
ctx.store.dispatch({ type: 'UPDATEIP', payload: ctx.req.headers['x-real-ip'] });
userToken = getUserTokenFromCookies(ctx.req);
sessID = getSessIDFromCookies(ctx.req);
language = getLanguageFromCookies(ctx.req);
const dictionary = require(`../dictionaries/${language}`);
ctx.store.dispatch({ type: 'SETLANGUAGE', payload: dictionary });
if(ctx.res) {
if(ctx.res.locals) {
if(!ctx.res.locals.authenticated) {
userToken = null;
sessID = null;
}
}
}
if (userToken && sessID) { // TBD: validate integrity of sessID
const userInfo = jwt.verify(userToken, process.env.JWT_SECRET);
ctx.store.dispatch({ type: 'ADDUSERINFO', payload: userInfo });
}
ctx.store.dispatch({ type: 'ADDSESSION', payload: sessID }); // component will be able to read from store's state when rendered
}
const pageProps = Component.getInitialProps ? await Component.getInitialProps(ctx) : {};
return { pageProps };
}
componentDidMount() {
// Remove the server-side injected CSS.
const jssStyles = document.querySelector('#jss-server-side');
if (jssStyles) {
jssStyles.parentNode.removeChild(jssStyles);
}
// Register serviceWorker
if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/serviceWorker.js'); }
// Handle FB's ugly redirect URL hash
removeFbHash(window, document);
}
render() {
const { Component, pageProps, store, apolloClient } = this.props;
return (
<Container>
<Head>
// redacted for brevity
</Head>
<ThemeProvider theme={mainTheme}>
{/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
<CssBaseline />
<ApolloProvider client={apolloClient}>
<Provider store={store}>
<Component {...pageProps} />
</Provider>
</ApolloProvider>
</ThemeProvider>
</Container>
);
}
}
export default withApolloClient(withRedux(makeStore)(MyApp));
So with this setup, when I compile and run my app, it throws the following:
TypeError: Cannot read property 'allUsers' of undefined
I'm really lost! The repo is up at https://github.com/amitschandillia/proost/tree/master/web.

Resources