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>
);
}
Related
I am newbie in React. I am trying to create a react web app than enables me POST data (with image and video files) to an API endpoint.
The first challenge is that the files (image and videos) get to my database(MSsql) as a blob.See image below
{
"id": 18,
"propertyId": "11",
"category": "Shortlet",
"roomType": "3 bedrooms",
"location": "Lekki phase1",
"amount": 9000000,
"thumbnailImageUrl": "blob:http://localhost:3000/97888e66-2fa8-4198-bf71-4f80f5a49b8c",
"interiorVideoUrl": "blob:http://localhost:3000/e111b303-007c-45bc-b80b-f017d4d559e1",
"compoundVideoUrl": "blob:http://localhost:3000/fc167a1f-a1bd-4952-a851-534cb87ae8be",
"streetVideoUrl": "blob:http://localhost:3000/d7a7aca6-fd56-4fb6-8e36-7e75bae3ed3a",
"dateCreated": "2022-09-30T07:31:22.66",
"dateModified": "2022-09-30T07:31:22.66",
"isPropertyAvailable": true,
"isDeleted": false
}
This is the code used to POST the data to the endpointusing setState method to get the values
POSTing the data using axios
First Question: I am not sure the blob files will be useful when retrieving them on the client-side from the database (especically on a different device)?
The 2nd Challenge: Someone advised i use FormData object to solve the blob issue. When i tried that, i get an axios 415 error in the console as shown below. The error persisted after console logging the FormData object to ensure all d data r passed to d endpoint.appending my data using FormData. Here is my code:
import axios from "axios";
import React, { useState } from "react";
import { Helmet } from "react-helmet";
import styled from "styled-components";
function AdminAddProperty() {
const categoryOptions = ["Rent", "Shortlet"];
const bedroomsOptions = [
"Shared",
"1 bedroom",
"2 bedrooms",
"3 bedrooms",
"4+ bedrooms",
];
const areaOptions = [
"Oniru",
"Lekki phase1",
"Lekki phase1 (right)",
"Ikate",
"Salem",
"Ilasan",
"Jakande",
"Osapa",
"Agungi",
"Ologolo",
"Igboefon",
"Idado",
"Newroad",
"Chevron",
"Conservation road",
"Orchid road",
"Ikota",
"VGC",
"Ajah",
"Awoyaya",
];
const [category, setCategory] = useState();
const [roomType, setRoomType] = useState();
const [location, setLocation] = useState();
const [price, setPrice] = useState(0);
const [thumbnailImageUrl, setThumbnailImageUrl] = useState();
const [interiorVideoUrl, setInteriorVideoUrl] = useState();
const [compoundVideoUrl, setCompoundVideoUrl] = useState();
const [streetVideoUrl, setStreetVideoUrl] = useState();
console.log(category);
console.log(roomType);
console.log(location);
console.log(price);
const handleCategoryChange = (e) =>{
setCategory(e.target.value)
}
const handleBedroomsChange = (e) =>{
setRoomType(e.target.value)
}
const handleAreaChange = (e) =>{
setLocation(e.target.value)
}
const handlePriceChange = (e) =>{
setPrice(e.target.value)
}
const handleimgChange = (e) =>{
setThumbnailImageUrl(URL.createObjectURL(e.target.files[0]))
};
const handleIntVidChange = (e) =>{
setInteriorVideoUrl(URL.createObjectURL(e.target.files[0]))
}
const handleCompChange = (e) =>{
setCompoundVideoUrl(URL.createObjectURL(e.target.files[0]))
}
const handleStreetChange = (e) =>{
setStreetVideoUrl(URL.createObjectURL(e.target.files[0]))
}
const handleFormSubmit = async (e) => {
//created a FormData object
var formData = new FormData();
// const string = "string"
// //update the FormData object
formData.append('propertyId', 18);
formData.append('category', category);
formData.append('roomType', roomType);
formData.append('location', location);
formData.append('amount', price );
formData.append('thumbnailImageUrl',thumbnailImageUrl);
formData.append('interiorVideoUrl', interiorVideoUrl );
formData.append('compoundVideoUrl', compoundVideoUrl );
formData.append('streetVideoUrl', streetVideoUrl);
formData.append('isPropertyAvailable', true);
console.log('%O',formData)
console.log([...formData])
console.dir(formData,{
Depth: null
})
console.log(thumbnailImageUrl)
e.preventDefault();
await axios({
method: 'post',
url: 'http://ampeer-001-site1.gtempurl.com/api/Admin/CreateProperty',
data: formData,
headers: {
'ContentType': 'application/octet-stream'
},
// data:{
// propertyId: '11',
// category: category,
// roomType: roomType,
// location: location,
// amount: price,
// thumbnailImageUrl: thumbnailImageUrl,
// interiorVideoUrl: interiorVideoUrl,
// compoundVideoUrl: compoundVideoUrl,
// streetVideoUrl: streetVideoUrl,
// isPropertyAvailable: true,
// }
})
.then((res)=>{
console.log(res)
alert(res.data.responseMessage)
})
.catch((err)=> console.log(err))
}
return (
<div>
<Helmet>
<title>Add New Property</title>
</Helmet>
<FormHolder>
<form onSubmit={handleFormSubmit} style={{height:'500px'}}>
<FormOne>
<label>Category:</label>
<select value={category} onChange={handleCategoryChange}>
{categoryOptions.map((item, key) => (
<option key={key}>{item}</option>
))}
</select>
<label>Bedrooms:</label>
<select value={roomType} onChange={handleBedroomsChange}>
{bedroomsOptions.map((item, key) => (
<option key={key}>{item}</option>
))}
</select>
<label>Area:</label>
<select value={location} onChange={handleAreaChange}>
{areaOptions.map((item, key) => (
<option key={key}>{item}</option>
))}
</select>
<label>Price:</label>
<input
type="number"
name="price"
id="price"
min="100000"
max="50000000"
placeholder="Price of property"
value={price}
onChange={handlePriceChange}
></input>
</FormOne>
<FormTwo>
<BoxOne>
<BoxOneCont>
<label>Thumbnail Picture</label>
<input
type="file"
name="picture"
accept="image/png, image/gif, image/jpeg"
// value={thumbnailImageUrl}
onChange={handleimgChange}
></input>
</BoxOneCont>
</BoxOne>
<BoxTwo>
<BoxTwoCont>
<label>Interior Video</label>
<input
type="file"
name="intvideo"
accept="video/*"
onChange={handleIntVidChange}
// value={interiorVideoUrl}
></input>
</BoxTwoCont>
</BoxTwo>
<BoxThree>
<BoxThreeCont>
<label>Compound Video</label>
<input
type="file"
name="compvideo"
accept="video/*"
onChange={handleCompChange}
></input>
</BoxThreeCont>
</BoxThree>
<BoxFour>
<BoxFourCont>
<label>Street Video</label>
<input
type="file"
name="streetvideo"
accept="video/*"
onChange={handleStreetChange}
></input>
</BoxFourCont>
</BoxFour>
</FormTwo>
<div style={{ margin: "auto", width: "365px" }}>
<Input />
</div>
</form>
</FormHolder>
</div>
);
}
console log of FormData()
Error message when using FormData
I would appreciate if any senior can help me solve this problem as i have spent so many days trying to solve this myself. Thank you.
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 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,
},
});
If i switch between Transfer to Beneficiary and All Beneficiary tab the Select options get duplicated but if i change the second useEffect hook dependency to allBeneficiary.current instead of beneficiaries presently there, Select options doesnt duplicate, but the options are not rendered on the first render until I switch to All Beneficiary tab and back to Transfer Beneficiary
Below is the Transfer to Beneficiary code
// Hooks and Contexts
import React, { useState, useContext, useEffect, useRef } from "react";
import { TransferPointsContext } from "../../../../../context/TransferPoints";
import { LoaderContext } from "../../../../../context/Loading";
// Components
import TransferSummary from "../../../../common/modals/TransferSummary";
import Loading from "../../../../features/Loader/Loading";
// UI
import swal from "sweetalert";
import toastr from "toastr";
import Select from "react-select";
import "./css/transfer-points.css";
import { nanoid }from 'nanoid'
function TransferPoints() {
const [showTransferSummary, setShowTransferSummary] = useState(false);
const [transferSummaryData, setTransferSummaryData] = useState(false);
const [showBeneficiaryDataPage, setShowBeneficiaryDataPage] = useState(false);
const [checked, setChecked] = useState(false);
const [ options, setOptions ] = useState([])
const {
verifyCardNumber,
verifyCardState,
getBeneficiaryList,
beneficiaries, //list of beneficiaries from API
hideBeneficiaryDataPage,
setInputs,
inputs,
} = useContext(TransferPointsContext);
const { loading } = useContext(LoaderContext);
toastr.options.progressBar = true;
toastr.options = {
toastClass: "alert",
iconClasses: {
error: "alert-error",
info: "alert-info",
success: "alert-success",
warning: "alert-warning",
},
};
const allBeneficiaries = useRef([]);
useEffect(() => {
getBeneficiaryList();
}, [allBeneficiaries]);
useEffect(() => {
if (beneficiaries.data !== null) {
if (
beneficiaries.data.status === 0 &&
beneficiaries.data.success === false
) {
toastr.error("Failed to fetch user beneficiaries!", "error", {
iconClass: "toast-error",
});
console.log("beneficiaries", beneficiaries.data);
} else if (
beneficiaries.data.status === 1 &&
beneficiaries.data.success === true
) {
console.log('All beneficiary ', allBeneficiaries.current)
beneficiaries.data.data.forEach((beneficiary) => {
console.log('For each ', beneficiary)
allBeneficiaries.current.unshift({
value: beneficiary.membership_number,
label: `${beneficiary.first_name} ${beneficiary.last_name == null ? '' : beneficiary.last_name}`,
});
});
console.log('LENGTH ', allBeneficiaries.current.length)
}
}
}, [beneficiaries]);
useEffect(() => {
if (hideBeneficiaryDataPage) {
setShowBeneficiaryDataPage(false);
}
}, [hideBeneficiaryDataPage]);
const beneficiaryData = useRef({});
useEffect(() => {
if (verifyCardState.data !== null) {
if (
verifyCardState.data.status === 1 &&
verifyCardState.data.success === true
) {
setShowBeneficiaryDataPage(true);
beneficiaryData.current = {
name: `${verifyCardState.data.data.first_name} ${verifyCardState.data.data.last_name == null ? '' : verifyCardState.data.data.last_name}`,
};
toastr.success("Membership Id Validated!", "Success", {
iconClass: "toast-success",
});
return;
}
if (
verifyCardState.data.status === 0 &&
verifyCardState.data.success === false
) {
if (verifyCardState.data.message && !verifyCardState.data.data) {
toastr.error(verifyCardState.data.message, "Validation failed!", {
iconClass: "toast-error",
});
setShowBeneficiaryDataPage(false);
return;
}
setShowBeneficiaryDataPage(false);
const errorMessages = verifyCardState.data.data;
for (const error in errorMessages) {
toastr.error(errorMessages[error], "Validation Error!", {
iconClass: "toast-error",
});
}
return;
}
}
}, [verifyCardState]);
const handleSearchInput = (event) => {
const card_number = event.value;
setInputs((inputs) => ({
...inputs,
card_number,
}));
verifyCardNumber(card_number);
};
const proceedToTransfer = () => {
const amount = document.getElementById("amount").value;
if (amount.trim().length === 0) {
swal({
title: "Oops!",
text: `Amount field cannot be empty`,
icon: "error",
button: "Ok",
});
return;
}
setShowTransferSummary(!showTransferSummary);
setTransferSummaryData({
amount,
name: beneficiaryData.current.name,
membership_id: inputs.card_number,
save_beneficiary: (beneficiaryData.current.save_beneficiary == 1) ? 1 : 0,
});
};
const handleInputChange = (event) => {
event.persist();
setInputs((inputs) => ({
...inputs,
[event.target.name]: event.target.value,
}));
};
const handleChange = (event) => {
event.persist();
setChecked(event.target.checked);
const save_beneficiary = event.target.checked === true ? 1 : 0;
beneficiaryData.current.save_beneficiary = save_beneficiary;
};
console.log('LENGTH ', allBeneficiaries.current)
// console.log('current beneficiaries ', (allBeneficiaries.current) )
// console.log('CHECKED ', beneficiaryData.current.save_beneficiary )
// let id = nanoid()
return (
<div>
{/* {loading ? <Loading /> : ""} */}
<form action="#">
<div className="row">
<div className="col-md-12">
<div className="form-group">
<label htmlFor="acc-email">Select Beneficiary </label>
<Select
onChange={handleSearchInput}
className="basic-single"
classNamePrefix="select"
isClearable="true"
isSearchable="true"
name="beneficiary_card_number"
defaultValue="Select"
options={allBeneficiaries.current} //THIS RETURNS DUPLICATED VALUE ON NAVIGATING TO ALL BENEFICIARIES AND BACK
/>
</div>
<h6 class="mt-3 heading-border border-0">OR</h6>
<div className="row align-items-center justify-content-between">
<div className="col-md-8">
<label htmlFor="card_number">Enter Membership Id</label>
<input
type="text"
className="form-control"
name="card_number"
onChange={handleInputChange}
value={inputs.card_number}
/>
</div>
<div className=" col-4 " style={{marginTop: '30px', paddingLeft: '10px', textAlign: 'end',}}>
<button
onClick={() => verifyCardNumber(inputs.card_number)}
type="button"
className="btn-lg btn btn-primary"
>
Validate Id
</button>
</div>
</div>
</div>
{showBeneficiaryDataPage === true ? (
<div className="col-sm-12">
<h6 class="mt-3 heading-border border-0"></h6>
<div className="row">
<div className="col-md-6">
<div className="form-group">
<label htmlFor="acc-name">Name</label>
<input
type="text"
className="form-control"
id="acc-name"
required
disabled
name="acc-name"
value={beneficiaryData.current.name}
/>
</div>
</div>
<div className="col-md-6">
<div className="form-group">
<label htmlFor="acc-lastname">Membership Id</label>
<input
type="text"
className="form-control"
id="acc-lastname"
required
disabled
name="acc-lastname"
value={inputs.card_number}
/>
</div>
</div>
</div>
<div className="row">
<div className="col-md-12">
<div className="form-group">
<label htmlFor="acc-lastname">Amount</label>
<input
type="text"
className="form-control"
id="amount"
required
name="amount"
onChange={handleInputChange}
value={inputs.amount}
/>
</div>
</div>
</div>
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
checked={checked}
name="save_beneficiary"
onChange={handleChange}
/>
<span className="ml-3 font-weight-bold terms-condition">
Save Beneficiary
</span>
</div>
<div className="mb-2"></div>
<div className="form-footer">
<div className="col-md-12 d-flex justify-content-center">
<button
onClick={() => proceedToTransfer()}
type="button"
className="btn-lg w-50 btn btn-primary"
>
Proceed
</button>
</div>
</div>
</div>
) : (
""
)}
</div>
</form>
{showTransferSummary === true ? (
<TransferSummary data={transferSummaryData} setShowTransferSummary={setShowTransferSummary} setShowBeneficiaryDataPage={setShowBeneficiaryDataPage}/>
) : (
""
)}
</div>
);
}
export default React.memo(TransferPoints);
This is the code for All Beneficiaries
import React, { useState, useContext, useEffect, useRef } from "react";
import "./css/transfer-points.css";
import { TransferPointsContext } from "../../../../../context/TransferPoints";
import Loading from "../../../../features/Loader/Loading";
import swal from "sweetalert";
import { LoaderContext } from "../../../../../context/Loading";
import toastr from "toastr";
import axios from 'axios'
import { ToastContainer, toast } from 'react-toastify';
function ShowAllBeneficiariesPage() {
const [ data, setData ] = useState([])
const {
getBeneficiaryList,
beneficiaries,
removeBeneficiary,
state,
} = useContext(TransferPointsContext);
const { loading } = useContext(LoaderContext);
toastr.options.progressBar = true;
toastr.options = {
toastClass: "alert",
iconClasses: {
error: "alert-error",
info: "alert-info",
success: "alert-success",
warning: "alert-warning",
},
};
const handleDelete = (id) => {
swal({
title: "Are you sure you want to remove beneficiary?",
text: "You won't be able to revert this!",
icon: "warning",
buttons: ["Cancel", "Proceed!"],
showCancelButton: true,
confirmButtonColor: "#3085d6",
cancelButtonColor: "#d33",
}).then((result) => {
if (result === true) {
removeBeneficiary(id);
console.log('ID OF RECEIVER ', id)
const newBeneficiary = data.filter(add => add.id !== id)
setData(newBeneficiary)
toastr.success("Beneficiary Removed !", "Success", {
iconClass: "toast-success",
});
}
});
};
const fetchData = () => {
axios.get(`user/beneficiaries`)
.then( res => setData(res.data.data ))
}
useEffect(() => {
fetchData()
}, [])
return (
<div>
{/* {loading ? <Loading /> : ""} */}
{ data.length === 0 ?
(
<div style={{textAlign: 'center'}}>No beneficiaries found</div>
)
:
(
<div className="col-sm-12">
{data.map((item) => {
console.log('Beneficiary Data ', data)
return (
<p className="mb-1 p-4 beneficiary-list">
{item.first_name} {item.last_name} - {item.membership_number}
<i
style={{ cursor: "pointer" }}
onClick={() => handleDelete(item.id)}
class="float-right fas fa-trash"
></i>{" "}
</p>
);
})}
</div>
)}
</div>
);
}
export default (ShowAllBeneficiariesPage);
I can't completely fix your issue, because I'd need more context and time, but I've found some issues on your code.
Never ever ever, have something in your render code that has a reference to a useRef variable. When a useRef value changes, react will completely ignore it and will not update your component. Use setState for those.
It sounds like your allBeneficiaries instead of being a ref or a state it's just derived state: It looks like it's a derived value from beneficiaries. In this case, you don't need to use any hook, just declare it as a const (e.g. const allBeneficiaries = getBeneficiaries(beneficiaries)). If you have performance issues, then consider using useMemo, but it should not be needed.
Never use a useRef as a dependency value in a useEffect - Same thing, react doesn't care about ref values, so you'll have unexpected behaviour there (effects retriggering when it shouldn't, effects not triggering when they should)
Try to avoid useEffect as much as posible. It should only be used for specific cases, such as fetching something from a server or manipulating the dom. For the rest of them, it's just problematic, best avoided.
Using allBeneficiaries (or any other ref object) as a dependency for a hook won't help you at all. The ref object's identity will never change over the lifetime of a component.
If you want to run an effect/... when the value boxed within the allBeneficiaries ref changes, the dependency will need to be allBeneficiaries.current.
Beside that, there's no good reason to use a ref for allBeneficiaries. Since it affects rendering, you will want to save it as a state atom (useState).
I have been trying to get my dynamic form to work in my Meteor React application.
It was all working as requested before I started add this but now I can't get it to work. I'm getting a "Uncaught TypeError: Cannot set property '0' of undefined" error. this points to this line:
{this.state.inputs.map((input, idx) => <input
This my whole code, I know it's a bit messy so any feedback is highly appreciated:
import React, { Component } from 'react';
import { Row, Col, Checkbox, Radio, ControlLabel, HelpBlock, FormGroup, FormControl, Button, Tabs, Tab } from 'react-bootstrap';
import { Bert } from 'meteor/themeteorchef:bert';
import { insertComment } from '../../../api/comments/methods';
import ReactQuill from 'react-quill';
var s3Url = null;
export default class AddSparkShanghai extends Component {
constructor(props) {
super(props);
this.createSpark = this.createSpark.bind(this);
this.onChange = this.onChange.bind(this);
this.state ={
inputs: ['input-0'],
city: '',
person: '',
location: '',
title: '',
content: [],
mediaUrls: [],
};
}
componentWillMount(){
// we create this rule both on client and server
Slingshot.fileRestrictions("myFileUploads", {
allowedFileTypes: ["image/png", "image/jpeg", "image/gif"],
maxSize: 10 * 1024 * 1024 // 10 MB (use null for unlimited)
});
}
upload(file){
var uploader = new Slingshot.Upload("myFileUploads");
uploader.send(document.getElementById('input').files[0], function (error, downloadUrl) {
if (error) {
// Log service detailed response
alert (error);
}
else {
s3Url = encodeURI(downloadUrl);
Bert.alert('File uploaded!', 'success');
Meteor.users.update(Meteor.userId(), {$push: {"profile.files": downloadUrl}});
}
});
}
createSpark(event) {
event.preventDefault();
var formData = $('#form').serializeArray()
console.log(formData);
var mediaArray = [];
if (this.mediaUrls.value == 0) {
mediaArray = [];
} else {
mediaArray.push(encodeURI(this.mediaUrls.value));
console.log(this.mediaUrl.value);
console.log(mediaArray);
}
const city = 'Shanghai';
const person = this.person.value;
const location = this.location.value;
const title = this.title.value;
const content = this.state.content;
const fileLink = s3Url;
const timestamp = parseInt(this.props.timestamp);
const mediaUrls = mediaArray;
const approved = true;
const adminSpark = true;
const createdBy = Meteor.userId();
insertComment.call({
city, person, location, title, content, fileLink, timestamp, approved, adminSpark, createdBy, mediaUrl,
}, (error) => {
if (error) {
Bert.alert(error.reason, 'danger');
} else {
Bert.alert('Spark added!', 'success');
}
});
}
onChange(html) {
this.setState ({ content: html });
}
appendInput() {
var newInput = `input-${this.state.inputs.length}`;
console.log (newInput);
this.setState({ inputs: this.state.inputs.concat([newInput]) });
}
render() {
const events = {
'text-change': delta => {
}
}
return (
<div className="background-container">
<form ref={(input) => this.sparkForm = input} onSubmit={(e) => this.createSpark(e)}>
<ControlLabel>Select your person (optional)</ControlLabel>
<select id="formControlsPerson" placeholder="Choose your person" className="form-control" ref={(input) => this.person = input}>
<option value='select'>Select your person</option>
<option value='jane'>Jane Siesta</option>
<option value='ben'>Ben Huang</option>
<option value='han'>Han Han</option>
<option value='mau'>Mau Mau</option>
<option value='void'>VOID</option>
<option value='tommy'>Tommy Hendriks</option>
<option value='gareth'>Gareth Williams</option>
<option value='gigi'>Gigi Lee</option>
</select>
<ControlLabel>Select your location (optional)</ControlLabel>
<select id="formControlsLocation" placeholder="Choose your location" className="form-control" ref={(input) => this.location = input}>
<option value='select'>Select your location</option>
<option value='shelter'>Shelter</option>
<option value='mansion'>The Mansion</option>
</select>
<ControlLabel>Title</ControlLabel>
<input type="text" label="Title" placeholder="Enter your title" className="form-control" ref={(input) => this.title = input}/>
<ControlLabel>Add Image</ControlLabel>
<div className="upload-area">
<p className="alert alert-success text-center">
<span>Click or Drag an Image Here to Upload</span>
<input type="file" id="input" className="file_bag" onChange={this.upload} />
</p>
</div>
<ControlLabel>Content</ControlLabel>
<div className='_quill'>
<ReactQuill
toolbar={false}
theme="snow"
ref='editor'
onChange={this.onChange}
events={events} />
</div>
<br />
<ControlLabel>Media (optional)</ControlLabel>
<div id="dynamicInput">
{this.state.inputs.map((input, idx) => <input
key={ input }
type="text"
label="Media"
placeholder="Add your media url"
className="form-control"
ref={(input) => this.mediaUrls[idx] = input}/> )}
</div>
<Button onClick={ () => this.appendInput() }>
Add media field
</Button>
<ControlLabel>Media (optional)</ControlLabel>
<div id="dynamicInput">
{this.state.inputs.map(input => <input key={input} type="text" label="Media" placeholder="Add your media url" className="form-control" ref={(input) => this.mediaUrl = input}/> )}
</div>
<Button onClick={ () => this.appendInput() }>
Add media field
</Button>
<Button type="submit" data-dismiss="modal">Submit</Button>
</form>
</div>
)}
}
I guess the problem is this line: ref={(input) => this.mediaUrls[idx] = input}/> )}, it seems like the value of this.mediaUrls is undefined