How to use GraphQL mutation in Next.js without useMutation - reactjs

I'm working on a codebase with Next.js version 9.3.0 and GraphQL. To get the benefits of Next.js optimizations, we are wrapping each page in withApollo, so inside the pages we can make use of useQuery, useMutation.
The issue I'm facing is that I need to use mutation in the Header component which is outside the page, which doesn't have access to ApolloClient because the app is not wrapped in withApollo.
The error I'm getting is this Could not find "client" in the context or passed in as an option. Wrap the root component in an <ApolloProvider>, or pass an ApolloClient instance in via options.
The Header component is inside the Layout component like this:
<>
<Meta />
<Header/>
{children} // pages which are composed with withApollo
<Footer />
</>
Instead of using useQuery in a component without withApollo is easy you can use
import { createApolloFetch } from 'apollo-fetch';
const fetch = createApolloFetch({
uri: process.env.GRAPHQL_SERVER,
});
const userInfoQuery = `{
userInfo {
loggedIn
basketCount
wishListCount
}
}`;
const userInfoData = fetch({
query: userInfoQuery,
});
Is there an alternative solution for useMutation in a component not composed with withApollo?
Any suggestions are welcomed,
Cheers

Silly me, mutations work with Apollo fetch too
https://github.com/apollographql/apollo-fetch#simple-graphql-mutation-with-variables but this is not maintained anymore
The solution that worked for me, in the end, was to still useMutation and to pass the client to it.
https://www.apollographql.com/docs/react/data/mutations/#usemutation-api
import { useMutation } from '#apollo/react-hooks';
import createApolloClient from 'lib/apolloClient';
import loginUser from 'mutations/login';
const [getToken, { data: mutationData, error: loginErrors, loading }] = useMutation(loginUser, { client: createApolloClient()});

Related

How to read RTK Query state without hooks in react-router 6.4

Recently new react-router 6.4 was released and it has the ability to load data before render of component. (https://reactrouter.com/en/main/start/overview#data-loading)
This seems like a cool feature. And I want to use it with RTK Query, since I already use Redux Toolkit.
So I want to to a basic thing, I have the api to load posts. I want to load them, and if request fails - redirect to other page. In react-router 6.4 it all can be done in router(https://reactrouter.com/en/main/start/overview#redirects).
Router is outside of scope of react, so I can not use hooks which are provided by rtk query, so it means that I have to use rtk query without hooks, which according to documentation is totally possible (https://redux-toolkit.js.org/rtk-query/usage/usage-without-react-hooks)
So my problem is, how do I read status of the request IN the react-router loader. I am able to read status in components, using hooks, but it makes components "dirty" and spreads the logic across the app.
import React from "react";
import ReactDOM from "react-dom/client";
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import { Provider } from "react-redux";
import { store } from "./redux/redux";
import { Comments } from "./Comments";
import { Posts } from "./Posts";
import { Root } from "./Root";
import { postsApi } from "./redux/redux";
const router = createBrowserRouter([
{
path: "/",
element: <Root />,
},
{
path: "posts",
element: <Posts />,
loader: () => {
store.dispatch(postsApi.endpoints.getPosts.initiate());
const request = postsApi.endpoints.getPosts.select()(store);
console.log(request);
return request;
},
},
{
path: "/comments",
element: <Comments />,
},
]);
const root = ReactDOM.createRoot(
document.getElementById("root") as HTMLElement
);
root.render(
<Provider store={store}>
<RouterProvider router={router} />
</Provider>
);
I am trying to use example from the docs, but I always get status "uninitialized" in the console, like the request had never been made, but it was, I can see the data in redux dev tools. And I also get the error "No data found at state.postsApi. Did you forget to add the reducer to the store?"
You are on the right track here, but you need to call unwrap() on the promise returned by dispatch, which will either return the result if successful or throw if not. I've modified your snippet to show what I mean.
loader: () => {
const p = store.dispatch(postsApi.endpoints.getPosts.initiate());
try {
const response = await p.unwrap()
return response
} catch (e) {
// see https://reactrouter.com/en/main/fetch/redirect
return redirect("/login")
} finally {
p.unsubscribe()
}
},
You would probably do something along the lines
const promise = dispatch(api.endpoints.myEndpoint.initiate(someArgument))
await promise // wait for data to be there
promise.unsubscribe() // remove the subscription. The data will stay in cache for 60 seconds and the component can subscribe to it in that timeframe.
Note that I do not access the data here and you probably shouldn't.
While it will be available after that await promise, I would use the loader only for data to be present - and then use the normal useMyEndpointQuery(someArgument) in the component to access that data.
You need to use the hook so the cache knows that your component is actually using that data - otherwise it would be removed from the cache after 60 seconds (or if you never unsubscribed, it would never be removed).
At that point there is no real benefit of passing that data from the loader function into the component - it will already be able to access it through the hook anyways.
So my suggestion: initiate and await the fetch from the loader, but actually access the data as before from the component.

Import global vs getStaticProps in Next/React Component

So I'm new in Next.js and I'm wondering, in my index.js file in pages I have a component that will return a list from a JSON object. So whats the difference between importing this JSON file outside my component or getting it inside getStaticProps?
import contactList from '../../public/contactList.json'
export default function Home () {
// ... component here
}
OR
export async function getStaticProps() {
import contactList from '../../public/contactList.json'
return {
props: {contactList}
}
}
export default function Home () {
// ... component here
}
OR even i could instead set an environment variable in Vercel for using fetch.
Whats the pros and cons of each of these?
EDIT: I should use import(adress) inside getStaticProps, witch will return an object with the json "array" in a default param, so correcting myself:
export async function getStaticProps() {
const list = await import ('../../public/contactList.json');
return {
props: { contactList: list.default }
}
}
Basically it boils down to better performance. From the Next.js docs:
Note: You can import modules in top-level scope for use in getStaticProps. Imports used in getStaticProps will not be bundled for the client-side.
So the data will be fetched at build time if you use the import in getStaticProps and the user accessing the website will have to download fewer data which will result in a faster page.
EDIT:
The Next.js docs explicitly state not to use fetch to get internal data:
Note: You should not use fetch() to call an API route in getStaticProps. Instead, directly import the logic used inside your API route. You may need to slightly refactor your code for this approach.
Fetching from an external API is fine!

Props passed to the enclosed component of withAuthenticator HOC is empty

I am expecting props.onStateChange but props is empty object.
Props passed to the enclosed component of withAuthenticator HOC is empty.
import { withAuthenticator } from "#aws-amplify/ui-react";
export const App = withAuthenticator((props) => {
console.log('props',props) // {}
return (
<BrowserRouter>
......
</BrowserRouter>
);
});
The thing I want to accomplish is sign-out functionality. I tried Auth.signOut() .But this is just clearing the localStorage but redirecting to sign-in page is not happening.
I searched for such issue and found out that
When using the Auth.signOut from within the withAuthenticator it will
not sign out because it is only updating the session locally in
LocalStorage. You need to have a way to rerender the actual
withAuthenticator component.
https://github.com/aws-amplify/amplify-js/issues/1529
https://github.com/aws-amplify/amplify-js/issues/4643
Solution that provide includes using props.onStateChange but the props in my case is empty.
Where am I wrong?
After some R&D I found that
import { withAuthenticator } from "#aws-amplify/ui-react";
Here withAuthenticator HOC doesn't provide any props.
And this approach is used to use pre-built UI components
import { withAuthenticator } from "aws-amplify-react";
Here withAuthenticator HOC provides props
authState
authData
onStateChange.
And this approach is used to customize (create) our own UI.

React Apollo Error: Invariant Violation: Could not find "client" in the context or passed in as an option

I'm building a project using React, Apollo and Next.js. I'm trying to update react-apollo to 3.1.3 and I'm now getting the following error when viewing the site.
Invariant Violation: Could not find "client" in the context or passed in as an option. Wrap the root component in an , or pass an ApolloClient instance in via options.
If I downgrade the react-apollo package to 2.5.8 it works without issue so I'm thinking something has changed between 2.5 and 3.x but can't find anything in the react-apollo or next-with-apollo documentation to indicate what that might be. Any assistance would be greatly appreciated.
withData.js
import withApollo from 'next-with-apollo';
import ApolloClient from 'apollo-boost';
import { endpoint } from '../config';
function createClient({ headers }) {
return new ApolloClient({
uri: endpoint,
request: operation => {
operation.setContext({
fetchOptions: {
credentials: 'include'
},
headers
});
},
// local data
clientState: {
resolvers: {
Mutation: {}
},
defaults: {}
}
});
}
export default withApollo(createClient);
_app.js
import App from 'next/app';
import { ApolloProvider } from 'react-apollo';
import Page from '../components/Page';
import { Overlay } from '../components/styles/Overlay';
import withData from '../lib/withData';
class MyApp extends App {
static async getInitialProps({ Component, ctx }) {
let pageProps = {};
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx);
}
// this exposes the query to the user
pageProps.query = ctx.query;
return { pageProps };
}
render() {
const { Component, apollo, pageProps } = this.props;
return (
<ApolloProvider client={apollo}>
<Overlay id="page-overlay" />
<Page>
<Component {...pageProps} />
</Page>
</ApolloProvider>
);
}
}
export default withData(MyApp);
In my case, I found that I had react-apollo#3.0.1 installed as well as #apollo/react-hooks#3.0.0. Removing #apollo/react-hooks and just relying on react-apollo fixed the invariant issue for me. Make sure that you aren't using any mismatched versions in your lock file or package.json
This is what someone said in a GitHub issue thread, which, was the case for me too. Make sure you try it!
I've had a mixture of solutions, i think it does boil down to how you initially go about setting up all the related packages.
"Some packages don't work well with others when it comes to connecting the client to Reacts Context.Provider"
I've had two go two fixes that seem to work well (With new projects and updating old):
1: Uninstall #apollo/react-hooks
Then:
import { ApolloProvider } from "#apollo/client";
instead of:
import { ApolloProvider } from "react-apollo";
(This allowed me to keep the "#apollo/react-hooks" package without conflicts)
3: Double-check that the server that is serving HttpLink client URI is up and running for the client to connect (This give a different error then the one were talking about but is still good to know in this situation)
Conclusion: It can be a slight bit of trial and error, but try to use the matching/pairing packages
I uninstalled 'react-apollo', used '#apollo/client' instead, it solved the issue for me.
import gql from 'graphql-tag';
import {graphql} from '#apollo/react-hoc';
import { ApolloClient, InMemoryCache } from '#apollo/client';
import { ApolloProvider } from '#apollo/react-hooks';
These imports worked for me perfectly. I had a great time debugging and finding different import libraries but finally after 3 hours this was the solution for using graphql and appolo.
I found this to be the solution as well, though now I'm only using #apollo/client and apollo-link since they are the latest version.
import {ApolloProvider} from 'apollo/client' instead 'react-apollo'or '#apollo/react-hooks'

How do you get URL parameters from react-router outside components to use in GraphQL query

Is it possible to access the URL params supplied in a route outside the components?
This of course can be accomplished with window.location but I am looking for something in the react-router API to do this more cleanly.
Additionally, if there is a more standard approach to doing this with GraphQL, that's even better! I am just looking into browser clients for gql and new to gql in general.
Example component:
import React from 'react';
import { graphql } from 'react-apollo';
import gql from 'graphql-tag';
import get from 'lodash.get';
const ContractComponent = (props) => {
...
};
const ContractQuery = gql`query ContractQuery ($contractId: String!){
contract(id: $contractId) {
...
}
}`;
const ContractWithData = graphql(ContractQuery, {
options: { variables: { contractId: contractId } }, // <-- var i want to pull from url params
})(ContractComponent);
export default ContractWithData;
Example route:
<Route path="contract/:contractId" component={Contract} />
I'm not really familiar with react-apollo, but I think the following is a good way to achieve what you want.
You can see in the docs that options can be a function taking the component props as input, returning an object with the options: http://dev.apollodata.com/react/queries.html#graphql-options
So I think you can do:
const ContractWithData = graphql(ContractQuery, {
options: props => ({ variables: { contractId: props.params.contractId } })
})(ContractComponent);
In the code above, props.params are the params passed by react-router to the route component (nothing special there).
You want to write options as a function when you need do to something dynamic at runtime, in this case accessing the route params.
Hope it helps.

Resources