In my apps, I am using following NPM modules to play with Strapi, GraphQL and Next.js:
react-apollo
next-apollo
graphql
gql
recompose
In the next step, I am creating Apollo config file, example below:
import { HttpLink } from "apollo-link-http";
import { withData } from "next-apollo";
const config = {
link: new HttpLink({
uri: "http://localhost:1337/graphql",
})
};
export default withData(config);
and then inside a class component, I am using a static method getInitialProps() to fetch data from the Strapi via GraphQL query.
Everything is fine but maybe there is another, better way via React hooks or any other?
I found one more nice hook solution for Next.js and GraphQL.
I want to share it with you. Let's start.
Note: I assume that you have Next.js application already installed. If not please follow this guide.
To build this solution we need:
#apollo/react-hooks
apollo-cache-inmemory
apollo-client
apollo-link-http
graphql
graphql-tag
isomorphic-unfetch
next-with-apollo
1. run npm command:
npm install --save #apollo/react-hooks apollo-cache-inmemory apollo-client apollo-link-http graphql graphql-tag isomorphic-unfetch next-with-apollo
2. create Appolo config file, eg. in folder ./config and call it appollo.js. File code below:
import { ApolloClient } from "apollo-client";
import { InMemoryCache } from "apollo-cache-inmemory";
import withApollo from "next-with-apollo";
import { createHttpLink } from "apollo-link-http";
import fetch from "isomorphic-unfetch";
const GRAPHQL_URL = process.env.BACKEND_URL || "https://api.graphql.url";
const link = createHttpLink({
fetch,
uri: GRAPHQL_URL
});
export default withApollo(
({ initialState }) =>
new ApolloClient({
link: link,
cache: new InMemoryCache()
.restore(initialState || {})
})
);
3. create _app.js file (kind of wrapper) in ./pages folder with below code:
import React from "react";
import Head from "next/head";
import { ApolloProvider } from "#apollo/react-hooks";
import withData from "../config/apollo";
const App = ({ Component, pageProps, apollo }) => {
return (
<ApolloProvider client={apollo}>
<Head>
<title>App Title</title>
</Head>
<Component {...pageProps} />
</ApolloProvider>
)
};
export default withData(App);
4. create reusable query component, eg. ./components/query.js
import React from "react";
import { useQuery } from "#apollo/react-hooks";
const Query = ({ children, query, id }) => {
const { data, loading, error } = useQuery(query, {
variables: { id: id }
});
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {JSON.stringify(error)}</p>;
return children({ data });
};
export default Query;
5. create a component for our data fetched via GraphQL
import React from "react";
import Query from "../components/query";
import GRAPHQL_TEST_QUERY from "../queries/test-query";
const Example = () => {
return (
<div>
<Query query={GRAPHQL_TEST_QUERY} id={null}>
{({ data: { graphqlData } }) => {
return (
<div>
{graphqlData.map((fetchedItem, i) => {
return (
<div key={fetchedItem.id}>
{fetchedItem.name}
</div>
);
})}
</div>
);
}}
</Query>
</div>
);
};
export default Example;
6. create our GraphQL query inside ./queries/test-query. Note: I assume that we have access to our example data and properties id and name via GraphQL
import gql from "graphql-tag";
const GRAPHQL_TEST_QUERY = gql`
query graphQLData {
exampleTypeOfData {
id
name
}
}
`;
export default GRAPHQL_TEST_QUERY;
7. to display our result create index.js file (homepage) in ./pages folder with below code:
import Example from './components/example';
const Index = () => <div><Example /></div>
export default Index;
That's all.. enjoy and extend this solution as you want..
I have found one more interestng solution with using apollo-server-micro and lodash
Quick guide:
create Next.js app (example name: next-app) and install required packages
npm i apollo-server-micro lodash
create required files in you Next.js app (next-app)
/next-app/pages/api/graphql/index.js
/next-app/pages/api/graphql/resolvers.js
/next-app/pages/api/graphql/typeDefs.js
add code to index.js
import { ApolloServer } from 'apollo-server-micro';
import resolvers from './resolvers';
import typeDefs from './TypeDef';
const apolloServer = new ApolloServer({
typeDefs,
resolvers,
});
export const config = {
api: {
bodyParser: false
}
};
export default apolloServer.createHandler({ path: '/api/graphql' });
add code to typeDefs.js
import { gql } from 'apollo-server-micro';
const typeDefs = gql`
type User {
id: Int!
name: String!
age: Int
active: Boolean!
}
type Query {
getUser(id: Int): User
}
`;
export default typeDefs;
add code to resolvers.js
import lodash from 'lodash/collection';
const users = [
{ id: 1, name: 'Mario', age: 38, active: true },
{ id: 2, name: 'Luigi', age: 40, active: true},
{ id: 3, name: 'Wario', age: 36, active: false }
];
const resolvers = {
Query: {
getUser: (_, { id }) => {
return lodash.find(users, { id });
}
}
};
export default resolvers;
test your Next.js app (next-app) by running below command and checking graphql URL http://localhost:3000/api/graphql
npm run dev
Related
I'm using supabase auth helper package to React and Next in my project. Immediately, when I start the page, I get an error message that says the following:
And it is that I am not calling that supabaseClient.auth.getSession() method. Which makes me believe that the error is internal. i'm just following the documentation: doc. And use the code that appears in the Basic Setup and in Server-side rendering (SSR)
Actually i have this:
`
// pages/_app.js
import { useState } from 'react'
import { useRouter } from 'next/router'
import '../styles/global.css'
import ProfileContext from '../utils/context/ProfileContext'
import useProfile from '../utils/hooks/useProfile'
import ProtectedRoutes from '../utils/constants/rutas/protectedRoutes'
const MyApp = ({ Component, pageProps }) => {
const [supabaseClient] = useState(() => createBrowserSupabaseClient())
console.log('supabaseClient', supabaseClient)
return (
<SessionContextProvider
supabaseClient={supabaseClient}
initialSession={pageProps.initialSession}
>
<ProfileContext.Provider value={useProfile()}>
<Component {...pageProps} />
</ProfileContext.Provider>
</SessionContextProvider>
)
}
export default MyApp
`
`
// pages/index.js
import { createServerSupabaseClient } from '#supabase/auth-helpers-nextjs'
import { useState, useEffect } from "react"
import Layout from "../layout/Layout"
import List from "../components/List"
import Empty from "../components/Empty"
import { supabase } from "../utils/supabaseClient"
const Index = ({ allData }) => {
const [session, setSession] = useState(null)
useEffect(() => {
setSession(supabase.auth.session())
}, [])
return (
<Layout>
{allData.length ? <List allData={allData} /> : <Empty />}
</Layout>
);
}
export default Index
export async function getServerSideProps(ctx) {
let { data: receptions, error } = await supabase
.from('receptions')
.select('*')
.order('id', { ascending: false })
if (error) throw error
return {
props: {
allData: receptions
},
};
}
`
As you will notice at no time I make use of the getSession() method. I hope I explained myself well.
Try this in to see if the user is not logged in to send the user to login.
This would be because you have an older version of the supabase-js library installed. Please install the latest 2.x version in order for this to work.
Run the following command to get the latest v2 version
npm install #supabase/supabase-js#2
I'm new to using GraphQL in React and have been moving a project from a REST API to a new GraphQL one. As part of this, I wanted to setup mock data to work on the application independent of the GQL API being completed. I've spent a bunch of time trying to follow the Apollo and GraphQL Tools docs but no matter what, I can't seem to get the mock resolvers to work properly. For context, I am using this in a NextJS/React app, and here's a minimum example of what I'm trying to do:
Setup App.js
import React from 'react';
import ApolloClient from 'apollo-client';
import { ApolloProvider } from 'react-apollo';
import { SchemaLink } from 'apollo-link-schema';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { makeExecutableSchema } from '#graphql-tools/schema';
import { addMocksToSchema } from '#graphql-tools/mock';
export default function App() {
const schema = makeExecutableSchema({typeDefs:`
type Query {
getPerson: Person!
}
type Person {
name: String!
}
`});
const mocks = {
Query: () => ({
getPerson: () => ({
name: () => "Name"
})
})
}
addMocksToSchema({ mocks, schema });
const link = new SchemaLink({ schema });
const client = new ApolloClient({
link,
cache: new InMemoryCache(),
connectToDevTools: true
});
return (
<ApolloProvider client={client}>
<Person />
</ApolloProvider>
)
}
Person.js
import React from 'react';
import { useQuery } from '#apollo/react-hooks';
import gql from 'graphql-tag';
export default function Person() {
const { loading, error, data } = useQuery(gql`
query PersonQuery {
getPerson {
name
}
}
`, {
errorPolicy: 'all'
});
console.log(data);
if (loading) return "Loading...";
if (error) console.log(error);
return (
<h1>{data.getPerson.name}<h1>
)
}
Looking at the console.log(error) result yields the error Cannot return null for non-nullable field Query.getPerson and making it a nullable field just returns { getPerson: null } of course. I've tried returning the resolver results as objects vs functions. Logging within the resolvers shows the Query part is being executed but nothing nested within that.
Am I setting something up incorrectly? I also tried not passing in custom mocks as suggested should be possible based on the graphql-tools docs, but to no avail. I also saw this issue from the apollo hooks GitHub that said the newest version of hooks broke the usage of addMocksToSchema, so I tried using the suggested 3.1.3 version but again no luck. Any help would be greatly appreciated!
You need to provide the mock to the client, not the plain schema.
const schemaWithMocks = addMocksToSchema({
schema,
mocks: {},
preserveResolvers: false,
});
const client = new ApolloClient({
// link: new SchemaLink({ schema }); < -- REPLACE THIS
link: (new SchemaLink({ schema: schemaWithMocks }) as unknown) as ApolloLink, // https://github.com/apollographql/apollo-link/issues/1258
cache: new InMemoryCache(),
connectToDevTools: true,
});
Now console.log(data) prints
{"getPerson": {"__typename": "Person", "name": "Name"}} 🎉
I'm currently trying to create an application that will serve a client side web application that will be mostly the same, but slightly different based on the different subdomains. For example, client1.website.com vs client2.website.com -- same base app, slightly different branding and content.
Currently, I have attempted to save this content by fetching static JSON files that contain the differences between the two sites. They are made as a normal GET call to the local server and then applied as a 'content' key in the state object of the application. The problem I am having is that I am unable to reference parts of the content because React attempts to render them before the parent application is finished applying the 'content' state.
Is this architecture the wrong way to go about things or is there a solution that I just haven't found yet?
I've posted my code below to try and show what I'm doing, I've used a third party state library to try and simplify what something like redux would do when full fleshed out:
// Index.js (Contains my store)
import React from "react";
import axios from "axios";
import ReactDOM from "react-dom";
import { StoreProvider, createStore, thunk, action } from "easy-peasy";
import "./index.css";
import App from "./pages/App";
const store = createStore({
company: {
contentType: "default",
baseContent: {},
partnerContent: {},
setContentType: action((state, payload) => {
state.contentType = payload;
}),
setContentData: action((state, payload) => {
state[payload.type] = payload.data;
}),
loadContent: thunk(async (actions, payload) => {
try {
if (payload !== "base") {
actions.setContentType(payload);
}
const partnerData = await axios.get(`content/${payload}.json`);
const baseData = await axios.get(`content/base.json`);
actions.setContentData({
data: partnerData.data,
type: "partnerContent",
});
actions.setContentData({
data: baseData.data,
type: "baseContent",
});
} catch (e) {
throw e;
}
}),
},
});
ReactDOM.render(
<StoreProvider store={store}>
<App />
</StoreProvider>,
document.getElementById("root")
);
//App.js (Where I attempt to suspend until my data is loaded into state)
import React, { useEffect, Suspense } from "react";
import { useStoreActions } from "easy-peasy";
import Header from "../components/Header";
import Content from "../components/Content";
import "./App.css";
const content = "default";
const App = () => {
const { loadContent } = useStoreActions((actions) => ({
loadContent: actions.payforward.loadContent,
}));
useEffect(() => {
async function fetchData() {
await loadContent(content);
}
fetchData();
}, [loadContent]);
return (
<div className='App'>
<Header />
<Content />
</div>
);
};
export default App;
//Header.js (Where I attempt to reference some URLs from the JSON file applied to my state)
import React, { useMemo } from "react";
import { useStoreState } from "easy-peasy";
const Header = () => {
//Unable to access state because it's currently undefined until the JSON is loaded in
const headerURLs = useStoreState(
(state) => state.company.partnerContent.routes.header.links
);
return (
<div>
<h1>This is the Header</h1>
{/* {headerURLs.map((url) => {
return <p>{url}</p>;
})} */}
</div>
);
};
export { Header as default };
I'm testing out Apollo Graphql with React and I'm trying to update the local state with Apollo Graphql with a nested object. I'm running into an issue. The data returns a null value and does not even return the value I set as a default. The only warning I see is Missing field __typename. I'm not sure what I'm missing or if this is not how you properly set nested values with Graphql or Apollo issue. I have a code sandbox with the example I'm trying to do https://codesandbox.io/embed/throbbing-river-xwe2y
index.js
import React from "react";
import ReactDOM from "react-dom";
import ApolloClient from "apollo-boost";
import { ApolloProvider } from "#apollo/react-hooks";
import App from "./App";
import "./styles.css";
const client = new ApolloClient({
clientState: {
defaults: {
name: {
firstName: "Michael",
lastName: "Jordan"
}
},
resolvers: {},
typeDefs: `
type Query {
name: FullName
}
type FullName {
firsName: String
lastName: String
}
`
}
});
client.writeData({
data: {
name: {
firstName: "Kobe",
lastName: "Bryant"
}
}
});
const rootElement = document.getElementById("root");
ReactDOM.render(
<ApolloProvider client={client}>
<App />
</ApolloProvider>,
rootElement
);
App.js
import React from "react";
import Name from "./Name";
import { useApolloClient } from "#apollo/react-hooks";
function App() {
const client = useApolloClient();
client.writeData({
data: {
name: {
firstName: "Lebron",
lastName: "James"
}
}
});
return (
<div>
<Name />
</div>
);
}
export default App;
Name.js
import React from "react";
import { NAME } from "./Queries";
import { useApolloClient } from "#apollo/react-hooks";
const Name = async props => {
const client = useApolloClient();
const { loading, data } = await client.query({ query: NAME });
console.log(data);
return <div>Hello {data.name.firstName}</div>;
};
export default Name;
QUERIES.js
import gql from "graphql-tag";
export const GET_NAME = gql`
{
name #client {
firstName
lastName
}
}
`;
Unfortunately, Apollo Client's documentation is not good in this manner and simply starts using __typename without properly explaining the reasoning behind it directly. I've seen other engineers struggling to understand its purpose before. As the warning is suggesting, you must pass a __typename property to objects you write directly to the cache, as Apollo Client will use this value by default in its data normalization process internally, to save/identify the data.
On all your calls to client.writeData, you should include a __typename property, like:
client.writeData({
data: {
name: {
__typename: 'FullName', // this is the name of the type this data represents, as you defined in your typeDefs
firstName: 'Lebron',
lastName: 'James',
},
},
});
Also, you can't use async/await on the render method of your component -- in the case of function components, the main body itself, as Promises are not valid React elements. So you have two options:
switch from client.query to the useQuery hook; or
since you're only requesting client-side fields, you can use the client.readQuery method which is synchronous and will return the data to you without a Promise. Note that with this method you're only able to make client-side requests, i.e if you want to request client and server fields at the same time, it won't work.
I'm creating an apollo react application. I want apollo to manage my local state. I want to structure my local state so not all scalar values are at the top level.
This is my configuration:
import React from 'react'
import ReactDOM from 'react-dom'
import ApolloClient from 'apollo-boost'
import gql from 'graphql-tag'
import { InMemoryCache } from 'apollo-cache-inmemory'
import { ApolloProvider, Query } from 'react-apollo'
const defaults = {
author: null
}
const resolvers = {
Query: {
author() {
return { id: 1 }
}
}
}
const typedefs = gql(`
type Query {
author: Author
}
type Author {
id: Int
}
`)
const apolloClient = new ApolloClient({
clientState: {
defaults,
resolvers,
typedefs
},
cache: new InMemoryCache()
});
ReactDOM.render(
<ApolloProvider client={ apolloClient }>
<Query query={ gql`{ author #client }` }>
{ ({ data }) => console.log(data.author) || null }
</Query>
</ApolloProvider>,
document.getElementById('app')
)
Then this app logs undefined. I.e., the query { author #client { id }} returns undefined in data.author.
It must be noted that when I set the type of author as Int, default value as 1, and I do the query { author #client }, the app correctly logs 1.
How can I have some structure in my local state with Apollo?
These are my relevant dependencies:
apollo-cache "^1.1.20"
apollo-cache-inmemory "^1.3.8"
apollo-client "^2.4.5"
apollo-link "^1.0.6"
apollo-link-error "^1.0.3"
apollo-link-http "^1.3.1"
apollo-link-state "^0.4.0"
graphql-tag "^2.4.2"
Solved:
Apparently I had to add __typename: 'Author' to the defaults author this way:
const defaults = {
author: {
__typename: 'Author'
}
}