useQuery: data undefined - graphql

my problem is quite strange. When I call a query in graphiql it works. As soon as I do it in my React app it doesn't. Question marked in code: When I log just dataCategories I get the object. When I try to log dataCategories.allCategories then I get the error "dataCategories undefined". I absolutely have no clue what is going on here...
const NEW_TWEET = gql`
mutation createTweet($title: String!, $content: String!) {
createTweet(input: { title: $title, content: $content }) {
_id
title
content
date
}
}
`;
const TWEETS_QUERY = gql` {
allTweets {
_id
title
content
date
}
}`;
const CATEGORIES_QUERY = gql` {
allCategories {
_id
label
}
}`;
const NewTweet = () => {
let history = useHistory();
const [title, setTitle] = useState("");
const [content, setContent] = useState("");
const [createTweet] = useMutation(NEW_TWEET, {
update(
cache,
{data: {createTweet}}
) {
const {allTweets} = cache.readQuery({ query: TWEETS_QUERY });
cache.writeQuery({
query: TWEETS_QUERY,
data: { allTweets: allTweets.concat([createTweet])}
});
}
});
const { loading: loadingCategories, error: errorCategories, data: dataCategories } = useQuery(CATEGORIES_QUERY);
console.log(dataCategories));
////// When I log just dataCategories I get the object. When I try to log dataCategories.allCategories then I get the errer "dataCategories undefined".
return (
<div className="container m-t-20">
<h1 className="page-title">New Tweet</h1>
<div className="newnote-page m-t-20">
<form onSubmit={e => {
e.preventDefault();
createTweet({
variables: {
title,
content,
date: Date.now()
}
});
notify.show("Tweet was created succuessfully.", "success")
history.push('/');
}
}>
<div className="field">
<label className="label">Tweet Title</label>
<div className="control">
<input name="title" className="input" type="text" placeholder="Tweet Title" value={title} onChange={e => setTitle(e.target.value)} />
</div>
</div>
<div className="field">
<label className="label">Tweet Content</label>
<div className="control">
<textarea name="content" className="textarea" rows="10" placeholder="Tweet Content here..." value={content} onChange={e => setContent(e.target.value)}></textarea>
</div>
{/*} <Autocomplete
multiple
id="tags-outlied"
options={dataCategories.allCategories}
getOptionLabel={(option) => option.label}
filterSelectedOptions
renderInput={(params) => (
<TextField
{...params}
label="Kategorien"
placeholder="Kategorien"
/>
)}
/>*/}
<div className="field">
<div className="control">
<button className="button is-link">Submit</button>
</div>
</div>
</form>
</div>
</div>
);
}
export default NewTweet;
The schema is:
import { makeExecutableSchema } from "#graphql-tools/schema";
import { resolvers } from "./resolvers";
const typeDefs = `
type Tweet {
_id: ID!,
title: String!,
date: Date,
content: String!
}
scalar Date
type Category {
_id: ID!,
label: String!
}
type Query {
getTweet(_id: ID!) : Tweet
allTweets : [Tweet]
getCategory(Cat: ID!) : Category
allCategories : [Category]
}
input TweetInput {
title: String!,
content: String!
}
input CategoryInput {
label: String!
}
input TweetUpdateInput {
title: String,
content: String
}
type Mutation {
createTweet(input: TweetInput) : Tweet
updateTweet(_id: ID!, input: TweetUpdateInput) : Tweet
deleteTweet(_id: ID!) : Tweet
createCategory(input: CategoryInput) : Category
deleteCategory(_id: ID!) : Category
}
`;
const schema = makeExecutableSchema({
typeDefs,
resolvers
});
export default schema;
The resolvers are:
import Category from './models/category'
export const resolvers = {
Query : {
async getTweet(root, { _id }) {
return await Tweet.findById(_id);
},
async allTweets() {
return await Tweet.find();
},
async getCategory(root, {_id}) {
return await Category.findById(_id);
},
async allCategories() {
return await Category.find();
}
},
Mutation : {
async createTweet(root, { input }) {
return await Tweet.create(input);
},
async updateTweet(root, { _id, input }) {
return await Tweet.findOneAndUpdate({_id}, input, {new: true});
},
async deleteTweet(root, {_id}) {
return await Tweet.findOneAndRemove({_id});
},
async createCategory(root, { input }) {
return await Category.create(input);
},
async deleteCategory(root, {_id}) {
return await Category.findOneAndRemove({_id});
}
}
}

Assuming there is no error in the query, this seems to be quite normal. The first time React renders your component, the query will not have fired. And then it may be in flight (so loadingCategories would be true). You should never assume that the results from a query (data) will not be null/undefined. You should always guard against it and display an appropriate UI unless/until data is not null.

Related

Shopify storefront nuxt3 customer login error

Hi i am trying to implement customer login using shopify storefront with graphql mutations but unable to do so getting an error of
TypeError: Cannot read properties of undefined (reading 'request')
can someone please help me here...
Thanks for looking
here is my form template
<form #submit.prevent="login">
<input
id="email"
type="email"
name="email"
v-model.trim="user.email"
autocapitalize="off"
autocorrect="off"
placeholder="Email Address"
spellcheck="false"
required
><br>
<input
id="password"
type="password"
name="password"
v-model.trim="user.password"
autocapitalize="off"
autocomplete="off"
autocorrect="off"
placeholder="Password"
spellcheck="false"
required
><br>
<button type="submit" class="btn-account">
<span>{{ isLoading === true ? 'Working...' : 'Login' }}</span>
</button>
</form>
Here is my script
<script>
// Graphql
import { customerLoginMutation } from "#/graphql/queries"
export default {
name: "Login",
data() {
return {
errorMessage: false,
isLoading: false,
user: {
email: "",
password: ""
}
}
},
methods: {
async login() {
const user = this.user
this.isLoading = true
try {
const { customerAccessTokenCreate } = await this.$graphql.request(customerLoginMutation, {
input: user
})
const { customerAccessToken, customerUserErrors } = customerAccessTokenCreate
if (customerUserErrors.length) {
const [{ message }] = customerUserErrors
throw new Error(message)
}
await this.$store.dispatch("login", customerAccessToken)
this.$router.push("/account")
}
catch (error) {
this.isLoading = false
this.errorMessage = true
console.error(error)
}
}
}
}
</script>
Here is my customer login mutation
export const customerLoginMutation = gql
mutation ($input: CustomerAccessTokenCreateInput!) {
customerAccessTokenCreate(input: $input) {
customerAccessToken {
accessToken
expiresAt
}
customerUserErrors {
code
field
message
}
}
}
export const customerAccessTokenRenew = gql
mutation ($customerAccessToken: String!) {
customerAccessTokenRenew(customerAccessToken: $customerAccessToken) {
customerAccessToken {
accessToken
expiresAt
}
userErrors {
field
message
}
}
}
export const customerActivate = gql
mutation ($id: ID!, $input: CustomerActivateInput!) {
customerActivate(id: $id, input: $input) {
customer {
id
}
customerAccessToken {
accessToken
expiresAt
}
customerUserErrors {
code
field
message
}
}
}

I get following error: expected Name, found $ when firing a GraphQL query from React using Apollo. The query works fine in GraphiQL

Here's the GraphQL Query:
const movieByName = gql`
query SomeName($name: String!){
movieByName(name: $name){
name
genre
year
}
}`
It works fine on graphiql though and here's the Schema
type Query {
movies: [Movie],
movieByName(name: String!): Movie
}
type Movie {
name: String,
genre: String,
year: String
}
Here's the final code file:
import React, { Component } from 'react';
import {HashLink as Link} from 'react-router-hash-link'
import { graphql } from 'react-apollo';
import { gql } from "apollo-boost";
const movieByName = gql`
query SomeName($name: String!){
movieByName(name: $name){
name
genre
year
}
}`
class Header extends Component {
state = {
name: '',
genre: '',
year: ''
}
searchSubmit = (event) => {
event.preventDefault()
console.log(this.props)
}
render(){
return (
<div className="topnav">
<a className="logo" href="/">Movie Maker</a>
<div className="search-container">
<form onSubmit={this.searchSubmit}>
<Link smooth to="#form">Add Movies</Link>
<input type="text" placeholder="Search.." name="search"
onChange={(e) => this.setState({name: e.target.value})}/>
<button type="submit"><i className="fa fa-search"></i></button>
</form>
</div>
</div>
);
}}
export default graphql(movieByName)(Header)

How can I force order of fetch result processing in my React app?

I'm using React 16.13.0. I want to create a simple search component -- a single text box that as you type displays results. I have created the following component ...
export default class Search extends Component {
constructor(props) {
super(props);
this.state = {
searchTerm: "",
setSearchTerm: "",
searchResults: [],
setSearchResults: []
}
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
const query = event.target.value;
if ( ! query ) {
this.setState({ searchTerm: query, searchResults: [] } );
} else {
this.setState({ searchTerm: query, loading: true }, () => {
this.doSearch(query);
});
}
}
doSearch = ( query ) => {
console.log("dosearch:" + query);
const searchUrl = '/coops/?contains=' + encodeURIComponent(query);
fetch(searchUrl,{
method: "GET"
}).then(response => response.json())
.then(data => {
console.log("query:" + query);
console.log(data);
this.setState({
searchResults: data,
loading: false,
});
});
};
renderSearchResults = () => {
const {searchResults} = this.state;
if (searchResults && searchResults.length) {
return (
<div className="results-container">
<div>Results</div>
<ul>
{searchResults.map(item => (
<li className="result-items" key={item.id} value={item.name}>{item.name}</li>
))}
</ul>
</div>
);
}
};
render() {
return (
<div className="searchForm">
<input
type="text"
placeholder="Search"
value={this.state.searchTerm}
onChange={this.handleChange}
/>
{ this.renderSearchResults() }
</div>
);
}
The issue is that if I type too fast, the fetch requests do not necessarily complete in the order they are sent out. For exmple, if my term is "south," the fetch corresponding to having typed "sou" may complete after I've fully typed "south," causing the results to be displayed incorrectly. How do I account for this and force my results to be displayed corresponding to the inputs typed?
You need to use onKeyUp={} this means that when user finished typing their search query only there you will start making the request.
<input
type="text"
placeholder="Search"
value={this.state.searchTerm}
onKeyUp={this.handleChange}
/>

React Apollo GraphQL Mutation returns 400 (bad request)

I'm very new to React Apollo and graphQL. I'm trying to create an edit profile form that adds data to the users profile. When I click submit nothing happens and get get errors in the console log:
Ideally I would like the form to initially render with any data that the user has previously entered so that when the form is submitted and they haven't changed all the inputs, the inputs they haven't changed aren't overwritten in the mongoDB database.
All and any advise would be much appreciated! Thank You!
POST http://localhost:3000/graphql 400 (Bad Request)
[GraphQL error]: Message: Variable "$email" is not defined., Location: [object Object],[object Object], Path: undefined
[GraphQL error]: Message: Variable "$locationCity" is not defined., Location: [object Object],[object Object], Path: undefined
[GraphQL error]: Message: Variable "$locationState" is not defined., Location: [object Object],[object Object], Path: undefined
[GraphQL error]: Message: Variable "$locationCountry" is not defined., Location: [object Object],[object Object], Path: undefined
[GraphQL error]: Message: Variable "$interests" is not defined., Location: [object Object],[object Object], Path: undefined
[Network error]: ServerError: Response not successful: Received status code 400
In my schema.js I have the following:
editOtherProfileDetails(email: String!, locationCity: String!, locationState: String!, locationCountry: String!, interests: String!): User
In my resolvers.js I have the following:
editOtherProfileDetails: async (root, { email, locationCity, locationState, locationCountry, interests }, { User }) => {
const user = await User.findOneAndUpdate({ email },
{ $set: { locationCity: locationCity } },
{ $set: { locationState: locationState } },
{ $set: { locationCountry: locationCountry } },
{ $set: { interests: interests } },
{ new: true }
);
if (!user) {
throw new Error('User Not Found');
}
return user;
},
In my index.js I have:
export const EDIT_OTHER_PROFILE_DETAILS = gql`
mutation($locationCountry: String!){
editOtherProfileDetails(email: $email, locationCity: $locationCity, locationState: $locationState, locationCountry: $locationCountry, interests: $interests){
email
locationCity
locationState
locationCountry
interests
}
}
`;
In my editProfile.js I have the following:
import React, { Fragment } from 'react';
import { Mutation } from 'react-apollo';
import {GET_USER_PROFILE, EDIT_OTHER_PROFILE_DETAILS, PROFILE_PAGE } from './../../queries';
import { withRouter } from 'react-router-dom';
import toastr from 'toastr';
const initialState = {
locationCity: '',
locationState: '',
locationCountry: '',
interests: '',
error: ''
}
class EditOtherProfileMutations extends React.Component {
constructor(props) {
super(props);
this.state = {
locationCity: '',
locationState: '',
locationCountry: '',
interests: '',
error: ''
}
}
componentDidMount() {
if (this.props.profile) {
this.setState({
locationCity: this.props.profile.locationCity,
locationState: this.props.profile.locationState,
locationCountry: this.props.profile.locationCountry,
interests: this.props.profile.interests
});
}
toastr.options = {
"closeButton": false,
"debug": false,
"newestOnTop": true,
"progressBar": true,
"positionClass": "toast-bottom-right",
"preventDuplicates": false,
"onclick": null,
"showDuration": "300",
"hideDuration": "1000",
"timeOut": "5000",
"extendedTimeOut": "1000",
"showEasing": "swing",
"hideEasing": "linear",
"showMethod": "fadeIn",
"hideMethod": "fadeOut"
}
}
handleChange(event) {
const name = event.target.name;
const value = event.target.value;
this.setState({
[name]: value.charAt(0).toUpperCase() + value.substr(1).toLowerCase()
});
}
handleSubmit(event, editOtherProfileDetails) {
event.preventDefault();
editOtherProfileDetails().then(async () => {
toastr.success('We have updated your details!', 'Saved!');
}).catch(error => {
this.setState({
error: error.graphQLErrors.map(x => x.message)
})
// console.error("ERR =>", error.graphQLErrors.map(x => x.message));
});
}
render() {
const { locationCity, locationState, locationCountry, interests } = this.state
const userName = this.props.session.getCurrentUser.userName;
this.state;
return (
<Fragment>
<Mutation
mutation={EDIT_OTHER_PROFILE_DETAILS}
variables={{ email: this.props.session.getCurrentUser.email, locationCity, locationState, locationCountry, interests }}
refetchQueries={() => [
{ query: GET_USER_PROFILE },
{ query: PROFILE_PAGE, variables: { userName } }
]}>
{/* eslint-disable */}
{(editOtherProfileDetails, { data, loading, error }) => {
/* eslint-enable */
return (
<form className="form" onSubmit={event => this.handleSubmit(event, editOtherProfileDetails)}>
<div className="form_wrap">
<div className="form_row">
<div className="form_item">
<div className="form_input">
<span className="edit_profile_span">City</span>
<input type="text" name="locationCity" placeholder="City" value={locationCity} style={{ textTransform: "capitalize"}} onChange={this.handleChange.bind(this)}/>
<span className="bottom_border"></span>
</div>
</div>
</div>
<div className="form_row">
<div className="form_item">
<div className="form_input">
<span className="edit_profile_span">State</span>
<input type="text" name="locationState" placeholder="State" value={locationState} style={{ textTransform: "capitalize"}} onChange={this.handleChange.bind(this)}/>
<span className="bottom_border"></span>
</div>
</div>
</div>
<div className="form_row">
<div className="form_item">
<div className="form_input">
<span className="edit_profile_span">Country</span>
<input type="text" name="locationCountry" placeholder="Country" value={locationCountry} style={{ textTransform: "capitalize"}} onChange={this.handleChange.bind(this)}/>
<span className="bottom_border"></span>
</div>
</div>
</div>
<div className="form_row">
<div className="form_item">
<div className="form_input">
<span className="edit_profile_span">Interests</span>
<input type="text" name="interests" placeholder="Interests (e.g Sports, Wine, Outdoors ect.)" value={interests} style={{ textTransform: "capitalize"}} onChange={this.handleChange.bind(this)}/>
<span className="bottom_border"></span>
</div>
</div>
</div>
<div className="form_buttons">
<button type="submit" className="btn">
Save changes</button>
</div>
</div>
</form>
);
}}
</Mutation>
</Fragment>
)
}
}
export default withRouter(EditOtherProfileMutations);
I think the problem is that your mutation input fields are not the same as those defined in your schema.
Your schema has:
editOtherProfileDetails(
email: String!,
locationCity: String!,
locationState: String!,
locationCountry: String!,
interests: String!
): User
And you defined your mutation as follows:
mutation($locationCountry: String!){ ... }
The input params must match, so I think it would work if you define your mutation like this:
mutation NameOfYourMutation(
$email: String!,
$locationCity: String!,
$locationState: String!,
$locationCountry: String!,
$interests: String!
) { ... }
Also, as you might anticipate, this will slowly become hard to maintain.
I recommend having a look at input objects.
look
you are declaring mutation that take one argument and in resolver, it using email, locationCity, locationState, locationCountry, interests this args but not declare it will cause the problem you should be you are used the $locationCity and other but it doesn't datafiles should be take as argument
as this shape
mutation($locationCountry: String!
,$locationCity: String!,
$locationState: String!,
$locationCountry: String!,
$interests: String!){
editOtherProfileDetails(email: $email, locationCity: $locationCity, locationState: $locationState, locationCountry: $locationCountry, interests: $interests){
email
locationCity
locationState
locationCountry
interests
}
tell me in commit if it works or not
I figured out that I had made a mistake. Instead of:
const user = await User.findOneAndUpdate({ email },
{ $set: { locationCity: locationCity } },
{ $set: { locationState: locationState } },
{ $set: { locationCountry: locationCountry } },
{ $set: { interests: interests } },
{ new: true }
);
I needed to put:
const user = await User.findOneAndUpdate({ email },
{ $set: { locationCity: locationCity, locationState: locationState, locationCountry: locationCountry, interests: interests } },
{ new: true }
);

graphql - call mutation from a form

I'm new to graphql
I have a simple react app that lists books using a graphql query that queries a mongoDB database.
The schema contains a addBook Mutation that adds books to the DB.
This works using graphiql and I can add books and display them.
My problem now is I'm trying to use this mutation to add the books from a form on the react page.
I have a addBook component and listBooks component.
I get the error TypeError: this.props.addBookMutation is not a function
addBooks.js
import React, { Component } from 'react';
import { graphql } from 'react-apollo';
import { addBookMutation } from '../queries/queries';
class AddBooks extends Component {
constructor(props) {
super(props);
this.state = {
name: "",
genre: "",
author: "",
}
}
submitForm(e) {
e.preventDefault()
this.props.addBookMutation({
variables: {
name: this.state.name,
genre: this.state.genre,
author: this.state.author,
}
})
}
render() {
return (
<div className="wrapper">
<form action="" className="o-form" onSubmit={this.submitForm.bind(this)}>
<div className="o-form__element">
<label className="o-form__label" htmlFor="">Book Name</label>
<input className="o-form__input" type="text" onChange={(e) => this.setState({ name: e.target.value })} />
</div>
<div className="o-form__element">
<label className="o-form__label" htmlFor="">Description</label>
<textarea className="o-form__input" type="text" onChange={(e) => this.setState({ genre: e.target.value })}>
</textarea>
</div>
<div className="o-form__element">
<label className="o-form__label" htmlFor="">Year</label>
<input className="o-form__input" type="text" onChange={(e) => this.setState({ author: e.target.value })} />
</div>
<button className="o-form__btn">Add Book</button>
</form>
</div>
)
}
}
export default graphql(addBookMutation)(AddBooks)
queries.js
import { gql } from 'apollo-boost';
const getBookQuery = gql`
{
fonts{
name
genre
author
}
}
`
const addBookMutation = gql`
mutation($name: String!, $genre: String!, $author: String!){
addBook(
name: $name,
genre: $genre,
author: $author
)
}
`
export { getBookQuery, addBookMutation };
you can't call this.props.addBookMutation, in your case for a class component call it by this.props.mutate({}) for more info
submitForm(e) {
e.preventDefault();
this.props.mutate({
variables: {
name: this.state.name,
genre: this.state.genre,
author: this.state.author,
}
}).catch(res => {
const errors = res.graphQLErrors.map(err => err.message);
this.setState({ errors });
});
}

Resources