I recently switched my reactjs code to nextjs code, and I've observed that while I'm in reactjs code, When I delete data or perform a delete action, it appears like the queries are re-fetched and I am given the most recent or updated data in the datatable, but when I attempt it on Nextjs, it does not work. Is there a way to fix this?
Keep note I am using client side for this action.
Code
Form.js
export default function MainCategoryForm() {
const [name, setName] = useState("");
const [file, setFile] = useState();
const [status, setStatus] = useState("");
const [createMainCategory, { loading }] = useMutation(
CREATE_MAINCATEGORY_MUTATION
);
async function onSubmit() {
const res = await createMainCategory({
variables: {
name,
slug: name.toLowerCase(),
file,
status,
},
update: (cache, { data: { createMainCategory } }) => {
const { mainCategories } = cache.readQuery({
query: FETCH_MAINCATEGORIES_QUERY,
});
cache.writeQuery({
query: FETCH_MAINCATEGORIES_QUERY,
data: { mainCategories: mainCategories.concat([createMainCategory]) },
});
},
refetchQueries: [{ query: FETCH_MAINCATEGORIES_QUERY }],
});
if (res) {
toast.success(`Main Category Created`, { autoClose: 2000 });
setName("");
setStatus("");
setFile("");
}
}
console.log(file);
return (
<>
<Form onSubmit={onSubmit} className={loading ? "loading" : ""}>
<h2>Create a Main Category:</h2>
<Form.Field>
<input
name="file"
type="file"
onChange={(event) => {
setFile(event.target.files[0]);
}}
/>
<Form.Input
placeholder="Please Enter Name"
name="name"
label="Name: "
onChange={(event) => {
setName(event.target.value);
}}
value={name}
/>
<label>Status: </label>
<select
name="category"
className="form-control"
onChange={(event) => {
setStatus(event.target.value);
}}
value={status}
>
<option active="true" hidden>
Please Enter Status
</option>
<option value="Activated">Activated</option>
</select>
<br />
<Button type="submit" color="teal">
Submit
</Button>
</Form.Field>
</Form>
</>
);
}
As shown above this code I have this refetchQueries: [{ query: FETCH_MAINCATEGORIES_QUERY }], in which after the add mutation or mutation it will refetch the query needed for the recent data to show in my datatable, I tried also putting that in the DeleteButton Component but it doesn't work.
Table
export default function MainCategoryTable({
mainCategory: { id, name, slug, status, url, createdAt },
}) {
return (
<>
<tr>
<td>{id}</td>
<td>
<img src={url} width={300} />
</td>
<td>{name}</td>
<td>{slug}</td>
<td>{status}</td>
<td>{dayjs(createdAt).format("h:mm:ss a")}</td>
<td>
<DeleteButton name={name} mainCategoryId={id} />
<Button>
<Link href={`/mainCategories/${id}`}>
<Icon name="edit" style={{ margin: 0 }} />
</Link>
</Button>
</td>
</tr>
</>
);
}
DeleteButton Component
export default function DeleteButton({ mainCategoryId, callback }) {
const [confirmOpen, setConfirmOpen] = useState(false);
const mutation = DELETE_MAINCATEGORY_MUTATION;
const [deleteMainCategoryOrMutation] = useMutation(mutation, {
update(proxy) {
setConfirmOpen(false);
if (mainCategoryId) {
const data = proxy.readQuery({
query: FETCH_MAINCATEGORIES_QUERY,
});
data.getMainCategories = data.getMainCategories.filter(
(ap) => ap.id !== mainCategoryId
);
toast.error(`Main Category Deleted`, { autoClose: 2000 });
proxy.writeQuery({ query: FETCH_MAINCATEGORIES_QUERY, data });
}
if (callback) callback();
},
variables: {
mainCategoryId,
},
});
return (
<>
<MyPopup content={"Delete Main Category"}>
<Button
as="div"
color="red"
floated="right"
onClick={() => setConfirmOpen(true)}
>
<Icon name="trash" style={{ margin: 0 }} />
</Button>
</MyPopup>
<Confirm
open={confirmOpen}
onCancel={() => setConfirmOpen(false)}
onConfirm={deleteMainCategoryOrMutation}
/>
</>
);
}
If you need any more code, such as my backend or any files to figure out what's wrong, I'll always amend my article. If you need any clarification or don't understand what I mean, please leave a comment down below.
You didn't set the refetchQueries in the DELETE_MAINCATEGORY_MUTATION mutation, instead you used the update option and read the query from the cache but you mutated the data, which is not the right way to do it, instead you should return a new array as follows:
const [deleteMainCategoryOrMutation] = useMutation(mutation, {
update(proxy) {
setConfirmOpen(false);
if (mainCategoryId) {
const previousData = proxy.readQuery({ query: FETCH_MAINCATEGORIES_QUERY });
const getMainCategories = previousData.getMainCategories.filter(
(ap) => ap.id !== mainCategoryId
);
const data = {
getMainCategories,
};
toast.error(`Main Category Deleted`, { autoClose: 2000 });
proxy.writeQuery({ query: FETCH_MAINCATEGORIES_QUERY, data });
}
if (callback) callback();
},
variables: {
mainCategoryId,
},
});
Related
This question already has answers here:
How to catch 401 error using fetch method of javascript
(7 answers)
Closed 4 months ago.
I want this setMessage to not display when I have PUT ERROR 404 not found in updateTemplate function. I tried it with catch(err) but I didn't succeed.
Full code snippet:
import "./App.css";
import React, { useEffect, useState } from "react";
function App() {
const [templates, setTemplates] = useState([]);
const [template, setTemplate] = useState({ templateName: "", tags: [] });
const [message, setMessage] = useState("");
useEffect(() => {
getTemplates();
}, []);
async function getTemplates() {
await fetch("http://localhost:8080/templates").then((result) => {
result.json().then((resp) => {
setTemplates(resp);
});
});
}
function selecTemplate(templateName) {
let item = templates.find((t) => t.templateName === templateName);
setTemplate(item);
setMessage("");
}
async function updateTemplate() {
let item = {
id: template.id,
templateName: template.templateName,
tags: template.tags,
};
console.warn("item", item);
await fetch(`http://localhost:8080/templates/${template.id}`, {
method: "PUT",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify(item),
}).then((result) => {
result.json().then((resp) => {
console.warn(resp);
getTemplates();
setMessage("Update succesful");
});
});
}
const handleChange = (event) => {
console.log(event.target);
if (event.target.name === "name") {
setTemplate({ ...template, name: event.target.value });
} else {
var tags = template.tags;
switch (event.target.name) {
case "tag0":
tags[0] = event.target.value;
break;
case "tag1":
tags[1] = event.target.value;
break;
case "tag2":
tags[2] = event.target.value;
break;
default:
break;
}
setTemplate({ ...template, tags: tags });
}
};
return (
<div className="App">
<div className="container">
<div className="table-responsive">
<table className="table table-dark" border="1">
<tbody>
<tr>
<td className="bg-info">Name</td>
<td className="bg-info">Select template</td>
</tr>
{templates.map((item, i) => (
<tr key={i}>
<td>{item.templateName}</td>
<td>
<button
className="btn btn-danger"
onClick={() => selecTemplate(item.templateName)}
>
Select
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
<div className="inputs">
<input
type="text"
name="name"
value={template.templateName}
onChange={handleChange}
/>{" "}
<br />
<br />
<input
type="text"
name="tag0"
value={template.tags[0]}
onChange={handleChange}
/>{" "}
<br />
<br />
<input
type="text"
name="tag1"
value={template.tags[1]}
onChange={handleChange}
/>{" "}
<br />
<br />
<input
type="text"
name="tag2"
value={template.tags[2]}
onChange={handleChange}
/>{" "}
<br />
<br />
<button className="btn btn-danger" onClick={updateTemplate}>
Update Template
</button>
<p style={{ color: "green", marginTop: "10px" }}>{message}</p>
</div>
</div>
</div>
);
}
export default App;
Below is the image of the error I am getting, I want to display "Update failed" instead "Update successful" when I get it:
fetch() throws an error on its own only when there is a network error, like the server not being able to respond. Here is note from mdn:
A fetch() promise will reject with a TypeError when a network error is encountered or CORS is misconfigured on the server-side, although this usually means permission issues or similar — a 404 does not constitute a network error, for example. An accurate check for a successful fetch() would include checking that the promise resolved, then checking that the Response.ok property has a value of true.
For other types of errors like a 404 in your case, you could use a check and throw an error yourself and then catch it, like so:
async function updateTemplate() {
let item = {
id: template.id,
templateName: template.templateName,
tags: template.tags,
};
console.warn("item", item);
await fetch(`http://localhost:8080/templates/${template.id}`, {
method: "PUT",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify(item),
})
.then((result) => {
if (!result.ok) throw Error("Update failed"); // line I added
return result.json();
})
.then((data) => {
console.warn(data);
getTemplates();
setMessage("Update succesful");
})
.catch((error) => {
console.log(error);
setMessage("Update failed");
});
}
I need access to the formik props outside of the form.
While using refs, how can I avoid checking if a function exists before calling it?
function BasicInfo({ event, initialValues, onSubmit }: Props) {
const { coords } = useLocation();
const { data: places, forward, getStaticMapUrl } = useMapbox();
const { data: games, search, leanGame } = useGames();
const throttle = useThrottle();
const formRef = useRef<FormikProps<BasicInfoValues>>(null);
const initValues: BasicInfoValues = initialValues || {
title: '',
game: { name: '' },
address: '',
starts_at: '',
ends_at: '',
coords: '',
};
const { setFieldValue, values, handleChange } = formRef.current || {};
const handleEditLocationClick = function () {
if (!setFieldValue) return;
setFieldValue('address', '');
setFieldValue('coords', '');
};
const renderLocation = function () {
if (!values) return null;
const { coords: coordinates, address } = values;
if (coordinates && address) {
const staticMapUrl = getStaticMapUrl({
coords: JSON.parse(coordinates),
width: 600,
height: 165,
});
const { street, city } = parseAddress(address);
return (
<div className="basic-info__location">
<div className="basic-info__location__image-container">
<Image
src={staticMapUrl}
alt="static map"
layout="fill"
objectFit="fill"
/>
</div>
<div className="basic-info__location__address-container">
<div className="basic-info__location__address-container__address">
<span className="basic-info__location__street">{street}</span>
<span className="basic-info__location__city">{city}</span>
</div>
<Button
text="Edit location"
color="secondary"
size="small"
onClick={handleEditLocationClick}
/>
</div>
</div>
);
} else {
return (
<AutoComplete<Feature>
name="address"
value={values.address}
onChange={(e) => {
if (!handleChange) return;
handleChange(e);
throttle.wait(() => {
if (!e.target.value) return;
if (!coords) return;
forward({
coords,
q: e.target.value,
});
}, 500);
}}
label="Venue location"
placeholder="Search for a venue or address"
items={places}
Input={InputGroup}
itemRenderer={(item) => <AddressItem placeName={item.place_name} />}
onItemClick={(item) => {
if (!setFieldValue) return;
setFieldValue('address', item.place_name);
setFieldValue('coords', JSON.stringify(item.center));
}}
/>
);
}
};
return (
<Formik initialValues={initValues} onSubmit={onSubmit} innerRef={formRef}>
{({ values, handleChange, setFieldValue }) => (
<Form id="basic-info" className="basic-info">
<FormSection
title="Basic Info"
description="Name your event and tell gamers what game will be played. Add
details that highlight what makes it unique."
icon="segment"
>
<InputGroup
name="title"
value={values.title}
onChange={handleChange}
label="Event title"
placeholder="Be clear and descriptive"
/>
<AutoComplete<Game>
name="game.name"
value={values.game.name}
onChange={(e) => {
handleChange(e);
throttle.wait(() => {
if (!e.target.value) return;
search({ name: e.target.value, limit: 5 });
}, 500);
}}
label="Featured game"
placeholder="Search games"
Input={InputGroup}
items={games}
itemRenderer={(item) => <GameItem game={item} />}
onItemClick={(item) => {
const game = leanGame(item);
setFieldValue('game', game);
}}
/>
<span>
Need game ideas?{' '}
<Link href="/" passHref>
<a className="link">Browse games by category</a>
</Link>
</span>
</FormSection>
<FormSection
title="Location"
description="Help gamers in the area discover your event and let attendees know where to show up."
icon="map"
>
{renderLocation()}
</FormSection>
<FormSection
title="Date and time"
description="Tell gamers when your event starts and ends so they can make plans to attend."
icon="date_range"
>
<InputGroup
name="starts_at"
value={values.starts_at}
onChange={handleChange}
label="Event starts"
placeholder="Search for a venue or address"
type="datetime-local"
// icon="calendar_today"
/>
<InputGroup
name="ends_at"
value={values.ends_at}
onChange={handleChange}
label="Event ends"
placeholder="Search for a venue or address"
type="datetime-local"
// icon="calendar_today"
/>
</FormSection>
</Form>
)}
</Formik>
);
}
Specifically this function where I check if setFieldValue exists:
const handleEditLocationClick = function () {
if (!setFieldValue) return;
setFieldValue('address', '');
setFieldValue('coords', '');
};
There are other functions that also need formik props so I will have to do these checks. I guess I could pass in setFieldValue function as an argument to the handleEditLocationClick function, but that doesn't seem like a good practice.
You should not be using ref at all here. Just pass the render prop parameter down to your helper functions:
function BasicInfo({ event, initialValues, onSubmit }: Props) {
const { coords } = useLocation();
const { data: places, forward, getStaticMapUrl } = useMapbox();
const { data: games, search, leanGame } = useGames();
const throttle = useThrottle();
const initValues: BasicInfoValues = initialValues || {
title: '',
game: { name: '' },
address: '',
starts_at: '',
ends_at: '',
coords: '',
};
function renderLocation({ values, handleChange, setFieldValue }) {
const handleEditLocationClick = function () {
setFieldValue('address', '');
setFieldValue('coords', '');
};
const { coords: coordinates, address } = values;
if (coordinates && address) {
const staticMapUrl = getStaticMapUrl({
coords: JSON.parse(coordinates),
width: 600,
height: 165,
});
const { street, city } = parseAddress(address);
return (
<div className="basic-info__location">
<div className="basic-info__location__image-container">
<Image
src={staticMapUrl}
alt="static map"
layout="fill"
objectFit="fill"
/>
</div>
<div className="basic-info__location__address-container">
<div className="basic-info__location__address-container__address">
<span className="basic-info__location__street">{street}</span>
<span className="basic-info__location__city">{city}</span>
</div>
<Button
text="Edit location"
color="secondary"
size="small"
onClick={handleEditLocationClick}
/>
</div>
</div>
);
} else {
return (
<AutoComplete<Feature>
name="address"
value={values.address}
onChange={(e) => {
handleChange(e);
throttle.wait(() => {
if (!e.target.value) return;
if (!coords) return;
forward({
coords,
q: e.target.value,
});
}, 500);
}}
label="Venue location"
placeholder="Search for a venue or address"
items={places}
Input={InputGroup}
itemRenderer={(item) => <AddressItem placeName={item.place_name} />}
onItemClick={(item) => {
setFieldValue('address', item.place_name);
setFieldValue('coords', JSON.stringify(item.center));
}}
/>
);
}
}
return (
<Formik initialValues={initValues} onSubmit={onSubmit} innerRef={formRef}>
{({ values, handleChange, setFieldValue }) => (
<Form id="basic-info" className="basic-info">
<FormSection
title="Basic Info"
description="Name your event and tell gamers what game will be played. Add
details that highlight what makes it unique."
icon="segment"
>
<InputGroup
name="title"
value={values.title}
onChange={handleChange}
label="Event title"
placeholder="Be clear and descriptive"
/>
<AutoComplete<Game>
name="game.name"
value={values.game.name}
onChange={(e) => {
handleChange(e);
throttle.wait(() => {
if (!e.target.value) return;
search({ name: e.target.value, limit: 5 });
}, 500);
}}
label="Featured game"
placeholder="Search games"
Input={InputGroup}
items={games}
itemRenderer={(item) => <GameItem game={item} />}
onItemClick={(item) => {
const game = leanGame(item);
setFieldValue('game', game);
}}
/>
<span>
Need game ideas?{' '}
<Link href="/" passHref>
<a className="link">Browse games by category</a>
</Link>
</span>
</FormSection>
<FormSection
title="Location"
description="Help gamers in the area discover your event and let attendees know where to show up."
icon="map"
>
{renderLocation({ values, handleChange, setFieldValue })}
</FormSection>
<FormSection
title="Date and time"
description="Tell gamers when your event starts and ends so they can make plans to attend."
icon="date_range"
>
<InputGroup
name="starts_at"
value={values.starts_at}
onChange={handleChange}
label="Event starts"
placeholder="Search for a venue or address"
type="datetime-local"
// icon="calendar_today"
/>
<InputGroup
name="ends_at"
value={values.ends_at}
onChange={handleChange}
label="Event ends"
placeholder="Search for a venue or address"
type="datetime-local"
// icon="calendar_today"
/>
</FormSection>
</Form>
)}
</Formik>
);
}
Self taught coder. Hopefully I can explain this adequately. Users create recipe cooking guides by inputing values like dish name, food category, cover img, etc. The steps array only has 1 step at the moment. I want a button that users can click that would add another "step" and "gif" to the steps array. Can anyone suggest a method to have another set of "step" and "img" render on screen when the user clicks the Add Step button? I have a handleAddSteps function with nothing inside. Somebody recommended using DOM? Just want to know if this is a good way to go before investing time down the wrong rabbit hole. Thank you kindly for any advice.
import React from "react";
import { useState, useEffect } from "react";
// Styles
import "./UploadGuide.scss";
// Firebase
import { doc, setDoc } from "firebase/firestore";
import { db, storage } from "../../firebase";
import { v4 as uuidv4 } from "uuid";
import { ref, uploadBytesResumable, getDownloadURL } from "firebase/storage";
function UploadGuide() {
const [file, setFile] = useState("");
const [data, setData] = useState({});
const [percentage, setPercentage] = useState(null);
// const [stepsCounter, setStepsCounter] = useState(0);
// Upload IMG
useEffect(() => {
const uploadFile = () => {
const name = new Date().getTime() + file.name;
const storageRef = ref(storage, file.name);
const uploadTask = uploadBytesResumable(storageRef, file);
uploadTask.on(
"state_changed",
(snapshot) => {
const progress =
(snapshot.bytesTransferred / snapshot.totalBytes) * 100;
console.log("Upload is " + progress + "% done");
setPercentage(progress);
switch (snapshot.state) {
case "paused":
console.log("Upload is paused");
break;
case "running":
console.log("Upload is running");
break;
default:
break;
}
},
(error) => {
console.log(error);
},
() => {
// Handle successful uploads on complete
getDownloadURL(uploadTask.snapshot.ref).then((downloadURL) => {
setData((prev) => ({ ...prev, img: downloadURL }));
});
}
);
};
file && uploadFile();
}, [file]);
console.log(file);
console.log(data);
// When user clicks Add Steps button, page renders another steps card
const handleAddSteps = () => {};
// When user submits guide, upload data to firecloud db and imgs to firebase storage.
const handleAddGuide = async (e) => {
e.preventDefault();
const newId = uuidv4();
// const stepsArray = []
await setDoc(doc(db, "guides", newId), {
author: e.target.author.value,
categoryId: "100",
categoryName: e.target.categoryName.value,
coverGif: data.img,
id: newId,
name: e.target.name.value,
tags: e.target.tags.value,
// steps: stepsArray
steps: [
{
step: e.target.step.value,
gif: data.img,
},
],
});
};
return (
<div className="upload">
<form onSubmit={handleAddGuide} className="upload__form">
<div className="upload__card">
<div className="upload__inputs">
<label className="upload__label">Guide Name</label>
<input
className="upload__input"
placeholder="Name of your recipe"
type="name"
name="name"
// value=""
></input>
</div>
<div className="upload__inputs">
<label className="upload__label">Author</label>
<input
className="upload__input"
type="author"
name="author"
></input>
</div>
<div className="upload__inputs">
<label className="upload__label">Category</label>
<select
name="categoryName"
className="upload__input"
defaultValue={"Cooking"}
>
<option value="Cooking">Cooking</option>
<option></option>
</select>
</div>
<div className="upload__inputs">
<label className="upload__label">Tags</label>
<input
name="tags"
className="upload__input upload__input--large"
placeholder="Add relevant tags to help people find your guide"
></input>
</div>
</div>
<div className="upload__card upload__card--addstep">
<div className="upload__inputs">
<label className="upload__label">Step 1</label>
<input
className="upload__input upload__input--large"
name="step"
></input>
</div>
<div className="upload__inputs">
<div>
<img src={file ? URL.createObjectURL(file) : ""}></img>
</div>
<label
htmlFor="file"
style={{ cursor: "pointer" }}
className="upload__add"
>
+ IMG
</label>
<input
id="file"
onChange={(e) => setFile(e.target.files[0])}
style={{ display: "none" }}
type="file"
></input>
</div>
</div>
<div className="upload__box">
<button
className="upload__add"
onClick={handleAddSteps}
style={{ cursor: "pointer" }}
>
Add Step
</button>
</div>
<div className="upload__cta">
<button
disabled={percentage !== null && percentage < 100}
type="submit"
className="upload__submit"
style={{ cursor: "pointer" }}
>
Upload
</button>
</div>
</form>
</div>
);
}
export default UploadGuide;
Here is an image of what the page looks like:
enter image description here
Please let me know if I'm missing any key information. Any advice is appreciated. Thank you.
Since each Guide can have multiple steps, I would rather create a state that handles an array of multiple step objects, than just a counter.
With this, you can render as much steps as you have in the array.
Then, adding steps just will be a matter of adding objects (with an empty state) to that array.
I made this functional example, that should be clear enough to show what I mean:
import React, { useState } from 'react';
const EMPTY_STEP = { text: '', file: null };
export default function App() {
const [guideSteps, setGuideSteps] = useState([{ ...EMPTY_STEP }]);
const handleStepChange = (stepIndex, property, value) => {
setGuideSteps((prevState) => {
const newGuideSteps = [...prevState];
newGuideSteps[stepIndex] = { ...newGuideSteps[stepIndex], [property]: value };
return newGuideSteps;
});
};
const handleAddStep = () => {
setGuideSteps((prevState) => [...prevState, { ...EMPTY_STEP }]);
};
return (
<div className="Steps">
{guideSteps.map((step, index) => {
return (
<div key={`step-${index + 1}`}>
<h2>{`Step ${index + 1}`}</h2>
<input type="text" value={step.text} onChange={(e) => handleStepChange(index, 'text', e.target.files ? e.target.files[0] : null, index)} />
<input type="file" value={step.file} onChange={(e) => handleStepChange(index, 'file', e.target.value, index)} />
</div>
);
})}
<button onClick={handleAddStep}>Add Step</button>
</div>
);
}
I currently have a simple next.js website where users can look at projects for an organization, and at the bottom of the page, the user can input a new project through the use of a form with multiple inputs. The database that i am currently using is Supabase.
My code currently takes in user input from each input box and stores them inside the newProject const, after which the data is then parsed into the createNewProject function and sent to Supabase.
const initialState = { solution_id: '', organization_id: '', budget_usd: '',other_info: '',country: '',project_duration_days: '',status: '',date_time_timezone: '' }
export default function Projects({ Projects }) {
useEffect(() => {
console.log(Projects)
}, [])
const [newProject, setProject] = useState(initialState)
console.log("User inputed data")
console.log(newProject)
const {solution_id, organization_id, budget_usd, other_info, country,project_duration_days,status,date_time_timezone} = newProject
const router = useRouter()
function onChange(e) {
setProject(() => ({ ...newProject, [e.target.name]: e.target.value }))
}
async function createNewProject() {
if (!solution_id || !organization_id || !country) return
const id = uuid()
newProject.id = id
const {data} = await supabase
.from('Projects')
.insert([
{ solution_id, organization_id, budget_usd,other_info,country,project_duration_days,status,date_time_timezone }
])
router.push(`/projects/${data.id}`)
}
return (
<div>
{Projects.map(project => {
return (
<div key={project.id}>
<h1><b>{project.Solutions.name} in {project.country}</b></h1>
<h2>Budget USD: {project.budget_usd}</h2>
<h2>Duration: {project.project_duration_days} days</h2>
<h2>Status: {project.status}</h2>
<h2>Organization: {project.Organizations.name}</h2>
<h2>Project Id: {project.id}</h2>
<button type="button" onClick={() => router.push(`/projects/${project.id}`)}>Donate now</button>
<br></br>
</div>
)
})}
<label htmlFor="solution_id ">solution_id</label>
<input onChange={onChange} value={newProject.solution_id} type="text" id="solution_id" name="solution_id" required />
<label htmlFor="organization_id ">organization_id</label>
<input onChange={onChange} value={newProject.organization_id} type="text" id="organization_id" name="organization_id" required />
<label htmlFor="budget_usd ">Last budget_usd</label>
<input onChange={onChange} value={newProject.budget_usd} type="text" id="budget_usd" name="budget_usd" required />
<label htmlFor="other_info ">other_info</label>
<input onChange={onChange} value={newProject.other_info} type="text" id="other_info" name="other_info" required />
<label htmlFor="country ">country</label>
<input onChange={onChange} value={newProject.country} type="text" id="country" name="country" required />
<label htmlFor="project_duration_days ">Project Duration Days</label>
<input onChange={onChange} value={newProject.project_duration_days} type="text" id="project_duration_days" name="project_duration_days" required />
<label htmlFor="status ">status</label>
<input onChange={onChange} value={newProject.status} type="text" id="status" name="status" required />
<label htmlFor="date_time_timezone ">date_time_timezone</label>
<input onChange={onChange} value={newProject.date_time_timezone} type="text" id="date_time_timezone" name="date_time_timezone" required />
<button type="button" onClick={createNewProject} >Submit new project</button>
</div>
)
}
export async function getServerSideProps() {
const fetchOrgs = async () => {
let { data: Organizations, error } = await supabase
.from('Organizations')
.select('*')
return Organizations
}
const fetchSolutions = async () => {
let { data: Solutions, error } = await supabase
.from('Solutions')
.select('*')
return Solutions
}
const fetchProjects = async () => {
let { data: Projects, error } = await supabase
.from('Projects')
.select(`
id,
solution_id,
organization_id,
budget_usd,
country,
project_duration_days,
status,
Solutions(name),
Organizations(name)
`)
.order('id', { ascending: true })
console.log(Projects)
return Projects
}
const Organizations = await fetchOrgs();
const Solutions = await fetchSolutions();
const Projects = await fetchProjects();
return { props: { Solutions, Organizations, Projects } }
}
However, whenever I press the submit button, the console.log for the newProject, would show that there is not data being passed into the variables, only the empty placeholder data in the initialState const. As such, I am unsure about how to pass data from next.js input forms into a variable to be posted into supabase.
I make form using Formik in my app. When I send form to my local server I create image with title. Attach images I should using input type="file".But I have very little experience using the formik.
What I should write in propTypes when in form I use input type="file' in file InputImage.js?
And How to add input type="file" in file AddImage.js in mark place?
Now I want to create input which attach image component InputImage.js similar to InputTitle.js.
I comment line where I dont know what I should write.
AddImage.js:
const AddImage = (props) => {
const {handleSubmit, values, handleChange} = useFormik({
initialValues: {
title: '',
image: '' // Did I write correctly here?
},
validateOnchange: false,
onSubmit: async (formValues) => {
const response = await api(`${imageRoutePath}`, {
method:'POST',
body: JSON.stringify(formValues),
});},
});
return (
<div>
<form onSubmit={handleSubmit}>
<InputTitle
label="title"
id="title"
inputProps={{
name:'title',
value: values.title,
onChange: handleChange,
}}
/>
<InputImage
label="image"
id="image"
inputProps={{
name:'image',
// WHAT I SHOULD WRITE THERE?
onChange: handleChange,
}}
/>
<button type="submit" disabled={isSubmitting}>Add</button>
</form>
</div>
);
};
export default AddImage;
InputImage.js:
const InputImage = ({
label, inputProps, error, id,
}) => (
<div className="formInputCategory">
<label htmlFor={id} className="formInputLabelCategory">
{label}
</label>
<input {...inputProps} id={id} />
{error && <span className="formInputErrorCategory">{error}</span>}
</div>
);
InputImage.propTypes = {
label: PropTypes.string.isRequired,
// WHAT I SHOULD WRITE THERE?
error: PropTypes.string,
id: PropTypes.string.isRequired,
};
InputImage.defaultProps = {
error: '',
}
---------------------------------------------------------------------------------------
example how I write InputTitle.js:
const InputTitle = ({
label, inputProps, error, id,
}) => (
<div className="formInputCategory">
<label htmlFor={id} className="formInputLabelCategory">
{label}
</label>
<input {...inputProps} id={id} />
{error && <span className="formInputErrorCategory">{error}</span>}
</div>
);
InputTitle.propTypes = {
label: PropTypes.string.isRequired,
inputProps: PropTypes.instanceOf(Object).isRequired,
error: PropTypes.string,
id: PropTypes.string.isRequired,
};
InputTitle.defaultProps = {
error: '',
}
Formik doesnot support fileupload by default, But you can try the following
<input id="file" name="file" type="file" onChange={(event) => {
setFieldValue("file", event.currentTarget.files[0]);
}} />
Here "file" represents the key that you are using for holding the file
setFieldValue is obtained from <Formik />
reference : formik setFieldValue prop
your code will look like :
const AddImage = (props) => {
const {handleSubmit, values, handleChange, setFieldValue } = useFormik({
initialValues: {
title: '',
image: '' // Did I write correctly here?
},
validateOnchange: false,
onSubmit: async (formValues) => {
const response = await api(`${imageRoutePath}`, {
method:'POST',
body: JSON.stringify(formValues),
});},
});
return (
<div>
<form onSubmit={handleSubmit}>
<InputTitle
label="title"
id="title"
inputProps={{
name:'title',
value: values.title,
onChange: handleChange,
}}
/>
<InputImage
label="image"
id="image"
inputProps={{
name:'file',
id="file",
// WHAT I SHOULD WRITE THERE?
type="file",
onChange={(event) => {
setFieldValue("file", event.currentTarget.files[0]);
}},
}}
/>
<button type="submit" disabled={isSubmitting}>Add</button>
</form>
</div>
);
};
export default AddImage;