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;
Related
I got a query using RTK, and a component where I attach the file and submit - after I attach the file and submit as shown in the image below I get the file in a weird format such as: file: "[object FileList]" after console log it.
How was wondering how to post a csv file in the correct format, such as file.csv. My component looks like as follow:
import { FC, FormEventHandler, RefObject, useState } from 'react';
import { useForm } from 'react-hook-form';
import { yupResolver } from '#hookform/resolvers/yup';
import * as yup from 'yup';
import {
Dialog,
DialogPortal,
DialogOverlay,
DialogContent,
DialogTitle,
DialogClose
} from '#radix-ui/react-dialog';
import { DownloadIcon, XIcon } from '#heroicons/react/outline';
import { setFormErrors } from 'utils/formHelpers';
import InputField from 'components/ui/atoms/InputField';
import { useUploadBatchMutation } from 'redux/api/uploadBatchApi';
const schema = yup
.object({
description: yup.string().optional(),
file: yup.string().label('file').required()
})
.required();
interface Inputs extends yup.InferType<typeof schema> {
base: null;
}
interface Props {
open: boolean;
onClose: (value: boolean) => void;
triggerRef: RefObject<HTMLAnchorElement>;
}
const UploadBatchDialog: FC<Props> = ({ open, onClose, triggerRef }) => {
const [showLoading, setShowLoading] = useState<boolean>(false);
const {
handleSubmit,
register,
setError,
formState,
setValue,
reset: resetForm
} = useForm<Inputs>({
mode: 'onSubmit',
resolver: yupResolver(schema)
});
const [uploadBatch] = useUploadBatchMutation();
const onSubmit: FormEventHandler<HTMLFormElement> = async (event) => {
console.log('test');
event.preventDefault();
try {
await handleSubmit(async (data) => {
console.log(data);
const formData = new FormData();
formData.append('file', data.file);
if (data.description) {
formData.append('name', data.description);
}
uploadBatch(formData);
})(event);
} catch (error: any) {
setTimeout(
() =>
setFormErrors({
error,
setError,
fields: Object.keys(schema.fields)
}),
50
);
}
};
const formFailed = () => Object.keys(formState.errors).length > 0;
const onDialogClose = (value: boolean) => {
resetForm();
onClose(value);
};
const onCloseAutoFocus = () => triggerRef.current?.focus();
const [file, setFile] = useState<any>();
return (
<Dialog open={open} onOpenChange={onDialogClose}>
<DialogPortal>
<DialogOverlay className="bg-opacity/90 fixed inset-0 z-50 bg-inpay-green-700">
<DialogContent
onCloseAutoFocus={onCloseAutoFocus}
onInteractOutside={(e) => e.preventDefault()}
className="fixed top-[50%] left-[50%] translate-y-[-50%] translate-x-[-50%] rounded-lg shadow-[0_0_6px_#B7BCBA]"
>
<DialogTitle className="flex h-12 items-center justify-between rounded-t-lg border-b border-b-inpay-pumice bg-white px-6">
<DownloadIcon className="w-8 stroke-1 group-hover:stroke-1.5" />
<span className="pr-20">Upload batch file</span>
<DialogClose
aria-label="Close"
className="outline-none focus:outline-none"
>
<XIcon className="-m-1 h-8 w-8 outline-none focus:outline-none" />
</DialogClose>
</DialogTitle>
<div className="flex justify-center rounded-b-lg bg-inpay-black-haze-100 p-6">
<div className="w-72">
<form onSubmit={onSubmit} autoComplete="off">
<InputField
{...register('description')}
className="mb-4"
label="Name (if left blank the file name will be used)"
name="description"
/>
<div className="mb-4 flex flex-col">
<label className="mb-0.5 pl-2 text-xs">
Attach payout request batch file
</label>
<div className="relative flex h-12 w-full rounded-lg bg-white p-1 shadow-plain focus:shadow-plain-lg group-hover:shadow-plain-lg">
<input
className="absolute opacity-0"
type="file"
accept="text/csv"
{...register('file')}
onChange={(e) => {
if (e.target.files) {
setFile(e.target.files[0]);
} else {
setFile(null);
}
}}
/>
<span className="flex w-full items-center justify-between pl-2">
{file ? file.name : 'Please attach your file'}
<button className="inpay-button h-10">
Attach file
</button>
</span>
</div>
{/* <div>Create batch template</div> */}
</div>
<button
type="submit"
className="inpay-button w-full"
// disabled={
// formState.isSubmitSuccessful || !formState.isValid
// }
>
Upload batch
</button>
{formState.errors.base && (
<div
role="alert"
className="mt-2 rounded-2xl bg-rose-200 p-3 text-sm text-inpay-red-600"
style={{
filter: 'drop-shadow(0 0 1px rgb(0 0 0 / 0.16))'
}}
>
{formState.errors.base.message}
</div>
)}
</form>
</div>
</div>
</DialogContent>
</DialogOverlay>
</DialogPortal>
</Dialog>
);
};
export default UploadBatchDialog;
while my post request looks like this:
import { customerApi } from './customerApi';
interface UploadedFile {
file: any;
name: string;
}
export const uploadBatchApi = customerApi.injectEndpoints({
endpoints: (builder) => ({
uploadBatch: builder.mutation<any, any>({
query(form) {
return {
url: 'payouts/payout_request_csv_batches',
method: 'POST',
credentials: 'include',
body: form
};
}
})
})
});
export const { useUploadBatchMutation } = uploadBatchApi;
I am wondering what I did wrong and why react hook passes that strange string. Any clues would be much appreciated.
So basically when i try to fill the form and exchange between two accounts , nothing happens
and when i check the console sometimes there's no error appearing and sometimes there's this one "Uncaught TypeError: Cannot destructure property 'connectWallet' of 'useContext(...)' as it is undefined." , and there's no data being destructered and sent to the browser , it's empty enter image description here
and it triggers "no accounts found"
this is the console after and click "send"
enter image description here
this is my code:
TransactionContext.jsx :
import React, {useEffect, useState} from 'react';
import {ethers} from 'ethers';
import {contractABI, contractAddress} from '../utils/constants';
export const TransactionContext = React.createContext();
const {ethereum} = window;
const getEthereumContract = () => {
const provider = new ethers.providers.Web3Provider(ethereum);
const signer = provider.getSigner();
const transactionContract = new ethers.Contract(contractAddress, contractABI, signer);
return transactionContract;
}
export const TransactionProvider = ({children}) => {
const [currentAccount, setCurrentAccount] = useState('');
const [formData, setFormData] = useState({addressTo: '', amount: '', keyword: '', message: ''});
const [isLoading, setIsLoading] = useState(false);
const [transactionCount, setTransactionCount] = useState(localStorage.getItem('transactionCount'));
const handleChange = (e, name) => {
setFormData((prevState) => ({ ...prevState, [name]: e.target.value }));
}
const checkIfWalletIsConnected = async () => {
try{
if(!ethereum) return alert("please install metamask");
const accounts = await ethereum.request({method: 'eth_accounts'});
if(accounts.length) {
setCurrentAccount(accounts[0]);
}
else {
console.log('no accounts found');
}
}
catch (error) {
console.log(error);
throw new Error("No ethereum object.")
}
};
const connectWallet = async () => {
try{if(!ethereum) return alert("please install metamask");
const accounts = await ethereum.request({method: 'eth_requestAccounts'});
setCurrentAccount(accounts[0]);}
catch (error){
console.log(error);
throw new Error("No ethereum object.")
}
}
const sendTransaction = async () => {
try{if(!ethereum) return alert("please install metamask");
const {addressTo, amount, keyword, message } = formData;
const transactionContract = getEthereumContract();
const parseAmount = ethers.utils.parseEther(amount);
await ethereum.request({
method: 'eth_sendTransaction',
params: [{
from: currentAccount,
to: addressTo,
gas: '0x5208',
value: parseAmount._hex,
}]
});
const transactionHash = await transactionContract.addToBlockChain(addressTo, parseAmount, message, keyword);
setIsLoading(true);
console.log('Loading - ${transactionHash.has}');
await transactionHash.wait();
setIsLoading(false);
console.log('success - ${transactionHash.has}');
const transactionCount = await transactionContract.getTransactionCount();
setTransactionCount(transactionCount.toNumber());
}
catch(error) {
console.log(error);
throw new Error("No ethereum object.")
}
}
useEffect( () => {
checkIfWalletIsConnected();
}, []);
return (
<TransactionContext.Provider value={{connectWallet, currentAccount, formData, setFormData, handleChange, sendTransaction}}>
{children}
</TransactionContext.Provider>
);
}
Welcome.jsx
import {AiFillPlayCircle} from 'react-icons/ai';
import {SiEthereum} from 'react-icons/si';
import {BsInfoCircle} from 'react-icons/bs';
import {Loader} from './';
import { TransactionContext } from '../context/TransactionContext';
import React, {useContext} from 'react';
const commonStyles = "min-h-[70px] sm:px-0 px-2 sm:min-w-[120px] flex justify-center items-center border-[0.5px] border-gray-400 text-sm font-light text-white";
const Input = ({placeholder, name, type, value, handleChange}) => (
<input
placeholder={placeholder}
type={type}
step="0.0001"
value={value}
onChange={(e) => handleChange(e, name)}
className="my-2 w-full rounded-sm p-2 outline-none bg-transparent text-white border-none text-sm white-glassmorphism" />
);
const Welcome = () => {
const {connectWallet, currentAccount, formData, sendTransaction, handleChange} = useContext(TransactionContext);
const handleSubmit = (e) => {
const { addressTo, amount, keyword, message } = formData;
e.preventDefault();
if(!addressTo || !amount || !keyword || !message) return;
sendTransaction();
};
return (
<div className="flex w-full justify-center items-center">
<div className="flex mf:flex-row flex-col items-start justify-between md:p-20 py-12 px-4">
<div className="flex flex-1 justify-start flex-col mf:mr-10 ">
<h1 className="text-3xl sm:text-5xl text-white text-gradient py-1">
Send Crypto <br/> across the world
</h1>
<p className="text-left mt-5 text-white font-light md:w-9/12 w-11/12 text-base">
Explore the universe of crypto trade cryptocurrencies smoothly and securely on Krypto
</p>
{!currentAccount && (<button type="button" onClick={connectWallet}
className="flex flex-row justify-center items-center my-5 bg-[#2952e3] p-3 rounded-full cursor-pointer hover:bg-[2546bd]">
<p className="text-white text-base font-semibold" >Connect Your Wallet </p>
</button>)}
<div className="grid sm:grid-cols-3 grid-cols-2 w-full mt-10">
<div className={`rounded-tl-2xl ${commonStyles}`}>
Reliability
</div>
<div className={commonStyles}>
Security
</div>
<div className={`rounded-tr-2xl ${commonStyles}`}>
Ethereum
</div>
<div className={`rounded-bl-2xl ${commonStyles}`}>
Web 3.0
</div>
<div className={commonStyles}>
Low fees
</div>
<div className={`rounded-br-2xl ${commonStyles}`}>
Blockchain
</div>
</div>
</div >
<div className="flex flex-col flex-1 items-center justify-start w-full mf:mt-0 mt-10">
<div className="p-3 justify-end items-start flex-col rounded-xl h-40 sm:w-72 w-full my-5 eth-card white-glassmorphism">
<div className="flex justify-between flex-col w-full h-full">
<div className="flex justify-between items-start">
<div className="w-10 h-10 rounded-full border-2 border-white flex justify-center items-center">
<SiEthereum fontSize={21} color="#fff"/>
</div>
<BsInfoCircle fontSize={17} color="fff"/>
</div>
<div>
<p className="text-white font-light text-sm ">
Address
</p>
<p className="text-white font-semibold text-lg mt-1 ">
Ethereum
</p>
</div>
</div>
</div>
<div className="p-5 sm:w-96 w-full flex flex-col justify-start items-center blue-glassmorphism">
<Input placeholder="Address to" name="addressTo" type="text" handleChange={handleChange} />
<Input placeholder="Amount of (ETH) " name="amount" type="number" handleChange={handleChange} />
<Input placeholder="Keyword (Gif)" name="Keyword" type="text" handleChange={handleChange} />
<Input placeholder="Enter message" name="message" type="text" handleChange={handleChange} />
<div className ="h-[1px] w-full bg-gray-400 my-2"/>
{false ? (
<Loader/>
) : (
<button
type="button" onClick={handleSubmit}
className="text-white w-full mt-2 border-[1px] p-2 border-[#3d4f7c] rounded-full cursor-pointer">
Send
</button>
)}
</div>
</div>
</div>
</div>
);
}
export default Welcome;
Main.jsx
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import './index.css'
import {TransactionProvider} from './context/TransactionContext';
ReactDOM.render(
<TransactionProvider>
<React.StrictMode>
<App />
</React.StrictMode>
</TransactionProvider>,
document.getElementById('root')
)
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
I have a ready-made template and in this template there is a file, which is the file below, and this code is through which you can select several images from the computer as shown in the image, but the problem is that I do not want to choose an image. I want to choose a PDF file and I tried to make Several changes such as changing the type of the selected file from image to file, but my problem was not resolved.
import { orange } from '#material-ui/core/colors';
import Icon from '#material-ui/core/Icon';
import { makeStyles } from '#material-ui/core/styles';
import clsx from 'clsx';
import FuseUtils from '#fuse/utils';
import { Controller, useFormContext } from 'react-hook-form';
function ProductImagesTab(props) {
const classes = useStyles(props);
const methods = useFormContext();
const { control, watch, setValue } = methods;
const images = watch('images');
return (
<div>
<div className="flex justify-center sm:justify-start flex-wrap -mx-16">
<Controller
name="images"
control={control}
render={({ field: { onChange, value } }) => (
<label
htmlFor="button-file"
className={clsx(
classes.productImageUpload,
'flex items-center justify-center relative w-128 h-128 rounded-16 mx-12 mb-24 overflow-hidden cursor-pointer shadow hover:shadow-lg'
)}
>
<input
accept="image/*"
className="hidden"
id="button-file"
type="file"
onChange={async (e) => {
function readFileAsync() {
return new Promise((resolve, reject) => {
const file = e.target.files[0];
if (!file) {
return;
}
const reader = new FileReader();
reader.onload = () => {
resolve({
id: FuseUtils.generateGUID(),
url: `data:${file.type};base64,${btoa(reader.result)}`,
type: 'image',
});
};
reader.onerror = reject;
reader.readAsBinaryString(file);
});
}
const newImage = await readFileAsync();
onChange([newImage, ...value]);
}}
/>
<Icon fontSize="large" color="action">
cloud_upload
</Icon>
</label>
)}
/>
<Controller
name="featuredImageId"
control={control}
defaultValue=""
render={({ field: { onChange, value } }) =>
images.map((media) => (
<div
onClick={() => onChange(media.id)}
onKeyDown={() => onChange(media.id)}
role="button"
tabIndex={0}
className={clsx(
classes.productImageItem,
'flex items-center justify-center relative w-128 h-128 rounded-16 mx-12 mb-24 overflow-hidden cursor-pointer outline-none shadow hover:shadow-lg',
media.id === value && 'featured'
)}
key={media.id}
>
<Icon className={classes.productImageFeaturedStar}>star</Icon>
<img className="max-w-none w-auto h-full" src={media.url} alt="product" />
</div>
))
}
/>
</div>
</div>
);
}
export default ProductImagesTab;
Looks like your Input HTML element is set to accept="image/*"
Instead you should have it accept pdf format like this <input type="file" accept="application/pdf">
https://www.w3schools.com/tags/tag_input.asp
Similar question answered here
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>
);
}
//...