Reactjs useState hook not updating on promise data - reactjs

I am using react hook instead of class based component but it is not updating the state when i fetch data from graphql API.
Here you go for my code:
import React, { useEffect, useState } from 'react';
import client from '../gqlClient';
import { gql, ApolloClient, InMemoryCache } from '#apollo/client';
const client = new ApolloClient({
uri: 'http://localhost:8000/graphql/',
cache: new InMemoryCache(),
});
function EmpTable() {
const [employee, setEmployee] = useState({});
useEffect(() => {
client
.query({
query: gql`
query {
employees {
name
}
}
`
})
.then(result => {
setEmployee({result});
console.log(employee);
});
}, [])
return (
<div>return something</div>
)
};
export default EmpTable;
When i print the employee It prints the initial value only.
But when print result, the console showing all the data that i have from API.
I made the useEffect only run once the page/component is loaded but it is not working.
Can anyone help me how to fix the issue?

setEmployee is the asynchronous method so you can't get the updated value of employee immediately after setEmployee.
setEmployee({result});
console.log(employee); // This will show the old `employee` value.
You should get the updated result in the useEffect with adding a employee dependency.
useEffect(() => {
client
.query({
query: gql`
query {
employees {
name
}
}
`
})
.then(result => {
setEmployee({result});
console.log(employee);
});
}, [])
useEffect(() => {
console.log(employee);
}, [employee]);

There is another solution that you can use a custom hook to use your returned value in any component you want to use.
import React, { useEffect, useState } from 'react';
import client from '../gqlClient';
import { gql, ApolloClient, InMemoryCache } from '#apollo/client';
const client = new ApolloClient({
uri: 'http://localhost:8000/graphql/',
cache: new InMemoryCache(),
});
const useCustomApi=()=>{
const [employee, setEmployee] = useState({});
useEffect(() => {
client
.query({
query: gql`
query {
employees {
name
}
}
`
})
.then(result => {
setEmployee({result});
});
}, [])
return employee;
}
function EmpTable() {
const employee = useCustomApi();
console.log(employee);
return (
<div>{JSON.stringify(employee,null,2)}</div>
)
};
export default EmpTable;

You need to use useEffect hook to achieve this.
more information about How to use setState callback on react hooks How to use `setState` callback on react hooks
here is your code should be:
import React, { useEffect, useState } from 'react';
import client from '../gqlClient';
import { gql, ApolloClient, InMemoryCache } from '#apollo/client';
const client = new ApolloClient({
uri: 'http://localhost:8000/graphql/',
cache: new InMemoryCache(),
});
function EmpTable() {
const [employee, setEmployee] = useState({});
useEffect(() => {
client
.query({
query: gql`
query {
employees {
name
}
}
`
})
.then(result => {
setEmployee({result});
});
}, [])
useEffect(() => {
console.log(employee);
}, [employee]);
return (
<div>{JSON.stringify(employee,null,2)}</div>
)
};
export default EmpTable;

Related

The react apollo useSubscription hook doesn't work in a function call but works when the same call is assigned to a variable

I am working with apollo graphql client. The subscription in the server is working fine watching for changes.
But in the client, I am not able to log data.
I also tried to mutate but still its resulting in the same thing.
useSubscription(BOOK_ADDED, {
onData: ({ data }) => {
console.log(data)
}
})
The above code doesn't log anything out.
But,
const value = useSubscription(BOOK_ADDED, {
onData: ({ data }) => {
console.log(data)
}
})
console.log(value)
The above code seems to work fine logging out a value.
I am attaching a few codes below for more clarity.
index.js or apollo setup:
import ReactDOM from 'react-dom'
import App from './App'
import {
ApolloClient,
ApolloProvider,
HttpLink,
InMemoryCache,
split,
} from '#apollo/client'
import { setContext } from '#apollo/client/link/context'
import { getMainDefinition } from '#apollo/client/utilities'
import { GraphQLWsLink } from '#apollo/client/link/subscriptions'
import { createClient } from 'graphql-ws'
import Assess from './Asses'
const authLink = setContext((_, { headers }) => {
const token = localStorage.getItem('library-user-token')
return {
headers: {
...headers,
authorization: token ? `bearer ${token}` : null,
},
}
})
const httpLink = new HttpLink({ uri: 'http://localhost:4002' })
const wsLink = new GraphQLWsLink(
createClient({
url: 'ws://localhost:4002',
})
)
const splitLink = split(
({ query }) => {
const definition = getMainDefinition(query)
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
)
},
wsLink,
authLink.concat(httpLink)
)
const client = new ApolloClient({
cache: new InMemoryCache(),
link: splitLink,
})
ReactDOM.render(
<ApolloProvider client={client}>
<Assess />
</ApolloProvider>,
document.getElementById('root')
)
App.js
//App.js
import { useSubscription, useQuery } from "#apollo/client";
import { ALL_BOOKS, BOOK_ADDED } from "./queries";
const App = () => {
console.log(BOOK_ADDED);
const result = useQuery(ALL_BOOKS);
useSubscription(BOOK_ADDED, {
onData: ({ data }) => {
console.log(data);
},
});
console.log(result)
if(result.loading){
return null
}
return (
<div>
{result?.data?.allBooks.map((r) => (
<li key={r.id}>{r.title}</li>
))}
</div>
);
};
export default App
The query and fragment:
const BOOK_DETAILS = gql`
fragment BookDetails on Books {
title
author {
name
}
published
genres
id
}
`;
export const BOOK_ADDED = gql`
subscription {
bookAdded {
...BookDetails
}
}
${BOOK_DETAILS}
`;
After reading the changelogs of #apollo/client. I got to know that the method demonstrated in question is only useful when the version of #apollo/client is >=3.7.0. As my version was #3.6.7 it wasn't logging the value out.
Earlier than this version the function required an onSubscriptionData callback
to perform the same operation, which is now deprecated. I have still demonstrated it below as someone using version <#3.7.0 might find it useful.
useSubscription(BOOK_ADDED,{
onSubscriptionData: ({subscriptionData: data}) =>{
console.log(data)
}
})
You may read the change log here.

How to fetch date by Query Apollo?

My problem is that I cannot see any date I try to fetch.
component.js
render(){
return (
<Query query={QueryCategories}>
{({ data }) => {
console.log("data", data)
return(
data.map((m)=> <p>{m}</p>)
)
}}
</Query>)}
query.js
import { gql } from "#apollo/client";
export const QueryCategories = gql`
query getCategories {
categories {
name
}
}
`;
index.js
import { ApolloClient, InMemoryCache } from '#apollo/client';
const cache = new InMemoryCache({
typePolicies: {
AttributeSet: {
keyFields: false,
},
Attribute: {
keyFields: false,
},
},
});
const client = new ApolloClient({
uri: 'http://localhost:4000',
cache,
});
My main goal is give it as props to another component and connect to redux

Fetch XML restapi with Apollo returns Unexcpected token

I start building my ApolloClient. It looks like this
import { ApolloClient, InMemoryCache, HttpLink } from "#apollo/client"
//import { RestLink } from "apollo-link-rest"
import fetch from "isomorphic-fetch"
const xml2js = require("xml2js")
const parseXmlResponseToJson = xml => {
// The function is not being fired
const { parseString } = xml2js
let jsonFeed = null
parseString(xml, function (err, result) {
jsonFeed = result
})
return jsonFeed
}
const restLink = new HttpLink({
uri:
"https://cors-anywhere.herokuapp.com/https://www.w3schools.com/xml/note.xml",
responseTransformer: async response =>
response.text().then(xml => parseXmlResponseToJson(xml)),
fetch,
})
export const client = new ApolloClient({
link: restLink,
cache: new InMemoryCache(),
})
The provider looks like this
import React from "react"
import { client } from "../apollo"
import { ApolloProvider as Provider } from "#apollo/client"
function ApolloProvider({ children }) {
return <Provider client={client}>{children}</Provider>
}
export default ApolloProvider
And the query I try to use looks like this
import React from "react"
import gql from "graphql-tag"
import { useQuery } from "#apollo/client"
const APOLLO_QUERY = gql`
{
note {
to
from
heading
body
}
}
`
const Component = () => {
const { loading, error, data } = useQuery(APOLLO_QUERY)
console.log("Apollo data", loading, error, data)
The error will return with Unexpected token < in JSON at position 2
// ...
I have been struggling with this for days. What am I doing wrong? I try to make it work with w3schools very simply xml example https://www.w3schools.com/xml/note.xml

How to use Apollo GraphQL subscriptions in the client-side NextJS?

I am new to NextJS. I have a page that needs to display real-time data pulled from a Hasura GraphQL backend.
In other non-NextJS apps, I have used GraphQL subscriptions with the Apollo client library. Under the hood, this uses websockets.
I can get GraphQL working in NextJS when it's not using subscriptions. I'm pretty sure this is running on the server-side:
import React from "react";
import { AppProps } from "next/app";
import withApollo from 'next-with-apollo';
import { ApolloProvider } from '#apollo/react-hooks';
import ApolloClient, { InMemoryCache } from 'apollo-boost';
import { getToken } from "../util/auth";
interface Props extends AppProps {
apollo: any
}
const App: React.FC<Props> = ({ Component, pageProps, apollo }) => (
<ApolloProvider client={apollo}>
<Component {...pageProps}/>
</ApolloProvider>
);
export default withApollo(({ initialState }) => new ApolloClient({
uri: "https://my_hasura_instance.com/v1/graphql",
cache: new InMemoryCache().restore(initialState || {}),
request: (operation: any) => {
const token = getToken();
operation.setContext({
headers: {
authorization: token ? `Bearer ${token}` : ''
}
});
}
}))(App);
And I use it this way:
import { useQuery } from '#apollo/react-hooks';
import { gql } from 'apollo-boost';
const myQuery = gql`
query {
...
}
`;
const MyComponent: React.FC = () => {
const { data } = useQuery(myQuery);
return <p>{JSON.stringify(data)}</p>
}
However, I would instead like to do this:
import { useSubscription } from '#apollo/react-hooks';
import { gql } from 'apollo-boost';
const myQuery = gql`
subscription {
...
}
`;
const MyComponent: React.FC = () => {
const { data } = useSubscription(myQuery);
return <p>{JSON.stringify(data)}</p>
}
What I've tried
I've tried splitting the HttpLink and WebsocketLink elements in the ApolloClient, like so:
import React from "react";
import { AppProps } from "next/app";
import { ApolloProvider } from '#apollo/react-hooks';
import withApollo from 'next-with-apollo';
import { InMemoryCache } from "apollo-cache-inmemory";
import ApolloClient from "apollo-client";
import { split } from 'apollo-link';
import { HttpLink } from 'apollo-link-http';
import { WebSocketLink } from 'apollo-link-ws';
import { getMainDefinition } from 'apollo-utilities';
import { getToken } from "../util/auth";
interface Props extends AppProps {
apollo: any
}
const App: React.FC<Props> = ({ Component, pageProps, apollo }) => (
<ApolloProvider client={apollo}>
<Component {...pageProps}/>
</ApolloProvider>
);
const wsLink = new WebSocketLink({
uri: "wss://my_hasura_instance.com/v1/graphql",
options: {
reconnect: true,
timeout: 10000,
connectionParams: () => ({
headers: {
authorization: getToken() ? `Bearer ${getToken()}` : ""
}
})
},
});
const httpLink = new HttpLink({
uri: "https://hasura-g3uc.onrender.com/v1/graphql",
});
const link = process.browser ? split(
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
);
},
wsLink,
httpLink
) : httpLink;
export default withApollo(({ initialState }) => new ApolloClient({
link: link,
cache: new InMemoryCache().restore(initialState || {}),
}))(App);
But when I load the page, I get an Internal Server Error, and this error in the terminal:
Error: Unable to find native implementation, or alternative implementation for WebSocket!
It seems to me that the ApolloClient is then being generated on the server-side, where there is no WebSocket implementation. How can I make this happen on the client-side?
Found workaround to make it work, take look at this answer https://github.com/apollographql/subscriptions-transport-ws/issues/333#issuecomment-359261024
the reason was due to server-side rendering; these statements must run in the browser, so we test if we have process.browser !!
relevant section from the attached github link:
const wsLink = process.browser ? new WebSocketLink({ // if you instantiate in the server, the error will be thrown
uri: `ws://localhost:4000/subscriptions`,
options: {
reconnect: true
}
}) : null;
const httplink = new HttpLink({
uri: 'http://localhost:3000/graphql',
credentials: 'same-origin'
});
const link = process.browser ? split( //only create the split in the browser
// split based on operation type
({ query }) => {
const { kind, operation } = getMainDefinition(query);
return kind === 'OperationDefinition' && operation === 'subscription';
},
wsLink,
httplink,
) : httplink;
This answer seems to be more actual
https://github.com/apollographql/subscriptions-transport-ws/issues/333#issuecomment-775578327
You should install ws by npm i ws and add webSocketImpl: ws to WebSocketLink argument.
import ws from 'ws';
const wsLink = new WebSocketLink({
uri: endpoints.ws,
options: {
reconnect: true,
connectionParams: () => ({
...getToken() && {Authorization: getToken()}
})
},
webSocketImpl: ws
});
Solution: Make wsLink a function variable like the code below.
// src/apollo.ts
import { ApolloClient, HttpLink, InMemoryCache } from "#apollo/client";
import { GraphQLWsLink } from "#apollo/client/link/subscriptions";
import { createClient } from "graphql-ws";
const httpLink = new HttpLink({
uri: 'http://localhost:3000/graphql'
});
const wsLink = () => {
return new GraphQLWsLink(createClient({
url: 'ws://localhost:3000/graphql'
}));
}
export const apolloClient = new ApolloClient({
link: typeof window === 'undefined' ? httpLink : wsLink(),
cache: new InMemoryCache(),
});
// pages/_app.tsx
import { ApolloProvider } from "#apollo/client";
import { apolloClient } from "../src/apollo";
function MyApp({ Component, pageProps }) {
return (
<ApolloProvider client={apolloClient}>
<Component {...pageProps} />
</ApolloProvider>
);
}

How to call GraphQL outside a component

I have made a bunch of React component calling GraphQL using the Query component and everything is working fine.
In one component I need to have some initial data from the database, but without any visual representation.
I have tried to use the query component but it seems to be triggered only on the render cycle. I have tried to package it into a function and call this function in the component that needs the data. But the code / query is not executed since there's no component to show.
How do I go about getting this data from the database without a component?
I can't find any documentation on how to solve this problem. But I can't be
the only one doing this.
Is ApolloConsumer or ApolloProvider the answer to my problems?
I'm working with conferences and sessions. A conference runs over a couple of days and each day has a number of sessions.
What I'm trying to achieve is to render a page with X numbers of tabs one for each day. Each tab represents a day and it shows the number of sessions for the day.
My sessions page:
import React from 'react';
import FullWidthTabs from '../components/Sessions';
import SessionTab from '../components/SessionTab';
import BwAppBar2 from '../components/BwAppBar2';
import ConferenceDays from '../components/ConferenceDays';
class SessionsPage extends React.Component {
static async getInitialProps() {
console.log("GetInitProps SessionsPage");
}
render() {
let a = ConferenceDays();
return (
<div>
<BwAppBar2 />
{a}
<FullWidthTabs days={['2018-06-11', '2018-06-12', '2018-06-13']} day1={ < SessionTab conferenceId = "57" day = '2018-06-11' / > }
day2={ < SessionTab conferenceId = "57" day = '2018-06-12' / > } day3={ < SessionTab conferenceId = "57" day = '2018-06-13' / > }>
</FullWidthTabs>
</div>
);
}
}
export default (SessionsPage);
Here the dates have been hardcoded in the page just for testing.
But order to know how many days the conference spans i'll have to find the conference and decide the start and end date and generate all the dates in between:
import React, { Component } from 'react'
import { graphql } from 'react-apollo'
import { Query } from 'react-apollo'
import gql from 'graphql-tag'
import Link from '#material-ui/core/Link';
import { useQuery } from "react-apollo-hooks";
import conferencesQuery from '../queries/conferences'
import { Table, Head, Cell } from './Table'
import ConferenceCard from './ConferenceCard';
import Grid from '#material-ui/core/Grid';
import Paper from '#material-ui/core/Paper';
import moment from 'moment';
const CONFERENCE_QUERY = gql`
query conference($conferenceId : ID!){
conference(id: $conferenceId){
title
start_date
end_date
}
}
`
let index = 0;
let loopDate = 0;
let dates = [];
let conferenceId = 57;
const ConferenceDays = () => (
<Query query={CONFERENCE_QUERY} variables={{conferenceId}}>
{({ loading, error, data }) => {
if (loading)
return <div>Fetching</div>
if (error)
return <div>Error</div>
const startDate = moment(data.conference.start_date, 'x');
const endDate = moment(data.conference.end_date, 'x');
for (loopDate = parseInt(data.conference.start_date);
loopDate < parseInt(data.conference.end_date);
loopDate += 86400000) {
let aDate = moment(loopDate, 'x');
dates.push(aDate.format('YYYY-MM-DD').toString());
}
console.log(dates);
return(dates);
}}
</Query>);
export default ConferenceDays
But is this approach incorrect?
Would it be more correct to lift the ConferenceDates component up in the hierarchy?
Kim
You could separate the creation of the ApolloClient to a separate file and use an init function to access the client outside of React components.
import React from 'react';
import {
ApolloClient,
HttpLink,
InMemoryCache,
} from "#apollo/client";
let apolloClient;
const httpLink = new HttpLink({
uri: "http://localhost:4000/graphql",
credentials: "same-origin",
});
function createApolloClient() {
return new ApolloClient({
link: httpLink,
cache: new InMemoryCache(),
});
}
export function initializeApollo() {
const _apolloClient = apolloClient ?? createApolloClient();
if (!apolloClient) apolloClient = _apolloClient;
return _apolloClient;
}
export function useApollo() {
const store = useMemo(() => initializeApollo(initialState), [initialState]);
return store;
}
Then you would use this outside components like this:
const client = initializeApollo()
const res = await client.query({
query: MY_QUERY,
variables: {},
})
I didn't try this myself, but I think this you an idea on how to go about this and how to access the ApolloClient.
If you are using functional components, you can use useApolloClient hook in a function as though it is not a hook.
import { useApolloClient, gql } from "#apollo/client";
MY_QUERY = gql'
query OUR_QUERY {
books{
edges{
node{
id
title
author
}
}
}
}
'
const myFunctionalComponent = () => { // outside function component
const client = useApolloClient();
const aNormalFunction = () => { // please note that this is not a component
client.query({
query: MY_QUERY,
fetchPolicy: "cache-first" // select appropriate fetchPolicy
}).then((data) => {
console.log(data) //do whatever you like with the data
}).catch((err) => {
console.log(err)
})
};
// just call it as a function whenever you want
aNormalFunction()
// you can even call it conditionally which is not possible with useQuery hook
if (true) {
aNormalFunction()
}
return (
<p>Hello Hook!</>
);
};
export default myFunctionalComponent;
ApolloClient has a mutate method. You can just import your Apollo client instance and call apolloClient.mutate.
From a React component you can use useLazyQuery from Apollo Client which will only fire when you call it. Then pass it to your function and call it there.
For people using urql as GraphQL client-
const graphqlClient = createClient({
url: '',
fetchOptions: () => {
return {
headers: { }
};
}
});
const fetchCountries = () => (dispatch: Dispatch) => {
graphqlClient
.query(countriesQuery, getCountriesVariable())
.toPromise()
.then(result => {
dispatch({
type: UPDATE_COUNTRIES,
payload: result.data.countries
});
if (result.error?.message) {
// https://formidable.com/open-source/urql/docs/basics/errors/
logErrorMessage(result.error?.message, 'AppActions.fetchCountries');
}
})
.catch(error => {
logError(error, 'AppActions.fetchCountries');
});
};
Docs: https://formidable.com/open-source/urql/docs/api/core/#clientquery

Resources