Calling a graphQL query from within react client - reactjs

I'm trying to return a list of all users transactions, but I can't seem to call the graphQL query correctly. I've defined the query at the bottom and I'm trying to call it in refreshData(). What am I missing?
async refreshData() {
const allTransactions = await graphql(getTransactionsQuery);
console.log(allTransactions);
this.setState({
transactions: allTransactions
});
}
render() {
const transactionsFromState = this.state.transactions;
const transactions = transactionsFromState.map((transaction) =>
<li key={transaction.id}>{transaction}</li>
);
return (
<div className="App">
<button onClick={() => this.refreshData()}> Refresh Data </button>
<ul>{ transactions }</ul>
</div>
);
}
}
const getTransactionsQuery = gql`
query getTransactions($id: ID!) {
transactions(userId: $id){Transaction}
}
`;
//change User ID use context.user.id
export default graphql(getTransactionsQuery)(App);

Using the graphql Higher Order Component
The graphql function you're calling returns a higher order component that can be then used to wrap another React component. It does not return a promise that can be awaited like you're trying to do.
You are already utilizing it (more or less) correctly here:
export default graphql(getTransactionsQuery)(App)
When you wrap your component with the graphql HOC like this, your component receives a data prop that can then be used inside your component's render method. This negates the need to utilize component state to persist your query results. Your render method should look something like this:
render() {
const { transactions } = this.props.data
const transactionItems = transactions
? transactions.map(t => <li key={t.id}>{t}</li>)
: null
return (
<div className="App">
<button onClick={() => this.refreshData()}> Refresh Data </button>
<ul>{ transactionItems }</ul>
</div>
);
}
data.transactions will be initially undefined before the query is fetched, so it's important to be mindful of that inside your render function. You can also check data.loading to determine if the query is still in flight. Please check the Apollo docs (here) for more examples.
Fetching a query with variables
Your query utilizes variables, so those variables need to be sent along with it. You'll need to pass those into the graphql function as options like this:
export default graphql(getTransactionsQuery, { options: { variables: { id: 'youridhere' } } })(App)
Options is normally passed an object, but it can also be passed a function. This function takes props as an argument, so that you can derive your variables from props if needed. For example:
const options = props => ({ variables: { id: props.id } })
export default graphql(getTransactionsQuery, { options })(App)
Check here for more information.
Refetching data
Since Apollo does the heavy lifting for you, there's no need to have a button to fetch the data from your server. But should you need to refetch your data, you can do so through the data prop passed down to your component by calling props.data.refetch(). You can read more about refetch here.

Related

React apollo client api calling two time

This is my React code of ApolloClient
function EmpTable() {
const GET_EMPLOYEE = gql`
query refetch($id: String) {
employeeById(id: $id) {
id
name
role
}
}
`;
const {refetch} = useQuery(GET_EMPLOYEE)
const getEmpByID = (id) => {
refetch({
id: id
}).then((response) => {
// do something
})
}
return (
<div className="row">
{/* { I am rending list of employee with map and passing id this way
<a onClick={() => getEmpByID(id)}>get employ info</a>
} */}
</div>
);
}
export default EmpTable;
Everything is working very well in this code, the only problem is the API being called two times, first time it returns no data, and 2nd time, it returns the expected data.
How can I prevent calling this twice?
I guess this is executing first time: const {refetch} = useQuery(GET_EMPLOYEE) and making the first request without data, because, no variable is passed there. I know I can can pass a variable in useQuery first time but the problem is that I can’t pass this from there, because the query params are not in my state or props.
Can anyone tell me what is the possible solution for this?
Due to documentantion:
When React mounts and renders a component that calls the useQuery
hook, Apollo Client automatically executes the specified query. But
what if you want to execute a query in response to a different event,
such as a user clicking a button? The useLazyQuery hook is perfect for
executing queries in response to events other than component
rendering. This hook acts just like useQuery, with one key exception:
when useLazyQuery is called, it does not immediately execute its
associated query. Instead, it returns a function in its result tuple
that you can call whenever you're ready to execute the query:
Example :
const GET_COUNTRIES = gql`
{
countries {
code
name
}
}
`;
export function DelayedCountries() {
const [getCountries, { loading, data }] = useLazyQuery(GET_COUNTRIES);
if (loading) return <p>Loading ...</p>;
if (data && data.countries) {
console.log(data.countries);
}
return (
<div>
<button onClick={() => getCountries()}>
Click me to print all countries!
</button>
{data &&
data.countries &&
data.countries.map((c, i) => <div key={i}>{c.name}</div>)}
</div>
);
}
useLazyQuery will be executed at the moment, when getCountries is called.
https://codesandbox.io/s/apollo-client-uselazyquery-example-6ui35?file=/src/Countries.js
This is a normal behavior in React 18, all events are called twice, this is new in React 18, and, only happens in development.
Now, why are they doing this? because it helps to identify bugged effects and hooks, and it works.
If you wanna disable this you can do it by disabling strict mode (https://reactjs.org/docs/strict-mode.html).
So, if the component is working, you have no warning about data leaking stay calm and continue. Another option to try is compile your application and run it in production mode, if the hook it's called twice then yeah, by all means, validate your component code (and it's parent components)

setInterval with updated data in React+Redux

I have setInterval setup to be working properly inside componentDidMount but the parameters are not updated. For example, the text parameter is the same value as when the component initially mounted, despite being changed in the UI. I've confirmed text's value is correctly updated in Redux store but not being passed to this.retrieveData(text). I suspect the const { text } = this.props set the value in componentDidMount, which forbids it from updating despite it being different. How would I go about this issue?
Code below is an example, but my real use-case is retrieving data based on search criteria. Once the user changes those criteria, it will update with the new result. However, I'm unable to pass those new criteria into componentDidMount so the page would refresh automatically every few seconds.
class App extends React.Component {
componentDidMount() {
const { text } = this.props //Redux store prop
setInterval(() => this.retrieveData(text), 3000)
}
retrieveData = (text) => {
let res = axios.post('/search', { text })
updateResults(res.data) //Redux action
}
render() {
const { text, results } = this.props
return (
<input text onChange={(e) => updateText(e.target.value)} />
<div>
{results.map((item) => <p>{item}</p>}
</div>
)
}
}
Because you are using componentDidMount and setTimeout methods your retrieveData is called only once with initial value of the text. If you would like to do it in your current way please use componentDidUpdate method which will be called each time the props or state has changed. You can find more information about lifecycle here https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/.
If you would like to use setInterval just like in the question, you just need to access props inside of retrieveData method instead of using an argument.
retrieveData = () => {
let res = post("/search", { text: this.props.text });
updateResults(res); //Redux action
};
You can find working example for both cases here https://codesandbox.io/s/charming-blackburn-khiim?file=/src/index.js
The best solution for async calls would be to use some kind of middleware like https://github.com/reduxjs/redux-thunk or https://redux-saga.js.org/.
You have also small issue with input, it should be:
<input type="text" value={text} onChange={(e) => updateText(e.target.value)} />

Apollo query fails with an error - TypeError: this.props.getPostInfo is not a function

I am trying to use Apollo with React to connect to my graphql api endpoint. In order to do this , I have a component as follows.
import React, {Component} from 'react'
import {GetPostInfoQuery} from "./../../../queries/posts"
import { compose, graphql, Query } from "react-apollo";
class PostItem extends Component {
getCurrentPostInfo =() => {
this.props.getPostInfo({
variables: {
postID: "UG9zdDoy"
}
})
.then((loading, data, error) => {
console.log(data)
})
.catch(error => {
alert(JSON.stringify(error) );
});
}
render() {
return (
<div>
<button onClick={this.getCurrentPostInfo}> Load Info </button>
</div>
)
}
}
export default compose(
graphql(GetPostInfoQuery, {
name: "getPostInfo"
})
)(PostItem);
My GetPostInfoQuery looks like as follows
export const GetPostInfoQuery = gql `
query postInfo($postID: ID!){
post(id:$postID) {
id
title
content
headerImage
createdAt
user {
id
firstname
lastname
locale {
id
name
country
}
}
tagList {
edges{
node {
id
name
}
}
}
}
}
`
I am passing a variable - postID whose value is UG9zdDoy to my query so that it can get executed and return me the data back. When I run the exact query using the graphiql interface it returns me data without any issue .
When I run the above code, I am getting the following error
and here is the same query running in graphiql
Can you please let me know what I am doing wrong here?
Thanks
The graphql HOC behaves differently depending on whether it receives a query or mutation. From the docs:
[For queries,] the higher-order component created when using graphql() will feed a data prop into your component... The data prop contains the data fetched from your query in addition to some other useful information and functions to control the lifecycle of your GraphQL-connected component.
The higher order component created when you pass a mutation to graphql() will provide your component with a single prop named mutate. Unlike the data prop which you get when you pass a query to graphql(), mutate is a function.
In other words, for queries you get a data prop that's an object and for mutations you get mutate prop that's a function. You're using a query, so you can't call the prop you receive like a function because it's not a function, like the error indicates.
If you want to trigger a query in response to some user action, you can call the query method on the client directly. See withApollo for more details.

How to trigger GraphQL Query from another React / ReactNative component

I have some problem.
I have 2 Component in React Native.
1. (Component A) for showing the data , this component use query to graphql
2. (Component B) is button to trigger query in component A with some filtering variable.
When I click the button component ,
it mutate
save filter into graphql link state
trigger the component A to rerender new data.
Problem is , component A not rerender..
Here is code that triggered when I click the button
this.props.mutate({
variables: {
Category
},
refetchQueries: [{
query: FETCH_SEARCH,
variables: {
productcategory: Category,
search: '',
},
}]
});
How do I achieve rerendering component A ?
Thanks
Hey your question is basically "what is wrong with my code?" without providing much code. I will try to guess what is wrong here:
Your query component is still connected to the old variables, refetching it with new variables will not update the query component. Only when you refetch with the same variables it will update.
What to do instead:
React is not designed to have a messaging system from one component to another. Instead lift the variable state into a parent component or a data store so that the query component takes the variables from props (if the props have the same name as the expected variable names you don't even have to explicitly pass them in the graphql higher order component function options. Now the query will be automatically refetched when the props - and therefore the variables - change.
<QueryComponent
search={this.state.search}
productcategory={this.state.productcategory}
/>
<SelectorComponent
onSearchChange={search => this.setState({ search })}
onCategoryChange={productcategory => this.setState({ productcategory })}
/>
I faced the same problem, after doing a lot of search, none of answers suited me. I didn't want to lift up the state, I wanted to maintain the query logic inside the same component, also re-fetching from another component was a bad idea, since the variables passed to the query are not the same and Apollo interprets it as another query and the first component don't change.
So my solution is to save the "filtering variable" in the local state with Apollo. This code is an example just for this answer, I didn't test it, just copied from my project and keep out only the necessary for the explanation.
In some place you need to initialise your apollo cache
const data = {
keyword: "",
};
cache.writeData({ data });
return ApolloClient({
cache,
});
In the first component, you will query this "keyword" variable from the cache and pass it to the actual query you want to re-fetch every time
const GET_KEYWORD = gql`
{
keyword #client
}
`;
const FirstComponent = () => {
const {
data: { keyword },
} = useQuery(GET_KEYWORD);
const {
data: importantData
} = useQuery(SOME_OTHER_QUERY_YOU_WANT_TO_REFETCH, {variables: { keyword }});
return <div>{importantData}</div>
}
In the second component you just need to update this "keyword"
const SecondComponent = () => {
const client = useApolloClient();
const handleChange = (keyword: string) => {
client.writeData({ data: { keyword: keyword } });
};
return (
<div>
<input onChange={handleChange} />
</div>
);
};

How to setState() in React/Apollo with graphQL

I am trying to setState() to a query result I have from graphQL, but I am having difficulty finding out how to do this because it will always be loading, or it's only used from props.
I first set the state
constructor (props) {
super(props);
this.state = { data: [] };
Then I have this query
const AllParams = gql`
query AllParamsQuery {
params {
id,
param,
input
}
}`
And when it comes back I can access it with this.props.AllParamsQuery.params
How and when should I this.setState({ data: this.props.AllParamsQuery.params }) without it returning {data: undefined}?
I haven't found a way to make it wait while it's undefined AKA loading: true then setState. I've tried componentDidMount() and componentWillReceiveProps() including a async function(){...await...} but was unsuccessful, I am likely doing it wrong. Any one know how to do this correctly or have an example?
EDIT + Answer: you should not setstate and just leave it in props. Check out this link: "Why setting props as state in react.js is blasphemy" http://johnnyji.me/react/2015/06/26/why-setting-props-as-state-in-react-is-blasphemy.html
There is more to the problem to update props, but some great examples can be found at this app creation tutorial: https://www.howtographql.com/react-apollo/8-subscriptions/
A simple solution is to separate your Apollo query components and React stateful components. Coming from Redux, it's not unusual to transform incoming props for local component state using mapStateToProps and componentWillReceiveProps.
However, this pattern gets messy with Apollo's <Query />.
So simply create a separate component which fetches data:
...
export class WidgetsContainer extends Component {
render (
<Query query={GET_WIDGETS}>
{({ loading, error, data }) => {
if (loading) return <Loader active inline="centered" />;
const { widgets } = data;
return (
<Widgets widgets={widgets} />
)
}}
</Query>
)
}
And now the Widgets components can now use setState as normal:
...
export class Widgets extends Component {
...
constructor(props) {
super()
const { widgets } = props;
this.state = {
filteredWidgets: widgets
};
}
filterWidget = e => {
// some filtering logic
this.setState({ filteredWidgets });
}
render() {
const { filteredWidgets } = this.state;
return (
<div>
<input type="text" onChange={this.filterWidgets} />
{filteredWidgets.count}
</div>
)
}
}
What is the reason behind setting it to state? Keep in mind, Apollo Client uses an internal redux store to manage queries. If you're trying to trigger a re render based on when something changes in the query, you should be using refetchQueries(). If you absolutely need to store it in local state, I would assume you could probably compare nextProps in componentWillReceiveProps to detect when loading (the value that comes back when you execute a query from apollo client) has changed, then update your state.
I had a similar issue (although it was happening for a totally different reason). My state kept getting set to undefined. I was able to solve it with a React middleware. It made it easy to avoid this issue. I ended up using superagent.
http://www.sohamkamani.com/blog/2016/06/05/redux-apis/

Resources