I have a React app which is using Apollo Client. I'm using apollo-link-state and apollo-cache-persist and need to reset my store to its default values when client.resetStore() is called in my app.
The docs say that when creating a client you should call client.onResetStore(stateLink.writeDefaults) and this will do the job but writeDefaults is exposed by Apollo Link State which i don't have direct access to as Iām using Apollo Boost.
Here is my Apollo Client code:
import ApolloClient from 'apollo-boost';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { persistCache } from 'apollo-cache-persist';
import { defaults, resolvers } from "./store";
const cache = new InMemoryCache();
persistCache({
storage: window.localStorage,
debug: process.env.NODE_ENV !== 'production',
cache
});
const client = new ApolloClient({
uri: process.env.NODE_ENV === 'production' ? 'https://five-yards-api.herokuapp.com/graphql' : 'http://localhost:7777/graphql',
credentials: 'include',
clientState: {
defaults,
resolvers
},
cache
});
// TODO: Doesn't work as expected
client.onResetStore(client.writeDefaults);
export default client;
I use Apollo Client 2.x, and Apollo Boost may be the same.
I had the same problem with Apollo Client 2.x, and below was the solution for me.
In the root file where you configure your Apollo (e.g. App.js):
cache.writeData({data : defaultData }); # I assume you already have this to initialise your default data.
# Then, you just add below underneath.
client.onResetStore(() => {
cache.writeData({data : defaultData });
});
const App = () => (...
AFAIK the only way to this is to migrate from Apollo Boost which configures a lot of things under the hood for you and set up Apollo Client manually. After migrating I was able to call onResetStore() as per the docs and everything is working :)
Apollo Boost migration:
https://www.apollographql.com/docs/react/advanced/boost-migration.html
Related
I have a React/Next.js app which is using GraphQL and Apollo to connect and interact with a headless API. I am using Strapi as my headless API/CMS which is working great, except for one issue. I am trying to upload a file from my React app to a content type in my Strapi CMS using a GraphQL mutation and it keeps failing on the upload part.
When I upload a file using Altair(my playground environment) to Strapi with the exact same mutation everything works fine, but once I try to run the same mutation from my React app I get this error:
Variable \"$file\" got invalid value {}; Upload value invalid.. Everything I see online brings up using apollo-upload-client and adding something like link: createUploadLink({ uri: "http://localhost:4300/graphql" }), to my Apollo client initiation. I have tried that but whenever I use it, it breaks my app and I get this GraphQL error Error: Cannot return null for non-nullable field UsersPermissionsMe.username..
It seems like the only answer I can find is using apollo-upload-client but when I use it, it seems to break my app. I don't know if I need to use it differently maybe because I am using Strapi, or Next, or #apollo/client. I am a little lost on this one.
This is how I initiate my Apollo client and when everything works except uploading a file.
import { ApolloClient, InMemoryCache, NormalizedCacheObject } from "#apollo/client";
import { parseCookies } from "nookies";
import { useMemo } from 'react'
import getConfig from "next/config";
const { publicRuntimeConfig } = getConfig();
let apolloClient: ApolloClient<NormalizedCacheObject>;
function createApolloClient(ctx) {
return new ApolloClient({
ssrMode: typeof window === "undefined",
uri: publicRuntimeConfig.PUBLIC_GRAPHQL_API_URL,
cache: new InMemoryCache(),
headers: {
Authorization: `Bearer ${ctx ? parseCookies(ctx).jwt : parseCookies().jwt}`,
},
});
}
My app runs perfect with this code except for the fact that I get "message":"Variable \"$file\" got invalid value {}; Upload value invalid.", whenever I try to upload a file. So when I try to use apollo-upload-client to fix that like this:
import { ApolloClient, InMemoryCache, NormalizedCacheObject } from "#apollo/client";
import { createUploadLink } from 'apollo-upload-client';
import { parseCookies } from "nookies";
import { useMemo } from 'react'
import getConfig from "next/config";
const { publicRuntimeConfig } = getConfig();
let apolloClient: ApolloClient<NormalizedCacheObject>;
function createApolloClient(ctx) {
return new ApolloClient({
ssrMode: typeof window === "undefined",
// uri: publicRuntimeConfig.PUBLIC_GRAPHQL_API_URL,
link: createUploadLink({ uri: publicRuntimeConfig.PUBLIC_GRAPHQL_API_URL }),
cache: new InMemoryCache(),
headers: {
Authorization: `Bearer ${ctx ? parseCookies(ctx).jwt : parseCookies().jwt}`,
},
});
}
I get this error: Error: Cannot return null for non-nullable field UsersPermissionsMe.username..
I am still new to GraphQL and Strapi so maybe I am missing something obvious. Everything works except uploads. I can upload from my playground, just not from my app, that is where I am at.
So after two days of this issue, I figured it out after 10 minutes of reading the apollo-upload-client documentation. So much shame. All I needed to do was move my header request from the ApolloClient options to the createUploadLink options. Final fix was
function createApolloClient(ctx) {
return new ApolloClient({
ssrMode: typeof window === "undefined",
link: createUploadLink({
uri: publicRuntimeConfig.PUBLIC_GRAPHQL_API_URL,
headers: {
Authorization: `Bearer ${ctx ? parseCookies(ctx).jwt : parseCookies().jwt}`,
},
}),
cache: new InMemoryCache(),
});
}
I'm trying to deploy my application to a production environment, but having some trouble wiring it all together.
I've got a create-react-app for my frontend, which is served up by a simple express/serve server. On the backend, I've got NGINX proxying successfully to my API server, which is using Apollo to serve my data. The API is running on port 4000.
The Apollo-Server is as-follows, and works fine:
import { resolve } from "path";
import dotenv from "dotenv";
const envi = process.env.NODE_ENV;
dotenv.config({ path: resolve(__dirname, `../.${envi}.env`) });
import "reflect-metadata";
import { connect } from "./mongodb/connect";
import { buildSchema } from "type-graphql";
import { ApolloServer } from "apollo-server";
import { SenateCommitteeResolver, HouseCommitteeResolver } from "./resolvers";
import { populateDatabase } from "./util";
(async () => {
// Connect to MongoDB
await connect();
console.log(`š Databases connected`);
const schema = await buildSchema({
resolvers: [HouseCommitteeResolver, SenateCommitteeResolver],
emitSchemaFile: resolve(__dirname, "schema.gql"),
});
// If development, set database docs
envi === "development" && (await populateDatabase());
// Launch the server!
const server = new ApolloServer({
schema,
playground: true,
});
// Server listens at URL
const { url } = await server.listen(4000);
console.log(`š Server ready, at ${url}`);
})();
I'm trying to connect my express server to the Apollo Server, but that's where I'm running into problems. The application is supposed to connect using Apollo's Client and HTTP Link, because I'm using Apollo Client on the frontend too:
import React, { useEffect } from "react";
import { AppRouter } from "./routers";
import ReactGA from "react-ga";
import { ApolloProvider } from "#apollo/client";
import client from "./graphql/client";
import "./styles/index.scss";
function App(): React.ReactElement {
return (
<ApolloProvider client={client}>
<AppRouter />
</ApolloProvider>
);
}
export default App;
And here's the client file:
import { ApolloClient, InMemoryCache, createHttpLink } from "#apollo/client";
const httpLink = createHttpLink({ uri: process.env.REACT_APP_API as string });
const cache = new InMemoryCache();
const client = new ApolloClient({
link: httpLink,
cache,
connectToDevTools: true,
});
export default client;
However, when the user navigates to the site and the site itself tries to make a request to my backend, I'm getting a CORS error:
Access to fetch at 'https://www.cloture.app/' from origin 'https://cloture.app' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
What's going wrong? How can I connect Apollo's client with my Apollo Server on the backend?
Adding it here, because the suggestion requires some code.
Try adding :
const server = new ApolloServer({
schema,
playground: true,
cors: {
origin: "*" // it will allow any client to access the server, but you can add specific url also
}
});
I am trying to use ApolloClient with a local IP as uri, but when I set it, it automatically changes it from http to https and, of course, it doesn't work locally.
I've tried 2 way of configuring Gatsby to use ApolloClient.
The first way is in gatsby-browser like so:
import React from 'react';
import 'core-js/modules/es6.set';
import 'core-js/modules/es6.map';
import 'raf/polyfill';
import Apollo from 'providers/Apollo';
export const wrapRootElement = ({ element }) => <Apollo>{element}</Apollo>;
And, the ApolloClient config:
import React from 'react';
import { ApolloProvider, ApolloClient, InMemoryCache } from '#apollo/client';
export default ({ children }) => {
const client = new ApolloClient({
uri: 'http://192.162.1.112:4000/graphql',
cache: new InMemoryCache(),
request: async operation => {
...
},
fetchOptions: {
mode: 'no-cors',
},
});
return <ApolloProvider client={client}>{children}</ApolloProvider>;
};
The second way is using the plugin gatsby-plugin-apollo in gatsby-config like so
{
resolve: 'gatsby-plugin-apollo',
options: {
uri: 'http://192.168.1.112:4000/graphql'
}
}
Notice both uri have http.
Also, it is either one or the other, not both. (Although I've tried with both and it the same result).
This is what I get trying to do a gql query in the network tab:
It is enforcing https and I can't test locally. How do can I make request to http using Gatsby and ApolloClient?
By the way, I set ApolloClient just like this in another project that doesn't use Gatsby (obviously not using the gatsby plugin either) and it works as expected.
There are a few confusing things in the way you are using apollo client.
There is no point in explaining something that has already been done.
You might want to check out this talk and demo by Jason Lengstorf. Here he explained how one can get started with gatsby + apollo client.
Youtube
gatsby-with-apollo
I'm stuck on making firebase work in my gatsby application that uses Redux with Redux-sagas. I know about the existence of firebase-sagas but I'm trying to make without using it.
I'm trying to init firebase auth by:
import * as firebase from 'firebase/app';
import 'firebase/auth';
export const app = firebase.initializeApp(
{
apiKey : "apiKey",
authDomain : "project.firebaseapp.com",
databaseURL : "https://project.firebaseio.com",
projectId : "project",
storageBucket : "project.appspot.com",
appId : "appId"
}
)
export const authRef = () => app.auth(); //also tried: firebase.auth() and firebase.auth(app)
//firebase.auth returns a function, but firebase.auth() throws error
I have the following config on my gatsby-node.js:
const path = require('path');
exports.onCreateWebpackConfig = ({ actions, plugins, loaders, getConfig }) => {
const config = getConfig()
config.resolve = {
...config.resolve,
mainFields: ['module', 'browser', 'main'],
alias: {
...config.resolve.alias,
['firebase/app'] : path.resolve(__dirname, 'node_modules/firebase/app/dist/index.cjs.js'),
['firebase/auth'] : path.resolve(__dirname, 'node_modules/firebase/auth/dist/index.cjs.js'),
}
}
actions.replaceWebpackConfig(config)
}
It trows the error:
{ [M [Error]: The XMLHttpRequest compatibility library was not found.]
code: 'auth/internal-error',
message: 'The XMLHttpRequest compatibility library was not found.' }
I think it's some problem related to webpack. I would love any insights on this problem :)
As Gatsby builds pages in a server environment, you can't access Firebase during Gatsby build time. Firebase calls (using the Web SDK) have to happen when the user is on a browser/client environment.
One solution to this problem is creating a function like so:
firebase.js:
import firebase from '#firebase/app';
import '#firebase/auth';
import '#firebase/firestore';
import '#firebase/functions';
const config = {
... firebase config here
};
let instance;
export default function getFirebase() {
if (typeof window !== 'undefined') {
if (instance) return instance;
instance = firebase.initializeApp(config);
return instance;
}
return null;
}
This file returns a function, which returns an instance of Firebase if the user has the global window available (e.g. on the browser). It also caches the Firebase instance to ensure it cannot be reinitialised again (in case of the user changing page on your website).
In your components, you can now do something similar to the following:
import getFirebase from './firebase';
function MyApp() {
const firebase = getFirebase();
}
As Gatsby will try to build this page into HTML during gatsby build, the firebase const will return null, which is correct, as the Firebase Web SDK cannot initialise on a server environment. However, to make use of Firebase on your website, you need to wait until Firebase is available (so the user has to have loaded your website), so we can make use of Reacts useEffect hook:
import React { useEffect } from 'react';
import getFirebase from './firebase';
function MyApp() {
const firebase = getFirebase();
useEffect(() => {
if (!firebase) return;
firebase.auth().onAuthStateChanged((user) => { ... });
}, [firebase]);
}
This works as Firebase is being used in a browser environment and has access to the browser, which is needed for the Web SDK to work.
It does have drawbacks; your compo have to return null in instances when you need Firebase to display content, which will mean your HTML build on the server will not contain any HTML, and it'll be injected via the client. In most cases though, such as an account page, this is fine.
If you need access to data from say Cloud Firestore to display page content, you're best using the Admin SDK to fetch content and add it to GraphQL during Gatsby build. That way it will be available on the server during build time.
Sorry if that was a waffle or not clear!
I have a Next.JS app with React and Apollo Client that's behaving weirdly when deployed to Heroku. The only explanation that fits what I'm seeing is that Heroku config variables aren't set early enough for doing server side rendering.
I have set a config var called API_URL (which is the address of a GraphQL API) and am using it when setting up Apollo Client, like so:
import withApollo from "next-with-apollo";
import ApolloClient, { InMemoryCache } from "apollo-boost";
export default withApollo(({ ctx, headers, initialState }) => {
// Logging to double check the API_URL
console.log(`process.env.API_URL: ${process.env.API_URL}`);
return new ApolloClient({
uri: process.env.API_URL,
cache: new InMemoryCache().restore(initialState || {})
});
});
Then _app.tsx is wrapped with the withApollo-component, like so:
// ...imports
class MyApp extends App {
render() {
const { Component, pageProps, apollo } = this.props;
return (
<Container>
<ApolloProvider client={apollo}>
<ThemeProvider theme={theme}>
<Component {...pageProps} />
</ThemeProvider>
</ApolloProvider>
</Container>
);
}
}
export default withApollo(MyApp);
All according to the example setup at next-with-apollo.
This all works just fine on localhost, but breaks with a bunch of 501-errors when running on Heroku. The Heroku config var API_URL has been set (I triple checked the Heroku dashboard).
The added logging in withApollo shows that process.env.API_URL is the expected value on localhost but undefined when running on Heroku.
I also tried using the Next.JS getInitialProps function to perform server side rendering and get the same results, console logging the Heroku config var shows it's always undefined.
Requests made after page load does not show this behavior at all.
Finally, when using a default value for the config value everywhere, e.g. uri = process.env.API_URL || "https://hardcoded.url/" everything works like a charm.
Should I not expect this to work? It seems to me like Heroku just doesn't set the config variables in time to use them when doing server side rendering?