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
Related
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.
I have a code which access data from GraphQL API in an arrow function:
const LinkList = () => {
const { loading, error, data } = useQuery(CURRENCIES);
if (loading) return <Loader/>;
if (error) return <pre>{error.message}</pre>
return (
<div className="options">
{data.currencies.map((currency) => {
return (
<button
key={currency}
id={currency}
className="option"
>
{currency.symbol}
{currency.label}
</button>
);
})}
</div>
);
};
But I really need to implement this piece of code with access to it in a class component. I was searching a documentation with accessing data in a classes, but nothing.
Any ideas?
You can use #apollo/client package and we can use client.query directly in the class component
import {
ApolloClient,
gql,
InMemoryCache,
NormalizedCacheObject
} from '#apollo/client';
const client = new ApolloClient<NormalizedCacheObject> ({
cache: new InMemoryCache({}),
uri: 'https://countries.trevorblades.com'
});
import * as React from 'react';
const GET_Countries = gql`
query {
countries{
code
name
}
}
`;
class App extends React.Component {
getData = async () => {
let res = await client.query({
query: GET_Countries
})
console.log(res)
// Set the state to make changes in UI
}
componentDidMount() {
this.getData()
}
render() {
return "Test";
}
}
export default App;
I am trying to add dynamic data from one file to another and am having issues with that.
The data I am trying to load from
path: /src/components/Subnav.js
import React, { Component } from "react";
class Subnav extends Component {
static async getInitialProps(ctx) {
const res = await fetch("https://api.github.com/repos/vercel/next.js");
const json = await res.json();
return { stars: json.stargazers_count };
}
render() {
return <div>Next stars: {this.props.stars}</div>;
}
}
export default Subnav;
The code where I want the above data in
import React from "react";
import Subnav from "../src/components/Subnav";
function Page({ json }) {
return (
<subNav />
)
}
output in the browser
<div>Next stars: </div>
The expected output in the browser
<div>Next stars: 88542</div>
Issue:
As you can see above, I am just seeing the static text which is "Next stars" however, i am not seeing the data from the JSON
getInitialProps is data fetching method for page, it means you only can use it inside /pages folder. If you want fetching data for components, you can use useEffect.
import React, { useEffect, useState } from "react";
const Subnav = () => {
const [stars, setStars] = useState(null);
useEffect(() => {
const getStars = async () => {
const res = await fetch("https://api.github.com/repos/vercel/next.js");
const json = await res.json();
setStars(json.stargazers_count)
}
getStars();
}, [])
return <div>Next stars: {stars}</div>
}
export default Subnav;
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;
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