React-Apollo-Hooks useMutation passing empty variables? - reactjs

I am trying to create an addBook mutation, but when I submit the form and call the mutation, a book is created with empty strings as the name, the genre and the authorID.
I can run the mutation in GraphiQL and it works correctly so I think that means the problem is in the component.
I'm not sure what is wrong?
Mutation:
const addBookMutation = gql`
mutation {
addBook(name: "", genre: "", authorID: "") {
id
name
}
}
`;
Component:
import React, { useState } from 'react';
import { useQuery, useMutation } from '#apollo/react-hooks';
import { getAuthorsQuery, addBookMutation } from '../queries/queries';
export default function AddBook() {
const { loading, error, data } = useQuery(getAuthorsQuery);
const [name, setName] = useState('');
const [genre, setGenre] = useState('');
const [authorID, setAuthorID] = useState('');
const [addBook, { data: bookData }] = useMutation(addBookMutation);
let authors = [];
if (error) return <p>Error :(</p>;
if (data) {
authors = data.authors;
}
return (
<div>
<form
className="bg-blue-900 p-4 flex flex-1 flex-wrap"
onSubmit={e => {
e.preventDefault();
addBook({
bookData: { name, genre, authorID },
});
}}
>
<label className="font-semibold text-gray-100">Book Name</label>
<input
type="text"
className="py-1 px-2 rounded border mx-2"
onChange={e => setName(e.target.value)}
/>
<label className="font-semibold text-gray-100">Genre</label>
<input
type="text"
className="py-1 px-2 rounded border mx-2"
onChange={e => setGenre(e.target.value)}
/>
<label className="font-semibold text-gray-100">Author</label>
<select
className="py-1 px-2 rounded border mx-2"
onChange={e => setAuthorID(e.target.value)}
>
{loading ? <option>Loading authors...</option> : ''}
<option defaultValue>Select Author</option>
{authors.map(author => (
<option key={author.id} value={author.id}>
{author.name}
</option>
))}
</select>
<button
type="submit"
className="bg-gray-200 text-gray-900 font-semibold py-1 px-2 rounded"
>
Add Book
</button>
</form>
</div>
);
}

First add the variables declaration to mutation
const addBookMutation = gql`
mutation AddBook($name: String!, $genre: String!, $authorID: String!){
addBook(name: $name, genre: $genre, authorID: $authorID) {
id
name
}
}
`;
Then pass the variables to the mutation as are declared
...
e.preventDefault();
addBook({
variables: { name, genre, authorID },
});
...

Related

onSubmit does nothing what is wrong?

I am trying to add an article using a form
It's supposed to create it on a thirdweb collection but it doesn't do anything when i'm hitting the button
My IDE ( VScode ) highlights image, Props, and FormEvent
It used to highlight File too but it doesn't do it anymore after this command npm install --save-dev #types/node
the console.log doesn't return anything when I click I don't know if the problem is e.preventDefault(); but I would like some help
My Thirdweb is well configured and should return a window with metamask to confirm the transaction (the transaction consisting of mining an nft and this is the equivalent of an insert in a database)
I thank you for your attention
import React, { FormEvent, useState } from 'react';
import { useRouter } from 'next/router';
import Header from '../components/Header';
import { useAddress, useContract } from '#thirdweb-dev/react';
import Image from 'next/image';
import Placeholder from '../images/placeholder.png';
type Props = {
name: string;
description: string;
image: File;
}
function addItem({}: Props) {
const { contract } = useContract(
process.env.NEXT_PUBLIC_COLLECTION_CONTRACT,
"nft-collection"
);
const address = useAddress();
const router = useRouter();
const [preview, setPreview] = useState<string>();
const [image, setImage] = useState<File>();
const mintNft = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
if (!contract || !address) return;
console.log(contract, address, image);
if (!image) {
alert("Please select an image");
return;
}
const target = e.target as typeof e.target & {
name: { value: string };
description: { value: string };
};
const metadata = {
name: target.name.value,
description: target.description.value,
image: image,
};
try {
const tx = await contract.mintTo(address, metadata);
const receipt = tx.receipt; // the transaction receipt
const tokenId = tx.id; // the id of the NFT minted
const nft = await tx.data(); // (optional) fetch details of minted NFT
console.log(receipt, tokenId, nft);
await router.push("/");
} catch (error) {
console.error(error);
}
};
return (
<div>
<Header />
<main className="max-w-6xl mx-auto p-10 border">
<h1 className="text-4xl font-bold">Add an Item to the Marketplace</h1>
<h2 className="text-xl font-semibold pt-5">Item Details</h2>
<p className="pb-5">
By adding an item to the marketplace, you're essentially Minting an
NFT of the item into your wallet which we can then list for sale!
</p>
<div className="flex flex-col justify-center items-center md:flex-row md:space-x-5 pt-5">
<Image
className="border h-80 w-80 object-contain" // object-contain is the same as object-fit: contain / permet de ne pas déformer l'objet
alt="Placeholder"
src={preview || Placeholder}
width={500}
height={500}
/>
<form onSubmit={mintNft} className="flex flex-col flex-1 p-2 space-y-2" >
<label className="font-light">Name of Item</label>
<input
className="formField"
placeholder="Name of item..."
type="text"
name="name"
id="name"
/>
<label className="font-light">Description</label>
<input
className="formField"
placeholder="Enter Description..."
type="text"
name="description"
id="description"
/>
<label className="font-light">Image of the Item</label>
<input
type="file"
onChange={(e) => {
if (e.target.files?.[0]) {
setPreview(URL.createObjectURL(e.target.files[0]));
setImage(e.target.files[0]);
}
}}
/>
<button
type="submit"
className="bg-blue-600 font-fold text-white rounded-full py-4 px-10 w-56 mx-auto md:mt-auto md:ml-auto"
>
Add/Mint Item
</button>
</form>
</div>
</main>
</div>
)
}
export default addItem;

How to pass key:value onChange from select-option in reactjs?

I follow this tutorial and try to modify it a little.
I have this code addRequestForm.js
import { BiPlus } from 'react-icons/bi'
import { useMutation, useQueryClient } from "react-query"
import { addRequest, getRequests } from "../../lib/helper"
import Bug from "./bugRequest"
import Success from "./successRequest"
export default function AddRequestForm({ formData, setFormData }) {
const queryClient = useQueryClient()
const addMutation = useMutation(addRequest, {
onSuccess: () => {
queryClient.prefetchQuery('requests', getRequests)
}
})
const handleSubmit = (e) => {
e.preventDefault();
if (Object.keys(formData).length == 0) return console.log("Don't have Form Data");
let { sector, contact_name } = formData;
const model = {
sector, contact_name
}
addMutation.mutate(model)
}
if (addMutation.isLoading) return <div>Loading!</div>
if (addMutation.isError) return <Bug message={addMutation.error.message}></Bug>
if (addMutation.isSuccess) return <Success message={"Added Successfully"}></Success>
return (
<form className="grid lg:grid-cols-2 w-4/6 gap-4 md:px-4 md:mx-auto" onSubmit={handleSubmit}>
<div className="input-type">
<label htmlFor="sector" className="block text-sm font-medium text-gray-700">
Sector
</label>
<select
id="sector"
name="sector"
autoComplete="sector-name"
className="border w-full px-5 py-3 focus:outline-none rounded-md"
onChange={(e) => setFormData({ [e.target.name]: e.target.value })}
>
<option value="North">North</option>
<option value="South">South</option>
<option value="West">West</option>
<option value="East">East</option>
{/* */}
</select>
</div>
<div className="form-control input-type">
<label className="input-group">
<span>Contact Name</span>
<input type="text" onChange={setFormData} name="contact_name" placeholder="Contact Name Here..“ className="w-full input input-bordered" />
</label>
</div>
<button type="submit" className="flex justify-center text-md w-2/6 bg-green-500 text-white px-4 py-2 border rounded-md hover:bg-gray-50 hover:border-green-500 hover:text-green-500">
Add <span className="px-1"><BiPlus size={24}></BiPlus></span>
</button>
</form >
)
}
For name=“contact_name” already succeed insert into database (mongodb + mongoose). But, I’m still confuse, how to do the same to select-option?
Already tried these:
onChange={(e) => setFormData({ e.target.name: e.target.value })}
onChange={(e) => setFormData({ sector.name: e.target.value })}
onChange={(e) => setFormData({ [e.target.name][0]: e.target.value })}
Got Syntax error: Unexpected token, expected ",". While I thought this is the right value when I’m consoling these ( result: “sector:myname”).
onChange={(e) => setFormData({ sector: e.target.value })}
onChange={(e) => setFormData({ [e.target.name]: e.target.value })}
No insert into database.
How to write it correctly? I’m using nextjs (i thought the same with reactjs, right?) + #reduxjs/toolkit 1.9.1.
In case needed, here is reducer.js:
import UpdateRequestForm from "./updateRequestForm";
import AddRequestForm from "./addRequestForm";
import { useSelector } from "react-redux";
import { useReducer } from "react";
const formReducer = (state, event) => {
return {
...state,
[event.target?.name]: event.target?.value
}
}
export default function FormRequest() {
const [formData, setFormData] = useReducer(formReducer, {})
const formId = useSelector((state) => state.app.client.formId)
return (
<div className="container mx-auto py-5">
{formId ? UpdateRequestForm({ formId, formData, setFormData }) : AddRequestForm({ formData, setFormData })}
</div>
)
}
try this
onChange = {(e) => {
let obj = {} ;
obj[e.target.name] = e.target.value;
setFormData(obj);
}
}
I'm not entirely following your use case for the reducer but you could replace it with a simple handleChange function as follows. Just a side note, calling setFormData(//newData) will complete erase what is in there so we first spread the original state and change the value based on the key.
State and handleChange:
const [formData, setFormData] = React.useState({
sector: "North",
contact_name: ""
});
const handleChange = ({ target }) => {
// Deconstruct the target
// Deconstruct the name and value from the target
const { name, value } = target;
// Set the state
setFormData((previousState) => ({
...previousState,
[name]: value,
}));
};
Usage:
<select
placeholder="Select a sector"
name="sector"
value={formData.sector}
onChange={handleChange}
>
<option value="North">North</option>
<option value="South">South</option>
<option value="West">West</option>
<option value="East">East</option>
</select>
<input
value={formData.contact_name}
placeholder="Contact Name"
name="contact_name"
onChange={handleChange}
/>
A codesandbox : https://codesandbox.io/s/interesting-kilby-emcyl4?file=/src/App.js:461-615
Also note that you will need to assign a name to the input element. In the example, name is set to sector. As the name value will be used to indicate which key in the state object must be updated.
It's good practice to have controlled input fields. So setting the value of the input to your state will accomplish this. Only thing to remember is you need a default state. I have added it all to the codesandbox if you would like to take a look.
In my case, just use this syntax:
…
value={formData.sector}
onChange={setFormData}
…
Case closed.

Download URL from storage not transferring to firestore - React [duplicate]

This question already has answers here:
The useState set method is not reflecting a change immediately
(15 answers)
Why does calling react setState method not mutate the state immediately?
(9 answers)
Closed 7 months ago.
I am trying to add the imgUrl uploaded to firestore but it's not working. The url is generated and the image is stored in firebase storage but I can't seem to pass that URL as a field into Firestore
This is my code
import React, {useEffect, useState} from "react";
import ReactTagInput from "#pathofdev/react-tag-input";
import "#pathofdev/react-tag-input/build/index.css";
import {db, storage} from "../firebase";
import { getDownloadURL, ref, uploadBytes, listAll} from "firebase/storage";
import {
addDoc,
collection,
getDoc,
serverTimestamp,
doc,
updateDoc,
} from "firebase/firestore";
import { toast } from "react-toastify";
import { v4 } from 'uuid';
import {useNavigate} from "react-router-dom";
const initialState = { // initial state for our form
title: "",
tags: [],
trending: "no",
category: "",
description: "",
imgUrl: ""
}
const categoryOption = [
"Fashion",
"Technology",
"Food",
"Politics",
"Sports",
"Business"
]
export default function AddEditBlog({ user }) {
let urlDownload
const [form, setForm] = useState(initialState)
const [imageUpload, setImageUpload] = useState()
const [imageList, setImageList] = useState([])
const navigate = useNavigate()
const imageListRef = ref(storage, "images/") // to use in useEffect (listAll), because we
// wanna access all the files inside the images folder
const { title, tags, category, trending, description, imgUrl} = form;
const uploadImage = () => {
if(imageUpload == null) return; // if there's no image, return and get out of this function
uploadBytes(imageRef, imageUpload).then((snapshot)=> {
getDownloadURL(snapshot.ref).then((url) => {
// setImageList((prev) => [...prev, url])
console.log("url", url)
toast.info("Image upload to firebase successfully");
setForm((prev) => ({ ...prev, imgUrl: url }))
console.log("form", form)
})
})
}
const handleChange = (event) => {
setForm({...form, [event.target.name]: event.target.value})
}
const handleTags = (tags) => {
setForm({ ...form, tags})
}
const handleTrending = (event) => {
setForm({ ...form, trending: event.target.value })
}
const onCategoryChange = (event) => {
setForm({ ...form, category: event.target.value })
}
const handleSubmit = async (event) => {
event.preventDefault()
if (category && tags && title && description && trending && imgUrl) {
try {
console.log("url", urlDownload)
await addDoc(collection(db, "blogs"), {
...form,
timestamp: serverTimestamp(),
author: user.displayName,
userId: user.uid
})
toast.success("Blog created successfully");
} catch(error) {
console.log(error)
}
} else {
console.log("complete form")
}
navigate("/")
}
return(
<div className="container-fluid mb-4">
<div className="container">
<div className="col-12">
<div className="text-center heading py-2">
Create Blog
</div>
</div>
<div className="row h-100 justify-content-center align-items-center">
<div className="col-10 col-md-8 col-lg-6">
<form className="row blog-form" onSubmit={handleSubmit}>
<div className="col-12 py-3">
<input
type="text"
className="form-control input-text-box"
placeholder="Title"
name="title"
value={title}
onChange={handleChange}
/>
</div>
<div className="col-12 py-3">
<ReactTagInput
tags={tags}
placeholder="Tags"
onChange={handleTags}
/>
</div>
<div className="col-12 py-3">
<p className="trending">Is it trending blog ?</p>
<div className="form-check-inline mx-2">
<input
type="radio"
className="form-check-input"
value="yes"
name="radioOption"
checked={trending === "yes"} //if trending is yes select checked button
onChange={handleTrending}
/>
<label htmlFor="radioOption" className="form-check-label">
Yes
</label>
<input
type="radio"
className="form-check-input"
value="no"
name="radioOption"
checked={trending === "no"} //if trending is not select checked button
onChange={handleTrending}
/>
<label htmlFor="radioOption" className="form-check-label">
No
</label>
</div>
</div>
<div className="col-12 py-3">
<select
value={category}
onChange={onCategoryChange}
className="catg-dropdown"
>
<option>Please select category</option>
{categoryOption.map((option, index) => (
<option value={option || ""} key={index}>
{option}
</option>
))}
</select>
</div>
<div className="col-12 py-3">
<textarea
className="form-control description-box"
placeholder="Description"
value={description}
name="description"
onChange={handleChange}
/>
</div>
<div className="mb-3">
<input
type="file"
className="form-control"
onChange={(event)=> {
setImageUpload(event.target.files[0])
}}
/>
</div>
<div className="col-12 py-3 text-center">
<button
className="btn btn-add"
type="submit"
onClick={uploadImage}
>
Submit
</button>
</div>
</form>
</div>
</div>
</div>
</div>
)
}
Everything else loads successfully. The console log I do of the url returns the correct URL so I don't know why I can't pass it into firestore

TypeError: Cannot read properties of undefined (reading 'value') for a input within a form

I am trying to do a mutation to add a new item into my Postgres database via Apollo GraphQL. I am following the documentation for a form/component that submits a new product into a query.
I am receiving an error on the input.value with it being a property that cannot be read. This is within the form and is within the bold bracket. Any help on how to fix this would be great!
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
}
}
`;
function AddProductForm():JSX.Element {
let input:any;
const [addProduct, {data, loading, error}] = useMutation(AddProducts)
return (
<form className='flex flex-col p-2' onSubmit={(e) => {
e.preventDefault();
addProduct({variables: {title: input.value}})
input.value=''
}}>
<input placeholder="Title" type='text' ref={(node) => (node = input)} required/>
<input placeholder="Description" type='text' ref={(node) => (node = input)} required/>
<button type="submit" className='bg-blue-500 text-white rounded-lg'>Submit</button>
</form>
)
}
const Home: NextPage = () => {
const {data, error, loading} = useQuery(AllProductQuery);
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>
<AddProductForm />
</div>
)
}
export default Home
You're using the ref attribute wrong, you have to use useRef hook like below, in order to access dom elements (here input tag).
here is what you need:
//...
import { useState, useRef } from "react";
//...
function AddProductForm(): JSX.Element {
const titleRef = useRef<HTMLInputElement>(null);
const descriptionRef = useRef<HTMLInputElement>(null);
const [addProduct, { data, loading, error }] = useMutation(AddProducts);
return (
<form
className="flex flex-col p-2"
onSubmit={(e) => {
e.preventDefault();
addProduct({ variables: { title: titleRef.current.value } });
titleRef.current.value = "";
//you can also do something with the descriptionRef
}}
>
<input
placeholder="Title"
type="text"
ref={titleRef}
required
/>
<input
placeholder="Description"
type="text"
ref={descriptionRef}
required
/>
<button type="submit" className="bg-blue-500 text-white rounded-lg">
Submit
</button>
</form>
);
}
//...

React Apollo: How to access element Trix-Editor within a Mutation component?

Please, I need help accessing a <trix-editor> element in my Mutation component. I'm using React hook useRef() to access this, but getting null. From Chrome Dev Tools (image below) I can see the element has been referred. I have tried some of the solutions given here & here but with no luck. Problem seems to be in the rendering of the Mutation component. I'm fairly new to React, so not sure if I'm getting this right. What am I doing wrong? Thank you in advance for your help.
My code:
const EditProfile = () => {
const trixInput = useRef(null);
const [state, setState] = useState({
firstName: "",
lastName: "",
bio: "",
avatar: "",
gender: "",
country: ""
});
let userID
let { username } = useParams();
let userFromStore = JSON.parse(sessionStorage.getItem('_ev')));
let history = useHistory();
useEffect(() => {
// trixInput.current.addEventListener("trix-change", event => {
console.log(trixInput.current); // <<<< this prints null to the screen
// })
},[]);
if (userFromStore !== username) {
return (
<div className="alert">
<span className="closebtn" onClick={() => history.push("/console")}>×</span>
<strong>Wanning!</strong> Not authorized to access this page.
</div>
);
}
return (
<Query query={GetAuthUser}>
{({ loading, error, data }) => {
if (loading) return "loading...";
if (error) return `Error: ${error}`;
if (data) {
let {userId, firstName, lastName, avatar, bio, gender, country} = data.authUser.profile;
userID = parseInt(userId);
return (
<Mutation mutation={UpdateUserProfile}>
{(editProfile, { loading }) => (
<div className="card bg-light col-md-8 offset-md-2 shadow p-3 mb-5 rounded">
<article className="card-body">
<form onSubmit={ e => {
e.preventDefault();
editProfile({
variables: {
userId: userID,
firstName: state.firstName,
lastName: state.lastName,
bio: state.bio,
avatar: state.avatar,
gender: state.gender,
country: state.country
}
}).then(({ data: { editProfile: { success, errors } } }) => {
success ? alert(success) : alert(errors[0]["message"]);
});
}}
>
<div className="form-row mb-3">
<div className="col">
<input
type="text"
name="firstName"
placeholder="First name"
className="form-control"
defaultValue={firstName}
onChange={e => setState({firstName: e.currentTarget.value})}
/>
</div>
<div className="col">
<input
type="text"
name="lastName"
placeholder="Last name"
className="form-control"
defaultValue={lastName}
onChange={e => setState({lastName: e.currentTarget.value})}
/>
</div>
</div>
<div className="form-group">
<label className="">Bio</label>
<input
type="hidden"
defaultValue={bio}
name="bio"
id="bio-body"
/>
// <<< having trouble accessing this ref element
<trix-editor input="bio-body" ref={trixInput}/>
</div>
<input type="submit" className="btn btn-primary" value="Update Profile" disabled={loading}/>
</form>
</article>
</div>
)}
</Mutation>
);
}
}}
</Query>
)
}
export default EditProfile;
UPDATE:
For anyone interested, problem was solved by extracting Mutation component to a different file. Reason been Mutation component wasn't mounting on render, only the Query component was mounting. First iteration of the solution is shown as an answer.
EditProfile component
import React, { useState } from 'react';
import { Query } from 'react-apollo';
import { useHistory, useParams } from "react-router-dom";
import { GetAuthUser } from './operations.graphql';
import ProfileMutation from './ProfileMutation'
const EditProfile = (props) => {
const [state, setState] = useState({
firstName: "",
lastName: "",
bio: "",
avatar: "",
gender: "",
country: ""
});
let { username } = useParams();
let userFromStore = JSON.parse(sessionStorage.getItem('_ev'));
let history = useHistory();
if (userFromStore !== username) {
return (
<div className="alert">
<span className="closebtn" onClick={() => history.push("/console")}>×</span>
<strong>Wanning!</strong> Not authorized to access this page.
</div>
);
}
return (
<Query query={GetAuthUser}>
{({ loading, error, data }) => {
if (loading) return "loading...";
if (error) return `Error: ${error}`;
return <ProfileMutation state={state} profile={data.authUser.profile} setState={setState}/>
}}
</Query>
)
}
export default EditProfile;
ProfileMutation component
import React, { useRef, useEffect } from 'react';
import { Mutation } from 'react-apollo';
import { UpdateUserProfile } from './operations.graphql';
import "trix/dist/trix.css";
import "./styles.css"
const ProfileMutation = ({ state, profile, setState }) => {
const trixInput = useRef('');
let { userId, firstName, lastName, avatar, bio, gender, country } = profile;
let userID = parseInt(userId);
// console.log(firstName)
useEffect(() => {
trixInput.current.addEventListener('trix-change', (e) => {
setState({bio: trixInput.current.value})
console.log(trixInput.current.value)
})
},[trixInput])
return (
<Mutation mutation={UpdateUserProfile}>
{(editProfile, { loading }) => (
<div className="card bg-light col-md-8 offset-md-2 shadow p-3 mb-5 rounded">
<article className="card-body">
<form onSubmit={ e => {
e.preventDefault();
editProfile({
variables: {
userId: userID,
firstName: state.firstName,
lastName: state.lastName,
bio: state.bio,
avatar: state.avatar,
gender: state.gender,
country: state.country
}
}).then(({ data: { editProfile: { success, errors } } }) => {
success ? alert(success) : alert(errors[0]["message"]);
//TODO: replace alerts with custom message box
});
}}
>
<div className="form-row mb-3">
<div className="col">
<input
type="text"
name="firstName"
placeholder="First name"
className="form-control"
defaultValue={firstName}
onChange={e => setState({firstName: e.currentTarget.value})}
/>
</div>
<div className="col">
<input
type="text"
name="lastName"
placeholder="Last name"
className="form-control"
defaultValue={lastName}
onChange={e => setState({lastName: e.currentTarget.value})}
/>
</div>
</div>
<div className="form-group">
<label className="">Bio</label>
<input
type="hidden"
defaultValue={bio}
name="bio"
id="bio"
/>
<trix-editor input="bio" ref={trixInput} />
</div>
<input type="submit" className="btn btn-primary" value="Update Profile" disabled={loading}/>
</form>
</article>
</div>
)}
</Mutation>
);
}
export default ProfileMutation;
Hope this helps someone! If anyone has a better solution please post it here. Thanks!

Resources