Apollo GraphQL failing connection - graphql

My root component is already wrapped with an ApolloProvider tag, but the error message tells me it is not.
Error Message
Invariant Violation: 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.
This error is located at:
in App (created by ExpoRoot)
Problem is my root component is already wrapped with an ApolloProvider tag
React Native Code
IMPORT statements
import {
ApolloClient,
InMemoryCache,
useQuery,
ApolloProvider,
gql,
} from "#apollo/client";
CONNECTION WITH GraphQL
const client = new ApolloClient({
uri: "https://www.outvite.me/gql/gql",
cache: new InMemoryCache(),
defaultOptions: { watchQuery: { fetchPolicy: 'cache-and-network' } },
})
TEST QUERY
const USER_QUERY = gql`
query USER {
users {
nodes {
edge {
username
}
}
}
}
`
DEFAULT APP
THIS IS WHERE THE ERROR IS BEING THROWN
const { data, loading } = useQuery(USER_QUERY) is the line that traceback shows
export default function App() {
const { data, loading } = useQuery(USER_QUERY)
return (
<ApolloProvider client={client}>
<View>
<Text style={styles.text}>Open</Text>
<Text style={styles.text}>Another text</Text>
</View>
<Button title="Toggle Sidebar" onPress={() => toggleSidebarView()} />
<Button title="Change theme" onPress={() => toggleColorTheme()} />
</ApolloProvider>
);
}

If I'm not mistaken, the useQuery hook only works if you're in a component that is already wrapped in the ApolloProvider so you probably want to do something like this
export default function MainApp() {
const { data, loading } = useQuery(USER_QUERY)
return (
<View>
... use 'data' in here somewhere...
</View>
);
}
and then the top-level App component would look like
export default function App() {
return (
<ApolloProvider client={client}>
<MainApp />
</ApolloProvider>
);
}

Related

use loader before matching any route for context global store?

In a regular React App I'd use Redux to manage the state, where I'd dispatch the initial data before matching any route in App, however, Redux is not advised in Remix, so I'm using useContext instead.
Is there a way to call loaders to fetch initial data (e.g. session, objects, etc.) before/without having to match any route and to then store that data in the context global store and then can be accessed by any component whithin the store? That way, the API will only be called during app initialization.
I'm at this moment calling the initial data in the loader of root.tsx, getting it with useLoaderData and then passing it as a prop to StoreProvider to dispatch it in the global state, however, I don't think this should be done like that way.
export let loader: LoaderFunction = async ({ request }) => {
let user = await getUser(request);
const products = await db.product.findMany();
return { user: user?.username, products };
};
function App() {
const data = useLoaderData<LoaderData>();
return (
<html lang="en">
...
<StoreProvider initData={data}>
<body>
...
<Outlet />
<ScrollRestoration />
<Scripts />
{process.env.NODE_ENV === "development" && <LiveReload />}
</body>
</StoreProvider>
</html>
);
}
export default App;
I think doing the data loading on the root route loader is the best way.
If you don't like that approach you could also fetch on entry.server and entry.client.
For example in entry.client you probably have something like this:
import { hydrate } from "react-dom";
import { RemixBrowser } from "remix";
hydrate(<RemixBrowser />, document);
So you can change it to do the fetch before calling hydrate.
import { hydrate } from "react-dom";
import { RemixBrowser } from "remix";
fetch(YOUR_API_ENDPOINT)
.then(response => response.json())
.then(data => {
hydrate(
<YourContextProvider value={data}>
<RemixBrowser />
</YourContextProvider>,
document
)
});
And in entry.server you can change the handleRequest function to something like this:
import { renderToString } from "react-dom/server";
import { RemixServer } from "remix";
import type { EntryContext } from "remix";
export default async function handleRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext
) {
let response = await fetch(YOUR_API_ENDPOINT)
let data = await response.json()
let markup = renderToString(
<YourContextProvider value={data}>
<RemixServer context={remixContext} url={request.url} />
</YourContextProvider>
);
responseHeaders.set("Content-Type", "text/html");
return new Response("<!DOCTYPE html>" + markup, {
status: responseStatusCode,
headers: responseHeaders
});
}
By doing it on entry.client and entry.server the fetch will only happen once and it will never be triggered again.
I still recommend you to do it inside the loader of the root so after an action it can be fetched again to keep the data updated.

How to render errors with apollo-link-error

I would like to use apollo client error link to create a MUI snackbar displaying any error that is returned by graphql.
The setup is a Nextjs web app using Material-ui and Apollo Client.
From Apollo documentation on Error links an error link can be created which will handle errors returned by the graphql API. The Apollo error link can not render anything it self as it will return void.
import { onError } from "#apollo/client/link/error";
// Log any GraphQL errors or network error that occurred
const errorLink = 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}`);
});
My initial thought was to use hooks and context. Creating a hook which the Apollo error link can push notifications on and a context provider in my _app.ts.
However the Apollo client is not a functional component hence this will not work.
I did think about creating a function to handle onError callback in Apollo queries and mutations but it seems like a lot of work to put an onError function on each query/mutation.
This can be handled using context. You have to do some changes to your component hierarchy.
ContextProvider => ApolloProvider
Make sure both are used in different components. Otherwise, you will not able to access hooks.
Ex: You should be able to access the hook inside the Root component, where you can add ApolloProvider.
I've created an example hope that helps (not using apollo but you can add): https://stackblitz.com/edit/react-ts-m7swyo
import React, { createContext, useContext, useReducer } from "react";
import { render } from "react-dom";
import "./style.css";
const Context = createContext({});
const Root = () => {
const { state, dispatch } = useContext(Context);
return (
<ApolloProvider
client={
new ApolloClient({
link: ApolloLink.from([
onError(({ graphQLErrors, networkError }) => {
// Do something and dispatch to context
// dispatch({ type: 'ERROR', error: graphQLErrors || networkError });
}),
]),
})
}
>
<App />
</ApolloProvider>
);
};
const reducer = (state, action) => {
if (action.type === "ERROR") {
return { ...state, ...action.payload };
}
return state;
};
const App = () => {
const [state, dispatch] = useReducer(reducer, {});
return (
<Context.Provider
value={{
state,
dispatch,
}}
>
<Root />
</Context.Provider>
);
};
render(<App />, document.getElementById("root"));

Invalid hook call. React hooks

error:
Error: Invalid hook call. Hooks can only be called inside of the body
of a function component. This could happen for one of the following
reasons
Hello I am trying to use useDispatch in my action but it is generating this error from invalid hoook
I can't solve it
can anybody help me?
my action
import {FETCH_FAIL,FETCH_LOADING,FETCH_SUCESS} from './actionType';
import api from '../../../services/api';
import { useDispatch } from "react-redux";
const FetchSucess = data => (console.log(data),{
type:FETCH_SUCESS,
data
});
const FetchFailed = error => ({
type:FETCH_FAIL,
error
});
const isLoadingFetch = () => ({type: FETCH_LOADING})
export default function AllProducts () {
const dispatch = useDispatch()
dispatch(isLoadingFetch());
// fetching data
api.get('/products')
.then( response => { dispatch(FetchSucess(response.data))})
.catch( err => { dispatch(FetchFailed(err.message));});
}
my component
import React, { useState, useEffect } from 'react';
export default function Cards() {
useEffect(() => {
// This will be invoked only once.
getAllProducts();
}, []);
const classes = useStyles();
const classes2 = useStyles2();
const products = useSelector(state => state.data.filteredProducts);
return (
<div className="App">
<Container maxWidth="md" className={classes.root}>
<Grid container md={4} spacing={1} ></Grid>
<Grid container md={8} spacing={1} alignItems={"center"}>
{products.map(product => (
<Grid item lg={4} md={4} sm={12} xs={12}>
<Card className={classes2.card}>
<CardMedia
className={classes2.media}
image={
"https://www.theclutch.com.br/wp-content/uploads/2019/11/skins-csgo-neymar.jpg"
}
/>
<CardContent className={classes2.content}>
<Typography
className={classes2.name}
variant={"h6"}
gutterBottom
>
{product.name}
</Typography>
<Typography
className={classes2.price}
variant={"h1"}
>
{util.formatCurrency(product.price)}
</Typography>
</CardContent>
</Card>
</Grid>
))}
</Grid>
</Container>
</div>
);
}
Based on this comment above:
All product is my action
If AllProducts is a Redux action that needs to perform an async operation and dispatch other actions in response to that operation, there's a convention available by which Redux will pass dispatch as a function argument. The action just needs to return a function which accepts that argument. For example:
export default function AllProducts () {
return function(dispatch) {
dispatch(isLoadingFetch());
// fetching data
api.get('/products')
.then( response => { dispatch(FetchSucess(response.data))})
.catch( err => { dispatch(FetchFailed(err.message));});
}
}
There's no need to use the hook, that's only necessary within React Function Components or within other hooks (which themselves are used within React Function Components).

Apollo mutations without React <Mutation> component

Apollo's <Mutation> component often works well, but sometimes you need to call mutations outside of the render() method.
In some cases you can simply pass along the mutation function like so:
import React, { Component } from "react";
import { DO_MUTATION } from "./mutations";
import { Mutation } from "react-apollo";
export default class MyComponent extends Component {
render() {
return (
<Mutation mutation={DO_MUTATION}>
{(doMutation) => (
<Button
onPress={() => {
this.handleSomething(doMutation);
}}
/>
)}
</Mutation>
);
}
handleSomething = (doMutation) => {
/* DO SOME STUFF */
doMutation();
};
}
But in other cases this is not a very reasonable option, for example:
import React, { Component } from "react";
import { DO_MUTATION } from "./mutations";
import { Mutation } from "react-apollo";
import SomeLibrary from "SomeLibrary";
export default class MyComponent extends Component {
render() {
return (
<Mutation mutation={DO_MUTATION}>
{(doMutation) => (
<Button
onPress={() => {
SomeLibrary.addListener(this.listenerHandler);
}}
/>
)}
</Mutation>
);
}
listenerHandler = () => {
/* HOW DO I DO MUTATIONS HERE? */
};
}
How can mutations be performed in these scenarios?
Update for React Hooks (2020-12-18):
If you are using Apollo v3+ and functional React components, there is now a much cleaner solution using the useMutation() hook provided by Apollo:
import React from "react";
import { useMutation } from "#apollo/client";
import SomeLibrary from "SomeLibrary";
import { DO_MUTATION } from "./mutations";
export default function MyComponent() {
const [doMutation, { data }] = useMutation(DO_MUTATION);
let listenerHandler = () => {
doMutation({
variables: {
some_var: "some_val",
},
});
};
return (
<button
onPress={() => {
SomeLibrary.addListener(listenerHandler);
}}
/>
);
}
Also, the official docs say:
The useMutation React hook is the primary API for executing mutations
in an Apollo application.
Using hooks is now preferred over HOCs in Apollo, so it is probably a good idea to use useMutation() if you can.
You can read the documentation for useMutation at:
https://www.apollographql.com/docs/react/data/mutations/
Original answer:
react-apollo includes two HOCs called graphql() and withApollo() that can be used to accomplish this.
The difference between the two is described in Apollo's documentation as:
If you are wondering when to use withApollo() and when to use graphql() the answer is that most of the time you will want to use graphql(). graphql() provides many of the advanced features you need to work with your GraphQL data. You should only use withApollo() if you want the GraphQL client without any of the other features.
When graphql() is provided a mutation it will add a this.props.mutate() function and can be used like this:
import React, { Component } from "react";
import { DO_MUTATION } from "./mutations";
import { graphql } from "react-apollo";
import SomeLibrary from "SomeLibrary";
export class MyComponent extends Component {
render() {
return (
<Button
onPress={() => {
SomeLibrary.addListener(this.listenerHandler);
}}
/>
);
}
listenerHandler = () => {
this.props.mutate({
variables: {
some_var: "some_val",
},
});
};
}
export default graphql(DO_MUTATION)(MyComponent);
withApollo() is similar but instead provides a this.props.client for you to use directly. A mutation can be performed like this:
import React, { Component } from "react";
import { DO_MUTATION } from "./mutations";
import { withApollo } from "react-apollo";
import SomeLibrary from "SomeLibrary";
export class MyComponent extends Component {
render() {
return (
<Button
onPress={() => {
SomeLibrary.addListener(this.listenerHandler);
}}
/>
);
}
listenerHandler = () => {
this.props.client.mutate({
mutation: DO_MUTATION,
variables: {
some_var: "some_val",
},
});
};
}
export default withApollo(MyComponent);

Redux: How to pass store to form created outside the Provider scope

I have written code, which uses a Modal dialog to display a form.
My react app is rendered at "root"
index.html
<div id="root"></div>
App.js
const store = configureStore();
ReactDOM.render(
<Provider store={store}>
<ExampleBasic/>
</Provider>
, document.getElementById('root'));
ExmpleBasic.js
Please ignore state management in component here. this is just for example.
import React, { PureComponent } from 'react';
import Lorem from 'react-lorem-component';
import Modal from '#atlaskit/modal-dialog';
import Button from '#atlaskit/button';
export default class ExampleBasic extends PureComponent {
state = { isOpen: false }
open = () => this.setState({ isOpen: true })
close = () => this.setState({ isOpen: false })
secondaryAction = ({ target }) => console.log(target.innerText)
render() {
const { isOpen } = this.state;
const actions = [
{ text: 'Close', onClick: this.close },
{ text: 'Secondary Action', onClick: this.secondaryAction },
];
return (
<div>
<Button onClick={this.open}>Open Modal</Button>
{isOpen && (
<Modal
actions={actions}
onClose={this.close}
heading="Modal Title"
>
<BasicFormContainer />
</Modal>
)}
</div>
);
}
}
BasicFormContainer.js
const mapStateToProps = state => ({
addDesignation: state.designations.addDesignation,
});
const mapDispatchToProps = dispatch => ({
});
export default connect(mapStateToProps, mapDispatchToProps)(BasicForm);
BasicForm.js
import React, { Component } from 'react';
import { Field, reduxForm } from 'redux-form';
class BasicForm extends Component {
constructor(props) {
super(props);
this.submit = this.submit.bind(this);
}
submit(values) {
console.log(values);
}
render() {
const { handleSubmit } = this.props;
return (
<form onSubmit={handleSubmit(this.submit)}>
<Field
name="designationName"
component="input"
placeholder="Name"
label="Enter name"
autoFocus
/>
</form>
);
}
}
export default reduxForm({
form: 'BasicForm',
enableReinitialize: true,
})(BasicForm);
However modal is rendered using portal, outside current DOM.
As modal is rendered outside the scope of redux context, it is not getting the
store. and i am getting an error "Uncaught Error: Field must be inside a component decorated with reduxForm()"
Below is link to same kind of problem, where redux form within portal is not working.
Redux Form Wrapped Inside Custom Portal Component?
in React 16 it is handled by portals, but version before then that you can try something like as follow.
export default class ExampleBasic extends PureComponent {
...
static contextTypes = { store: React.PropTypes.object };
render() {
const { isOpen } = this.state;
const actions = [
{ text: 'Close', onClick: this.close },
{ text: 'Secondary Action', onClick: this.secondaryAction },
];
return (
<div>
<Button onClick={this.open}>Open Modal</Button>
{isOpen && (
<Modal
actions={actions}
onClose={this.close}
heading="Modal Title"
>
<Provider store={this.context.store}>
<BasicFormContainer />
</Provider>
</Modal>
)}
</div>
);
}
}
You need to pass in the values of BasicForm.js to the Redux store and dispatch an action from there itself and not from the BasicFormContainer.js. This way, the Modal remains inside of the scope of your root element and thus there is no need to access the store outside of the Provider.
Then update the Redux store based on the values entered in the form. Once, the store is updated, you can then access it from anywhere in your application such as Modal in your case.
I downgraded to version 2.1.0 to solve the problem.

Resources