I had a child component UploadImage.js and parent component Parent.js. I am uploading an image and want to pass the value of file name to the Parent.js component. How can I do so?
UploadImage.js
import React, { useEffect, useState } from 'react';
import { useDropzone } from 'react-dropzone';
.
.
.
const UploadImage = () => {
const [files, setFiles] = useState([]);
const { getRootProps, getInputProps } = useDropzone({
accept: {
'image/*': []
},
onDrop: acceptedFiles => {
setFiles(acceptedFiles.map(file => Object.assign(file, {
preview: URL.createObjectURL(file)
})));
}
});
//preview component
const thumbs = files.map(file => (
<div style={thumb} className="d-flex flex-row mt-1 col-12 mx-auto" key={file.name}>
<div style={thumbInner}>
<img
src={file.preview}
style={img}
// Revoke data uri after image is loaded
onLoad={() => { URL.revokeObjectURL(file.preview) }}
/>
</div>
</div>
)
);
//wanted to pass file[0].name to Parent Component
console.log(files.length > 0 ? files[0].name : "")
useEffect(() => {
// Make sure to revoke the data uris to avoid memory leaks, will run on unmount
return () => files.forEach(file => URL.revokeObjectURL(file.preview));
}, []);
return (
<section className="container">
<div {...getRootProps({ className: 'dropzone mx-3 text-center mt-4 mb-2 p-3 bg-light border border-primary border-1 rounded-4 ' })}>
<input {...getInputProps()} />
<p className='fw-bold text-primary'>Drag 'n' drop some files here, or click to select files</p>
</div>
<aside style={thumbsContainer} className="d-flex flex-row">
{thumbs}
</aside>
</section>
);
}
export default UploadImage;
And my Parent component is like this
import React, { useState} from "react";
import UploadImage from "../components/uploadImage";
const Parent = () => {
const [uploadFileName, setUploadFileName] = useState("");
return (
<div className="mx-3 mt-4 mb-2">
<UploadImage />
<h3 className="m-3">{uploadFileName} </h3>
</div>
);
};
export default UploadButton;
How can I display the file name from UploadImage.js to Parent.js in the uploadFileName state ???
you create a function in your parent element like:
const NameSetter = imageName => {
setUploadFileName(imageName);
}
and then send the NameSetter as a prop to your child element like:
<UploadImage nameHandler={NameSetter} />
and then in your child element you call the nameHandler prop like:
(call this when you get the name, for ex: on the callback of your backend )
props.nameHandler('name of your image');
you can use call back props to update the children to parent.
import React, { useState} from "react";
import UploadImage from "../components/uploadImage";
const Parent = () => {
const [uploadFileName, setUploadFileName] = useState("");
return (
<div className="mx-3 mt-4 mb-2">
<UploadImage setUploadFileName={setUploadFileName}/>
<h3 className="m-3">{uploadFileName} </h3>
</div>
);
};
export default UploadButton;
Then you can set whereever you want to call in child it will update in parent component. You can check through by adding consoling on the parent component.
Hey MagnusEffect you're almost correct, just make these changes-
In UploadImage.js-
const UploadImage = ({setUploadFileName}) => {
<input {...getInputProps()} onChange=
{(e)=>setUploadFileName(e.target.files[0].name)} />
}
While in Parent Component just pass setvalues-
const Parent = () => {
const [uploadFileName, setUploadFileName] = useState("");
return (
<div className="mx-3 mt-4 mb-2">
<UploadImage setUploadFileName={setUploadFileName} />
<h3 className="m-3">{uploadFileName} </h3>
</div>
);
}
Hope this code will help to solve your query if you still facing issue, just lemme know i will help you more. Thanks
You should move const [files, setFiles] = useState([]); to Parents.js and then pass them by Props for UploadImage.js.
// UploadImage Component
const UploadImage = (props) => {
const {files, onUpdateFiles} = props;
const { getRootProps, getInputProps } = useDropzone({
accept: {
'image/*': []
},
onDrop: acceptedFiles => {
onUpdateFiles(acceptedFiles.map(file => Object.assign(file, {
preview: URL.createObjectURL(file)
})));
}
});
...
}
// Parents component
const Parent = () => {
const [files, setFiles] = useState([]);
return (
<div className="mx-3 mt-4 mb-2">
<UploadImage files={files} onUpdateFiles={setFiles} />
{files.length > 0 && <h3 className="m-3">{files[0].name}</h3>}
</div>
);
};
Related
I am running into an issue where regardless of what component is clicked in a list of Job items, it returns the id of the last element only.
I have a key passed to the mapped list:
let activeListings = jobs.filter((job) => !job.isArchived);
activeListings?.map((job) => {
return (
<div key={job._id} className="bg-white w-8/12 py-4 my-4">
<Job
job={job}
setJob={setJobs}
handleShowDetailsToggle={handleShowDetailsToggle}
archiveToggler={archiveToggler}
setShowingResumeModal={setShowingResumeModal}
setShowingCoverLetterModal={setShowingCoverLetterModal}
showingResumeModal={showingResumeModal}
showingCoverLetterModal={showingCoverLetterModal}
getJobs={getJobs}
key={job._id}
/>
</div>
);
})
In the Job component, there is a button that when clicked opens a modal to select a file to upload and a preview.
Regardless of which Job component the Upload component is called from, it always gets passed the job._id from the final Job component listed.
Here is the resume modal component that opens on the button click:
import React, { useState } from "react";
import Upload from "../../Upload";
const AddNewResumeModal = (props) => {
const { setShowingResumeModal, job, getJobs, id } = props;
const [previewSource, setPreviewSource] = useState("");
return (
<div className="resume-modal-background">
<div className="resume-modal-container">
<button
className="close-modal-btn"
onClick={() => setShowingResumeModal(false)}
>
×
</button>
<h1 className="resume-modal-title">Upload Resume</h1>
<button onClick={() => console.log(job._id)}>Test2</button>
<Upload
resume={true}
setShowingResumeModal={setShowingResumeModal}
job={job}
getJobs={getJobs}
previewSource={previewSource}
setPreviewSource={setPreviewSource}
/>
<div></div>
</div>
</div>
);
};
export default AddNewResumeModal;
and the Upload component it renders:
import React, { useState } from "react";
import { Viewer } from "#react-pdf-viewer/core";
import "#react-pdf-viewer/core/lib/styles/index.css";
const URL = "http://localhost:8000";
const Upload = (props) => {
const {
setShowingResumeModal,
job,
getJobs,
previewSource,
setPreviewSource,
} = props;
const [fileInputState, setFileInputState] = useState("");
// const [previewSource, setPreviewSource] = useState("");
const handleFileInputChange = (e) => {
const file = e.target.files[0];
console.log(file);
previewFile(file);
};
const previewFile = (file) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onloadend = () => {
setPreviewSource(reader.result);
};
};
const handleSubmitFile = (e) => {
e.preventDefault();
if (!previewSource) return;
uploadFile(previewSource, job._id);
getJobs();
setShowingResumeModal(false);
};
const uploadFile = async (base64encodedFile, id) => {
console.log(base64encodedFile);
console.log(id);
try {
await fetch(`${URL}/upload/${id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ data: base64encodedFile }),
});
} catch (error) {
console.error(error);
}
};
return (
<div>
<form>
<input
type="file"
name="pdf"
id="pdf"
accept=".pdf"
onChange={handleFileInputChange}
value={fileInputState}
className="form-input"
onClick={() => console.log(job._id)}
/>
</form>
<div className="preview-container">
{previewSource ? (
<div className="viewer-container">
<Viewer fileUrl={previewSource} />
</div>
) : (
<div className="no-preview-source">Preview area</div>
)}
</div>
<button
onClick={handleSubmitFile}
type="submit"
className="upload-submit-btn"
>
Upload Resume
</button>
</div>
);
};
export default Upload;
Please help me understand how to correctly pair each job listing so that the correct id of the job is passed to the backend.
I have the following React component:
import React from "react";
import { useState, useEffect } from "react";
import { TailSpin } from "react-loader-spinner";
function Pokemon({ name, url }) {
const [data, setData] = useState(null);
useEffect(() => {
fetch(url)
.then((r) => r.json())
.then(setData);
}, [url]);
const onClickButtonChange = () => {
let cardMore = document.querySelector(".card_more");
let cardMain = document.querySelector(".card_main");
cardMore.style.display = "block";
cardMain.style.display = "none";
};
return (
<div>
{data ? (
<div>
<div className="card card_main">
<div className="animate__animated animate__bounceInUp">
<div className="card-image">
<img src={data.sprites.front_default} alt="pokemon_img" />
<span className="card-title">{name}</span>
<button onClick={onClickButtonChange}>More</button>
</div>
<div className="card-content">
{data.abilities.map((n, index) => (
<p key={index}>{n.ability.name}</p>
))}
</div>
</div>
</div>
<div className="card card_more">
<p>{data.height}</p>
<p>{data.weight}</p>
</div>
</div>
) : (
<div>
<TailSpin type="Puff" color="purple" height={100} width={100} />
</div>
)}
</div>
);
}
export { Pokemon };
My implementation of the More button needs to display additional features (the card_more block). Right now this function only works on the very first element. I understand that in React this can most likely be done more correctly, but I don’t know how, so I use CSS styles.
P.S Edited:
I tried to use React features, maybe someone can tell me or does it make sense?
import React from "react";
import { useState, useEffect } from "react";
import { TailSpin } from "react-loader-spinner";
function Pokemon({ name, url }) {
const [data, setData] = useState(null);
const [show, setShow] = useState(false);
useEffect(() => {
fetch(url)
.then((r) => r.json())
.then(setData);
}, [url]);
const handleMore = async () => {
if (show === true) {
setShow(false);
} else if (show === false || !data) {
const r = await fetch(url);
const newData = await r.json();
setData(newData);
setShow(true);
}
};
return (
<div>
{data && show ? (
<div>
<div className="card card_main">
<div className="animate__animated animate__bounceInUp">
<div className="card-image">
<img src={data.sprites.front_default} alt="pokemon_img" />
<span className="card-title">{name}</span>
</div>
<div className="card-content">
{data.abilities.map((n, index) => (
<p key={index}>{n.ability.name}</p>
))}
</div>
</div>
<button onClick={handleMore}>More</button>
</div>
<div className="card card_more">
<p>{data.height}</p>
<p>{data.weight}</p>
</div>
</div>
) : (
<div>
<TailSpin type="Puff" color="purple" height={100} width={100} />
</div>
)}
</div>
);
}
export { Pokemon };
Youre right, this isn't the way you should do it in React. But your problem in your onClickButtonChange-Function is that youre only getting one element with document.querySelector(".card_more") and everytime you call it you get the same element back (No matter on which card you call it)
What you need to do is: Identify the single component elements. Thats most likely solved by passing a id/key value down via props and then putting this id on a parent-element (e.g. div.card) and you give it an id:
<div className="card card_main" id={props.keyvalue}>
....
</div>
And then in your onClickButtonChange-Function you call:
let cardMore = document.querySelector(`#${props.keyvalue} .card_more`);
...
This should give you the right element.
I want to transfer a variable from search component as "child" to API component as "Parent". I did it with event for another part of codes but I don't know how did work exactly for a variable on my codes.
I want send "apiUrl" to parent. When user click on "current" get new location and generate new apiUrl, then send it to parent component for other stuff
Child:
import React from "react";
import "./Search.css";
const Search = function (props) {
const handleChange = (event) => {
props.onchange(event.target.value);
};
const handleSubmit = (event) => {
event.preventDefault();
props.onsubmit(event.target.value);
};
const navigation = (event) => {
event.preventDefault();
navigator.geolocation.getCurrentPosition(showPosition);
};
const showPosition = (position) => {
let latitude = position.coords.latitude;
let longitude = position.coords.longitude;
let latandlon = `lat=${latitude}&lon=${longitude}`;
let apiKey = "23422500afd990f6bd64b60f46cf509a";
let unit = "metric";
let apiUrl = `https://api.openweathermap.org/data/2.5/weather?${latandlon}&appid=${apiKey}&units=${unit}
`;
};
return (
<form className="form" onSubmit={handleSubmit}>
<div className="input-group">
<input
type="search"
className="form-control me-1"
placeholder="Enter City Name"
aria-label="City Name"
aria-describedby="basic-addon2"
onChange={handleChange}
/>
<div className="input-group-append">
<button className="btn btn-outline-secondary me-1" type="submit">
Search
</button>
<button
className="btn btn-outline-secondary me-1"
type="button"
onClick={navigation}
>
Current
</button>
</div>
</div>
</form>
);
};
export default Search;
Parent:
import React, { useState, useEffect } from "react";
import axios from "axios";
import Search from "./Search";
import ShowCurrentLocation from "./ShowCurrentLocation";
import HumidityAndWind from "./HumidityAndWind";
import CurrentStatus from "./CurrentStatus";
import ShowCurrentDay from "./ShowCurrentDay";
import CurrentDegree from "./CurrentDegree";
const Api = function (props) {
let [searchcity, setSearchcity] = useState("Tehran");
const [value, setValue] = useState("");
const [loader, setLoader] = useState(false);
const [weatherdata, setWeatherdata] = useState("");
const onchange = (data) => {
setValue(data);
};
const onsubmit = () => {
setSearchcity(value);
searchcity = value;
callApi();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(callApi, []);
function callApi() {
const apiKey = "23422500afd990f6bd64b60f46cf509a";
let units = "metric";
let apiUrl = `https://api.openweathermap.org/data/2.5/weather?q=${searchcity}&appid=${apiKey}&units=${units}`;
return axios.get(apiUrl).then(getWeatherData);
}
function getWeatherData(response) {
setWeatherdata({
temprature: Math.round(response.data.main.temp),
humidity: response.data.main.humidity,
wind: response.data.wind.speed,
description: response.data.weather[0].description,
city: response.data.name,
country: response.data.sys.country,
});
setLoader(true);
}
if (loader) {
return (
<div>
<div className="row">
<div className="col-md-9">
<Search
data1={searchcity}
onsubmit={(event) => {
onsubmit(event);
}}
data2={value}
onchange={(event) => {
onchange(event);
}}
/>
</div>
<div className="col-md-3 my-auto text-center">
<ShowCurrentLocation
data1={weatherdata.city}
data2={weatherdata.country}
/>
</div>
</div>
<div className="row my-auto">
<div className="col-md-7 my-auto">
<div className="row ">
<div className="col-6 my-auto text-start">
<div>
<HumidityAndWind
data1={weatherdata.humidity}
data2={weatherdata.wind}
/>
</div>
</div>
<div className="col-6 my-auto text-center">
<div>
<ShowCurrentDay />
</div>
</div>
</div>
</div>
<div className="col-md-5 my-auto">
<div className="row">
<div className="col-6 my-auto text-center">
<div>
<CurrentStatus data={weatherdata.description} />
</div>
</div>
<div className="col-6 my-auto text-center">
<CurrentDegree data={weatherdata.temprature} />
</div>
</div>
</div>
</div>
</div>
);
} else {
return "Loader";
}
};
export default Api;
You can't pass data or variable from your children to parent. But you can create some function from parent and pass it into a child that you want and the function is receive parameter for passing your data from children to parent. You can use useState to if you want, and pass that into your children component.
Example using useState:
ParentComponent
import { useState } from "react";
import ChildrenComponent from "./ChildrenComponent";
const ParentComponent = () => {
const [dataFromChild, setDataFromChild] = useState("");
console.log(dataFromChild);
return (
<div>
<ChildrenComponent setDataFromChild={setDataFromChild} />
</div>
);
};
export default ParentComponent;
ChildrenComponent
import { useState } from "react";
const ChildrenComponent = ({ setDataFromChild }) => {
const [data, setData] = useState("");
const handleChange = (e) => {
setData(e.target.value);
};
setDataFromChild(data);
return (
<div>
<label htmlFor="data"></label>
<input type="text" id="data" name="data" onChange={handleChange} />
<span>Its Data: </span>
<span>{data}</span>
</div>
);
};
export default ChildrenComponent;
so in above example we can access data on children component on parent component through function setDataFromChild using useState. Whenever the data onchildren change, the parent dataFromParent should be change to.
I am creating a page to update product details on an e-commerce site I am building using NextJS, and I have the image upload section nested inside an accordion on the individual item page. Once images have been uploaded, I would like to clear the upload form and close the accordion. It is closing the accordion I am having trouble with.
ImageUploadAccordion.js:
import React, {useRef} from 'react';
import {Accordion} from 'react-bootstrap'
import ImageUpload from './ImageUpload'
export default function ImageUploadAccordion({ item }) {
const accordionRef = useRef(null);
const toggleAccordion = () => {
accordionRef.current.click();
}
return (
<Accordion ref={accordionRef} defaultActiveKey="0">
<Accordion.Item eventKey="1">
<Accordion.Header>
<span className="btn btn-outline-success">Upload Images</span>
</Accordion.Header>
<Accordion.Body>
<ImageUpload
toggle={toggleAccordion}
item={item}
/>
</Accordion.Body>
</Accordion.Item>
</Accordion>
)
}
ImageUpload.js:
import React, {useState} from 'react';
import { useRouter } from 'next/router'
export default function ImageUpload({ item, toggle }) {
const router = useRouter()
const [images, setImages] = useState([])
const [imageURLS, setImageURLS] = useState([])
const [tags, setTags] = useState([])
const [theInputKey, setTheInputKey] = useState('')
const uploadImageToClient = (event) => {
if (event.target.files && event.target.files[0]) {
setImages((imageList) => [...imageList, {"index": images.length, "data": event.target.files[0]}]);
setImageURLS((urlList) => [
...urlList,
URL.createObjectURL(event.target.files[0])
]);
}
let randomString = Math.random().toString(36);
setTheInputKey(randomString)
};
const uploadTagToClient = (e) => {
if (event.target.value) {
const name = e.target.getAttribute("name")
// const i = event.target.value;
// document.getElementById("image-upload")
setTags((tagList) => [...tagList, {"name": name, "tag": e.target.value}]);
}
};
const removeImage = (name) => {
// debug
alert(`Trying to remove image index ${name}`)
let newImages = []
let newTags = []
setImages(images.filter(image => image.data.name !== name));
setTags(tags.filter(tag => tag.name !== name));
}
const uploadToServer = async (e) => {
const body = new FormData()
images.map((file, index) => {
body.append(`file${index}`, file.data);
});
// Use the filenames as keys then we can retrieve server side once we have the images
tags.map((tag, index) => {
body.append(tag.name, tag.tag)
})
const response = await fetch("/api/file", {
method: "POST",
"Content-Type": "multipart/form-data",
body
})
var message = await response.json();
alert(message['message'])
setImages([])
setTags([])
toggle()
};
const openImageUploadDialogue = () =>{
document.getElementById("image-upload").click()
}
return (
<div className="container">
<input style={{display:'none'}} accept="image/*" id="image-upload" type="file" key={theInputKey || '' } className="btn btn-outline-success-inverse" onChange={uploadImageToClient} />
<button className="btn btn-outline-success-inverse" onClick={openImageUploadDialogue} >
Add Image
</button>
<hr className = "text-pink"/>
<div className="row">
<div className="col d-flex flex-wrap">
{images.map((file, index) => {
return (
<div className="div p-1" key={file.data.name}>
<p className="text-pink">{file.data.name}</p>
<p>Tag</p>
<input type="text" name={file.data.name} id={`${file.data.name}`} onChange={uploadTagToClient} />
<img src={imageURLS[index]} height="200" width="150" />
<div className="btn btn-outline-success-inverse" onClick={ () =>removeImage(file.data.name)}>Remove Image</div>
</div>
);
})}
</div>
<button
className="btn btn-outline-success-inverse"
type="submit"
onClick={uploadToServer}
>
Upload Images
</button>
</div>
</div>
);
}
I tried by creating a reference to the accordion using useRef, and a function which uses this reference to activate the click event, which I passed to the ImageUpload component, according to another answer to a similar question, but it doesn't seem to work and I'm unsure as to why.
Any help always appreciated :-)
I believe you have the wrong target as the ref, update it to target the button that is automatically generated to wrap the header content.
<h2 class="accordion-header"><button type="button" aria-expanded="true" class="accordion-button"><span class="btn btn-outline-success">Upload Images</span></button></h2>
Rudimentary example:
export default function ImageUploadAccordion({ item }) {
const accordionRef = useRef(null);
const toggleAccordion = () => {
accordionRef.current.querySelector('button').click();
}
return (
<Accordion defaultActiveKey="0">
<Accordion.Item eventKey="1">
<Accordion.Header ref={accordionRef}>
<span className="btn btn-outline-success">Upload Images</span>
</Accordion.Header>
<Accordion.Body>
<ImageUpload
toggle={toggleAccordion}
item={item}
/>
</Accordion.Body>
</Accordion.Item>
</Accordion>
)
}
I am generating a list of components on the screen like so:
const MessagesContainer = ({ messages, categories, addHandler }) => {
const options = categories.map(category => (
{ value: category.name, label: category.name }
));
return (
<div className="d-flex flex-wrap justify-content-center">
{messages.map(message =>
<div key={message.id}>
<MessageEditor
message={message}
options={options}
addHandler={addHandler}
/>
</div>
)}
</div>
);
};
const MessageEditor = ({ message, options, addHandler }) => {
const [modifedMessage, setModifiedMessage] = useState(message);
const [isAdded, setIsAdded] = useState(false);
const textClass = (charLimit - modifedMessage.text.length) > 0 ?
'text-success' : 'text-danger';
const buttonClass = isAdded ? 'danger' : 'primary';
const ref = useRef(null);
const textAreaHandler = textArea => {
const copiedMessage = { ...modifedMessage };
copiedMessage.text = textArea.target.value;
setModifiedMessage(copiedMessage);
};
const addButtonHandler = () => {
const add = !isAdded;
setIsAdded(add);
let selectedCategoires = ref.current.state.value;
// Firing this handler results in ALL the MessageEditor
// componets on the screen being re-rendered
addHandler(modifedMessage, add, selectedCategoires);
}
return (
<div className="d-flex flex-column message-view-container ml-5 mr-5 mb-5">
<div className={`message-count-container ${textClass}`}>
{charLimit - modifedMessage.text.length}
</div>
<Select
ref={ref}
placeholder="Tags"
isMulti
name="tags"
options={options}
defaultValue={[options[0]]}
className="basic-multi-select select-container"
classNamePrefix="select"
isDisabled={isAdded}
/>
<Form.Control
style={{
width:350,
height:220,
resize:'none'
}}
className="mb-1"
as="textarea"
defaultValue={message.text}
onChange={textAreaHandler}
disabled={isAdded}
/>
<Button variant={buttonClass} onClick={addButtonHandler}>
{isAdded ? 'Remove' : 'Add'}
</Button>
</div>
);
};
And the parent component that holds the addHandler:
const { useState } = require("react");
const Messages = () => {
const [messages, setMessages] = useState([]);
const [saveMessages, setSaveMessages] = useState({});
const addHandler = (modifiedMessage, add, selectedCategoires) => {
const copiedSaveMessages = { ...saveMessages };
if (add) {
if (selectedCategoires) {
selectedCategoires = selectedCategoires.map(item => item.value);
}
copiedSaveMessages[modifiedMessage.id] = {
text: modifiedMessage.text,
tags: selectedCategoires ? selectedCategoires : []
}
} else {
delete copiedSaveMessages[modifiedMessage.id];
}
// This results in every single MessageEditor component being
// re-rendered
setSaveMessages(copiedSaveMessages);
};
return (
<div>
{categories &&
<div>
<div className="ml-5 mr-5 mt-5">
<MessagesContainer
messages={messages}
categories={categories}
addHandler={addHandler}
/>
</div>
</div>
}
{Object.keys(saveMessages).length > 0 &&
<div>
<Image
className="upload-icon"
src={uploadIcon}
/>
<div className="text-primary count-container">
<h2>{Object.keys(saveMessages).length}</h2>
</div>
</div>
}
</div>
);
};
The issue is that if I hit the add button an trigger addHandler it causes all the MessageEditor components to re-render. And the performance is very slow if I have a few hundred components on the screen.
I guess this is because the saveMessages state variable belongs to the Messages component and MessageEditor is a child of Messages so it also re-renders.
Is there an approach I can take to update this state without causing all the other components to re-render?
In Messages you should wrap your addHandler in a useCallback hook (React useCallback hook) so that it is not re-created at each render.
const addHandler = useCallback((modifiedMessage, add, selectedCategoires) => {
// function body...
}, []);
Additionally, you can also memoize MessageEditor using React.memo() (React memo).
const MessageEditor = React.memo(({ message, options, addHandler }) => {
// component body...
});