How to include variables in the React Apollo query and execute it? - reactjs

I'm using compose to perform multiple queries in a component. I want to be able to use variables for the queries.
1) How do I include the variables in the query?
2) How do I execute the query?
Following is the component:
import React from 'react'
import {
View,
} from 'react-native'
import { graphql, withApollo } from 'react-apollo'
import { compose } from "recompose";
import { GET_ITEM, GET_REVIEWS } from '../graphql/query'
const PostingDetail = props => {
const itemId = props.navigation.getParam('itemId', null)
console.log("props", props.itemQuery)
return (
<View>
</View>
)
}
export default compose(
withApollo,
graphql(GET_ITEM, {
name: 'itemQuery',
options: ({ itemId }) => ({
variables: {
id: itemId
}
})
}),
graphql(GET_REVIEWS, { name: 'reviewsQuery'}),
)(PostingDetail)
I want to be able to use itemId as the variables for the query, however, the above code displays the following error:
"message": "Variable \"$id\" of required type \"ID!\" was not
provided."

This property allows you to configure the name of the prop that gets passed down to your component. By default if the GraphQL document you pass into graphql() is a query then your prop will be named data. If you pass a mutation then your prop will be named mutate. While appropriate these default names collide when you are trying to use multiple queries or mutations with the same component. To avoid collisions you may use config.name to provide the prop from each query or mutation HOC a new name.
Example
export default compose(
graphql(gql`mutation (...) { ... }`, { name: 'createTodo' }),
graphql(gql`mutation (...) { ... }`, { name: 'updateTodo' }),
graphql(gql`mutation (...) { ... }`, { name: 'deleteTodo' }),
)(MyComponent);
function MyComponent(props) {
// Instead of the default prop name, `mutate`,
// we have three different prop names.
console.log(props.createTodo);
console.log(props.updateTodo);
console.log(props.deleteTodo);
return null;
}
And the key of the variable you want to use is not in the query statement, showing the error message.
use variables of options
export default graphql(gql`
query ($width: Int!, $height: Int!) {
...
}
`, {
options: (props) => ({
variables: {
width: props.size,
height: props.size,
},
}),
})(MyComponent);

Related

How to pass an argument to a components query (non-static)?

I am trying to create a component that I later can reuse on my website like so <TimeToRead id={someId}/>. My idea was to pass that id further down into the query.
However that does not work and ends up in: TypeError: Cannot read property 'edges' of undefined. Why is that so and what am I doing wrong here?
Is createPage(... context: id: someId) inside gatsby-node.js the only way to pass arguments? But that would only apply to templates...
How can I pass arguments to components?
import React from "react"
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome"
import { faClock } from "#fortawesome/free-solid-svg-icons"
import { graphql } from "gatsby"
const TimeToRead = ({id}) => {
console.log(id)
return (
<React.Fragment>
<FontAwesomeIcon icon={faClock} /> {timeToReadQuery.allMarkdownRemark.edges.node.timeToRead} ~ min.
</React.Fragment>
)
}
export const timeToReadQuery = graphql`
query timeToReadQuery($id: String!) {
allMarkdownRemark(
filter: { id: { eq: $id } }
) {
edges {
node {
timeToRead
}
}
}
}
`
export default TimeToRead
In Gatsby there are two types of queries. Page queries that can be defined in page components only and accept arguments passed as context in createdPage() and static queries which don't access variables and can be used in everywhere but are limited to one per file.
If you TimeToRead component file is not a page component then you have 2 options:
Use a static query - you just can't have variables in it.
Define a graphql fragment to use into parent page component.
// in child component
export const remarkTimeToReadFragment = graphql`
fragment RemarkTimeToRead on Query {
postTimeToRead: markdownRemark(id: { eq: $id }) {
timeToRead
}
}`
// in page component
export const pageQuery = graphql`
query PageQuery($id: String!) {
...RemarkTimeToRead
}
`
This particular example may produce a warning because $id param is not used directly in the page query and the linter just won't account for it being used by the fragment.

Cant Set Apollo Local State with nested values

I'm testing out Apollo Graphql with React and I'm trying to update the local state with Apollo Graphql with a nested object. I'm running into an issue. The data returns a null value and does not even return the value I set as a default. The only warning I see is Missing field __typename. I'm not sure what I'm missing or if this is not how you properly set nested values with Graphql or Apollo issue. I have a code sandbox with the example I'm trying to do https://codesandbox.io/embed/throbbing-river-xwe2y
index.js
import React from "react";
import ReactDOM from "react-dom";
import ApolloClient from "apollo-boost";
import { ApolloProvider } from "#apollo/react-hooks";
import App from "./App";
import "./styles.css";
const client = new ApolloClient({
clientState: {
defaults: {
name: {
firstName: "Michael",
lastName: "Jordan"
}
},
resolvers: {},
typeDefs: `
type Query {
name: FullName
}
type FullName {
firsName: String
lastName: String
}
`
}
});
client.writeData({
data: {
name: {
firstName: "Kobe",
lastName: "Bryant"
}
}
});
const rootElement = document.getElementById("root");
ReactDOM.render(
<ApolloProvider client={client}>
<App />
</ApolloProvider>,
rootElement
);
App.js
import React from "react";
import Name from "./Name";
import { useApolloClient } from "#apollo/react-hooks";
function App() {
const client = useApolloClient();
client.writeData({
data: {
name: {
firstName: "Lebron",
lastName: "James"
}
}
});
return (
<div>
<Name />
</div>
);
}
export default App;
Name.js
import React from "react";
import { NAME } from "./Queries";
import { useApolloClient } from "#apollo/react-hooks";
const Name = async props => {
const client = useApolloClient();
const { loading, data } = await client.query({ query: NAME });
console.log(data);
return <div>Hello {data.name.firstName}</div>;
};
export default Name;
QUERIES.js
import gql from "graphql-tag";
export const GET_NAME = gql`
{
name #client {
firstName
lastName
}
}
`;
Unfortunately, Apollo Client's documentation is not good in this manner and simply starts using __typename without properly explaining the reasoning behind it directly. I've seen other engineers struggling to understand its purpose before. As the warning is suggesting, you must pass a __typename property to objects you write directly to the cache, as Apollo Client will use this value by default in its data normalization process internally, to save/identify the data.
On all your calls to client.writeData, you should include a __typename property, like:
client.writeData({
data: {
name: {
__typename: 'FullName', // this is the name of the type this data represents, as you defined in your typeDefs
firstName: 'Lebron',
lastName: 'James',
},
},
});
Also, you can't use async/await on the render method of your component -- in the case of function components, the main body itself, as Promises are not valid React elements. So you have two options:
switch from client.query to the useQuery hook; or
since you're only requesting client-side fields, you can use the client.readQuery method which is synchronous and will return the data to you without a Promise. Note that with this method you're only able to make client-side requests, i.e if you want to request client and server fields at the same time, it won't work.

[GraphQL error]: Message: Variable "$id" of required type "MongoID!" was not provided

I have the following query:
import { gql } from 'apollo-boost';
const GetUserInfo = gql`
query getUserInfo($id: MongoID!){
userById(_id: $id){
local{
username
...
}
}
`;
export {GetUserInfo} ;
then I have bound the query to my "React.Component"
import React from "react";
...
import { GetUserInfo } from "../../queries/queries";
import { graphql } from 'react-apollo';
class Dashboard extends React.Component {
constructor(props) {
super(props);
...
Dashboard.propTypes = {};
export default graphql(GetUserInfo, {
options: (props) => {
return {
variables: {
_id: props.ID
}
}
}
})(Dashboard);
I am certain the props.ID is equivalent to a MongoDB ID. I am also certain the props from the parent component are being read.
The above code results in the following error:
[GraphQL error]: Message: Variable "$id" of required type "MongoID!" was not provided., Location: [object Object], Path: undefined
console error
Any feedback will be greatly appreciated. Thank you!
The variable used in the query is $id; the query needs to be executed with this variable passed in the options.
Currently a value for _id is passed and that's a different variable that is never used in the query.
You can have both variable be of the same name.
export default graphql(GetUserInfo, {
options: (props) => {
return {
variables: {
id: props.ID
}
}
}
})(Dashboard);

Is there a way to pass a dynamic GraphQL query to a graphql() decorated Component when using Apollo Client 2.0?

I encounter this issue sometimes when I am working with dynamic data. It's an issue with higher-order components which are mounted before the data they need is available.
I am looking to decorate a component with the graphql() HOC in Apollo Client, like this:
export default compose(
connect(),
graphql(QUERY_NAME), <-- I want QUERY_NAME to be determined at run-time
)(List)
The problem is I don't know how to get Apollo to use a query that is determined by the wrapped component at run-time.
I have a file that exports queries based on type:
import listFoo from './foo'
import listBar from './bar'
import listBaz from './baz'
export default {
foo,
bar,
baz,
}
I can access them by listQueries[type], but type is only known inside the component, and it is available as this.props.fromRouter.type.
Is there a strategy I can use to achieve:
export default compose(
connect(),
graphql(listQueries[type]),
)(List)
I think there might be a way to do it like this:
export default compose(
connect(),
graphql((props) => ({
query: listQueries[props.fromRouter.type],
})),
)(List)
Am I on the right track?
Another possible solution could be to make the Component generate its own sub-component that is wrapped with graphql() because the query would be known then.
For example:
const tableWithQuery = graphql(listQueries[props.fromRouter.type])((props) => {
return <Table list={props.data} />
})
I think I figured it out.
I have a Router Component that reads this.props.match.params to get the type of view and requested action.
With this information, I can create just one List, Create, Edit, and View Component and supply each with whatever queries are needed.
I created a function that gets all queries and mutations for a supplied type.
It was actually quite simple to just take the component such as <List /> and wrap it with graphql() and give it the correct query or mutation that was just determined.
Now, the components mount with this.props.data being populated with the correct data
I spread in all the queries and mutations just in case I need them. I suspect I will need them when I go to read this.props.data[listQueryName]. (which will grab the data in, for example, this.props.data.getAllPeople)
Here is the logic (I will include all of it, to minimize confusion of future searchers):
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { compose, graphql, withApollo } from 'react-apollo'
import listQueries from './list/queries'
import createMutations from './forms/create/mutations'
import editMutations from './forms/edit/mutations'
import viewQueries from './forms/view/queries'
import List from './list/List'
import Create from './forms/create/Create'
import Edit from './forms/edit/Edit'
import View from './forms/view/View'
// import Delete from './delete/Delete'
class Router extends Component {
constructor(props) {
super(props)
this.state = {
serverErrors: [],
}
}
getGraphQL = (type) => {
console.log('LIST QUERY', listQueries[type])
console.log('LIST QUERY NAME', listQueries[type].definitions[0].name.value)
console.log('CREATE MUTATION', createMutations[type])
console.log('CREATE MUTATION NAME', createMutations[type].definitions[0].name.value)
console.log('EDIT MUTATION', editMutations[type])
console.log('EDIT MUTATION NAME', editMutations[type].definitions[0].name.value)
console.log('VIEW QUERY', viewQueries[type])
console.log('VIEW QUERY NAME', viewQueries[type].definitions[0].name.value)
return {
listQuery: listQueries[type],
listQueryName: listQueries[type].definitions[0].name.value,
createMutation: createMutations[type],
createMutationName: createMutations[type].definitions[0].name.value,
editMutation: editMutations[type],
editMutationName: editMutations[type].definitions[0].name.value,
viewQuery: viewQueries[type],
viewQueryName: viewQueries[type].definitions[0].name.value,
}
}
renderComponentForAction = (params) => {
const { type, action } = params
const GQL = this.getGraphQL(type)
const {
listQuery, createMutation, editMutation, viewQuery,
} = GQL
// ADD QUERIES BASED ON URL
const ListWithGraphQL = graphql(listQuery)(List)
const CreateWithGraphQL = graphql(createMutation)(Create)
const EditWithGraphQL = compose(
graphql(viewQuery),
graphql(editMutation),
)(Edit)
const ViewWithGraphQL = graphql(viewQuery)(View)
if (!action) {
console.log('DEBUG: No action in URL, defaulting to ListView.')
return <ListWithGraphQL fromRouter={params} {...GQL} />
}
const componentFor = {
list: <ListWithGraphQL fromRouter={params} {...GQL} />,
create: <CreateWithGraphQL fromRouter={params} {...GQL} />,
edit: <EditWithGraphQL fromRouter={params} {...GQL} />,
view: <ViewWithGraphQL fromRouter={params} {...GQL} />,
// delete: <Delete fromRouter={params} {...GQL} />,
}
if (!componentFor[action]) {
console.log('DEBUG: No component found, defaulting to ListView.')
return <ListWithGraphQL fromRouter={params} {...GQL} />
}
return componentFor[action]
}
render() {
return this.renderComponentForAction(this.props.match.params)
}
}
Router.propTypes = {
match: PropTypes.shape({
params: PropTypes.shape({ type: PropTypes.string }),
}).isRequired,
}
export default compose(connect())(withApollo(Router))
If this code becomes useful for someone later. I recommend commenting everything out except code necessary to render the List View. Start with verifying the props are coming in to a little "hello world" view. Then, you will be done the hard part once you get correct data in there.

relay fragment spread not working

I'm in the learning process of relay and facing a very wired issue. Relay is not returning the data from network response if I use fragment spread operator (actual data is returning from graphql, confirmed from the network tab). But if I define the field requirements in the query itself, it returns data.
This is index.js of the app:
import React from 'react'
import ReactDOM from 'react-dom'
import {
graphql,
QueryRenderer
} from 'react-relay'
import environment from './relay/environment'
import AllTodo from './components/AllTodo'
const query = graphql`
query frontendQuery {
...AllTodo_todos
}
`
ReactDOM.render(
<QueryRenderer
environment={environment}
query={query}
render={({ error, props }) => {
if (error) return <div>{error}</div>
else if (props) {
console.log(props)
return <AllTodo { ...props } />
}
else return <div>loading...</div>
}}
/>,
document.getElementById('root')
)
AllTodo component:
import React, { Component } from 'react'
import { graphql, createFragmentContainer } from 'react-relay'
class AllTodo extends Component {
render() {
return (
<div>
{ this.props.todos.map(todo => {
<div>{ todo.id } { todo.description }</div>
}) }
</div>
)
}
}
export default createFragmentContainer(AllTodo, graphql`
fragment AllTodo_todos on RootQueryType {
allTodos {
id
description
complete
}
}
`);
Relay environment:
import {
Environment,
Network,
RecordSource,
Store,
} from 'relay-runtime'
import { BACKEND_URL } from '../../constants'
// a function that fetches the results of an operation (query/mutation/etc)
// and returns its results as a Promise:
function fetchQuery(
operation,
variables,
cacheConfig,
uploadables,
) {
return fetch(BACKEND_URL + '/graphql', {
method: 'POST',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify({
query: operation.text,
variables,
}),
}).then(response => {
return response.json();
});
}
// a network layer from the fetch function
const network = Network.create(fetchQuery);
// export the environment
export default new Environment({
network: network,
store: new Store(new RecordSource())
})
The graphql schema:
schema {
query: RootQueryType
mutation: RootMutationType
}
type RootMutationType {
# Create a new todo item
createTodo(description: String): Todo
# Update a todo item
updateTodo(id: String, description: String, complete: Boolean): Todo
# Delete a single todo item
deleteTodo(id: String): Todo
}
type RootQueryType {
# List of all todo items
allTodos: [Todo]
# A single todo item
todo(id: String): Todo
}
# A single todo item
type Todo {
id: String
description: String
complete: Boolean
}
This is the response I'm getting while console.log(props) on index.js:
Please help me to understand what I'm missing here. Thanks in advance.
I'm having the exact same problem. Basically, Relay doesn't know how to deal with queries spreading fragments on the root.
That said, you could try to refactor your query to
query frontendQuery {
allTodos {
...AllTodo_todos
}
}
and redefine your fragment container to
export default createFragmentContainer(AllTodo, {
todos: graphql`
fragment AllTodo_todos on Todo {
id
description
complete
}
`
});
In my case it's even a little bit more complicated because I'm using a refetch container and the only solution I've found so far is to put my field under another root field; the old and trusty viewer
EDIT: I found a way to avoid moving stuff under viewer. Basically you pass all the data from the QueryRenderer as a prop for the corresponding container. To have an idea see: https://github.com/facebook/relay/issues/1937

Resources