graphql - call mutation from a form - graphql

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 });
});
}

Related

Retriving data for input radio won't make a default selection

Goal:
Retrieve data 'smartphones', by API, and apply it as a default selection in the input radio component for the react hook form.
Problem:
The code do not work that makes smartphones as a preselection in the input radio button.
However, if I make a change by using hard coding, it works but hard coding do not solve the case.
I don't know how to solve it.
Info:
*Using React TS and React hook form.
*Newbie in react TS and hook form.
Stackblitz:
https://stackblitz.com/edit/react-ts-z9cnzl
Thank you!
import React, { useState } from 'react';
import { useForm } from 'react-hook-form';
import './style.css';
type FormValues = {
lastName: string;
favShow: string;
};
export default function App() {
const [category, setCategory] = useState('');
React.useEffect(() => {
async function FetchData() {
var data = await fetch('https://dummyjson.com/products/1').then((res) => {
return res.json();
});
console.log(data.category);
setCategory(data.category);
}
FetchData();
}, []);
const [data, setData] = useState(null);
const { register, handleSubmit } = useForm<FormValues>({
defaultValues: {
lastName: 'asaaaaaaf',
favShow: category,
//favShow: 'smartphones',
},
});
const onSubmit = (data) => {
setData(data);
console.log('asdf');
};
return (
<React.Fragment>
<form onSubmit={handleSubmit(onSubmit)} className="form-container">
<h1 className="form-heading">React Hook Form Example</h1>
<input
{...register('lastName', { required: true })}
className="form-control"
type="text"
placeholder="Last name"
maxLength={15}
name="lastName"
/>
<br />
<br />
<label htmlFor="ted-lasso">
<input
{...register('favShow', { required: true })}
type="radio"
name="favShow"
value="smartphones"
id="smartphones"
/>{' '}
smartphones
</label>
<label htmlFor="got">
<input
{...register('favShow', { required: true })}
type="radio"
name="favShow"
value="GOT"
id="got"
/>
GOT
</label>
<br />
<br />
<button className="submit-btn" type="submit">
Submit
</button>
</form>
{data && <p className="submit-result">{JSON.stringify(data)}</p>}
</React.Fragment>
);
}
I got some help and the solution is:
Stackblitz:
https://stackblitz.com/edit/react-ts-m2s6ev?file=index.tsx
import React, { useState } from 'react';
import { useForm } from 'react-hook-form';
import './style.css';
type FormValues = {
lastName: string;
favShow: string;
};
export default function App() {
const [category2, setCategory2] = useState();
const [data, setData] = useState(null);
const { register, handleSubmit, resetField } = useForm<FormValues>({
defaultValues: {
lastName: '',
favShow: '',
},
});
React.useEffect(() => {
async function FetchData() {
var data = await fetch('https://dummyjson.com/products/1').then((res) => {
return res.json();
});
setCategory2(data);
}
FetchData();
}, []);
React.useEffect(() => {
if (category2) {
const obj = JSON.parse(JSON.stringify(category2));
console.log(obj);
resetField('lastName', { defaultValue: obj.discountPercentage });
resetField('favShow', { defaultValue: obj.category });
}
}, [resetField, category2]);
const onSubmit = (data) => {
setData(data);
};
return (
<React.Fragment>
<form onSubmit={handleSubmit(onSubmit)} className="form-container">
<h1 className="form-heading">React Hook Form Example</h1>
<input
{...register('lastName', { required: true })}
className="form-control"
type="text"
placeholder="Last name"
maxLength={15}
name="lastName"
/>
<br />
<br />
<label htmlFor="ted-lasso">
<input
{...register('favShow', { required: true })}
type="radio"
name="favShow"
value="smartphones"
id="smartphones"
/>{' '}
smartphones
</label>
<label htmlFor="got">
<input
{...register('favShow', { required: true })}
type="radio"
name="favShow"
value="GOT"
id="got"
/>
GOT
</label>
<br />
<br />
<button className="submit-btn" type="submit">
Submit
</button>
</form>
{data && <p className="submit-result">{JSON.stringify(data)}</p>}
</React.Fragment>
);
}

Submission of Form Data via Nextjs and Graphql Apollo

I am trying to do a POST method using ApolloGraphql Mutation into my local postgres database. I am able to query and my api works when I am adding a new item via the Apollo Graphql Client, but am trying to figure out a way to post via a form.
import type { NextPage } from 'next'
import Head from 'next/head'
import {Card} from "../components/Card"
//import {products} from "../data/products";
import {gql, useQuery, useMutation} from "#apollo/client"
import { useState } from 'react';
const AllProductQuery = gql`
query Product_Query {
products {
title
description
}
}
`;
const AddProducts = gql`
mutation Add_Product($title: String!
$description: String!
) {
product(description: $description, title: $title) {
id
description
title
}
}
`;
const Home: NextPage = () => {
const {data, error, loading} = useQuery(AllProductQuery);
const [title, setTitle] = useState("")
const [description, setDescription] = useState("")
const [createPost] = useMutation(AddProducts, {
variables: {
title,
description
}
});
if (loading) {return <p>Loading...</p>}
if(error) {return <p>{error.message}</p>}
return (
<div >
<Head>
<title>Create Next App</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<div className='container mx-auto my-20 px-5 '>
{data.products.map((product: any) => (
<Card key={product.id} title={product.title} description={product.description} />
))}
</div>
<form className='flex flex-col p-2' onSubmit={e => {
e.preventDefault(); createPost();
}}>
<input placeholder="Title" type='text' value={title}onChange={(e) => {setTitle(e.target.value);}} required/>
<input placeholder="Description" type='text' value={description} onChange={(e) => {setDescription(e.target.value);}} required/>
<button type="submit" className='bg-blue-500 text-white rounded-lg'>Submit</button>
</form>
</div>
)
}
export default Home
I am currently creating a [createPost] with a useMutation function and putting my variables as title and description. In the form I then apply that method. Any help would be great!

useQuery: data undefined

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.

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)

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 }
);

Resources