Since I am pretty new to react hooks, I am unable to understand some part of the existing code why my component is re-rendering multiple times when the state of an attribute gets changed. Below is the component code. I have added console.log for better understanding.
import React, { useState, useRef } from 'react';
import api from '../api/api';
import { UPLOAD_DATA } from '../api/urls';
import Alert from '../layout/alerts/Alerts';
const StudentDetailsView = ({ symbol }) => {
console.log("inside StudentDetailsView");
const initialState = {
form: {
qualification: symbol.qualification,
profession: symbol.profession
}
};
const [loading, setLoading] = useState(false);
const [errors, setErrors] = useState(null);
const [editFlag, setEditFlag] = useState(false);
const [inputs, setInputs] = useState(initialState);
console.log("before dataUpdated");
const [dataUpdated, setDataUpdated] =useState(false);
console.log("after dataUpdated");
const formRef = useRef(null);
const handleCancel = () => {
setEditFlag(false);
setInputs(initialState);
};
const handleSubmit = (e) => {
console.log("inside handleSumbit");
const form = formRef.current;
e.preventDefault();
e.stopPropagation();
form.classList.add('was-validated');
if (form.checkValidity()) {
callback();
}
};
const callback = ()=> {
setLoading(true);
const formData = new FormData();
formData.append('model', new Blob([JSON.stringify(inputs.form)], {
type: 'application/json'
}));
api.multipartEdit(UPLOAD_DATA, formData)
.then(response => {
setInputs(inputs => ({
...inputs,
form: {
qualification: response.data.qualification,
profession: response.data.profession
}
}));
setErrors(null);
setDataUpdated(true);
})
.catch(error => {
setErrors(error);
})
.finally(() => {
setLoading(false);
setEditFlag(false);
});
}
const handleInputChange = (event) => {
event.persist();
setInputs(inputs => ({
...inputs,
form: {
...inputs.form,
[event.target.name]: event.target.value
}
}));
}
return (
<div>
{
errors &&
<Alert type={errors.type} title={errors.title} description={errors.description} id="alert" />
}
<div >
{editFlag ? (
<div >
</div>
) :
(<div className="edit">
<button type="button" onClick={() => setEditFlag(!editFlag)}>
Edit
</button>
</div>)
}
</div>
<div>
<form className="needs-validation" onSubmit={handleSubmit} ref={formRef} noValidate>
{
editFlag ? (<div className="update-cancel-button">
<button className="btn btn-primary" type="submit" >
{loading ? (
<div className="spinner-border uxf-spinner-border-sm">
<span className="sr-only">Loading...</span>
</div>) : 'Update'}
</button>
<button className="btn btn-secondary cancel-button" type="button" onClick={handleCancel}>Cancel</button>
</div>) : <div></div>
}
<dl className="uxf-dl uxf-dl-horizontal">
<dt>Name</dt>
<dd>{symbol.name}</dd>
<dt>Age</dt>
<dd>{symbol.age}</dd>
<dt>Qualification</dt>
{editFlag ?
(<dd>
<textarea className="form-control" name="qualification" id="qualification"
value={inputs.form.qualification}
onChange={handleInputChange}
maxLength="255"></textarea>
<div className="invalid-feedback">
Please provide a Qualification.
</div>
</dd>)
:
(<dd>{dataUpdated ? (inputs.form.qualification ? inputs.form.qualification : '-') : (symbol.qualification ? symbol.qualification : '-')}</dd>)
}
<dt>Profession</dt>
{editFlag ?
(<dd>
<textarea className="form-control" name="profession" id="profession"
value={inputs.form.profession}
onChange={handleInputChange}
minLength="1"
maxLength="1000"
required></textarea>
<div className="invalid-feedback">
Please provide a Profession.
</div>
</dd>)
:
(<dd>{dataUpdated ? inputs.form.profession : symbol.profession}</dd>)
}
</dl>
</form>
</div>
</div>
);
}
export default StudentDetailsView;
Since my component is getting re-rendered, my state values which are getting set in the code (eg, dataUpdated) are getting updated with the default value again and again. How do I prevent this from happening? Please see the below images for better understanding.
(Showing the mockup of the edit component as the actual data was showing in the actual edit component)
I have clicked the edit button once and then clicked the cancel button once and this is the console log got generated.
Please uase spread operators on initializing or settingup states
const [inputs, setInputs] = useState({...initialState});
const handleCancel = () => {
setEditFlag(false);
setInputs({...initialState});
};
Related
I am working through a tutorial for a course I'm taking. The lab I'm working on walks through creating a to-do app. I'm on step 3, which asks us to create a button that deletes a task. I feel ridiculous, because I know I can figure it out but...well, I haven't yet! I will post the code to see if there are any initial issues, and then update with the methods I've already tried. Any help is greatly appreciated. Thank you!
import React, { useState } from "react";
import "./App.css";
const App = () => {
const [todos, setTodos] = useState([]);
const [todo, setTodo] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
const newTodo = {
id: new Date().getTime(),
text: todo.trim(),
completed: false,
};
if (newTodo.text.length > 0) {
setTodos([...todos].concat(newTodo));
setTodo("");
} else {
alert("Enter Valid Task");
setTodo("");
}
}
const deleteTodo = (id) => {
let updatedTodos = [...todos].filter((todo) => todo.id !== id);
setTodos(updatedTodos);
}
const button = <button onClick={() => deleteTodo(todo.id)}>Delete</button>
return (
<div>
<h1>To-do List</h1>
<form onSubmit={handleSubmit}>
<input
type="text"
onChange={(e) => setTodo(e.target.value)}
placeholder="Add a new task"
value={todo}
/>
<button type="submit">Add Todo</button>
</form>
{todos.map((todo) => <div>ID: {todo.id} Task: {todo.text} {button}</div>)}
</div>
);
};
export default App;
I didn't just copy and paste, so it's possible that I messed something up while typing. I'm expecting the deleteTodo() function to accept a todo.id and filter the list of todos, excluding the one I want to delete. I'm thinking that the issue may be cause by the way I've created the button? Again, I'm not sure why I can't figure it out. TIA.
EDIT: Okay, it works now! Thank you all so much for explaining this. For anyone else that comes across this problem, here's where I mis-stepped:
const button = <button onClick={() => deleteTodo(todo.id)}Delete<button>
#Nicholas Tower's explanation was very clear--creating this outside of .map(...)causes deleteTodo to get the todo state, not the not the todo I want it to delete from the todos array. #Lars Vonk, #0stone0, and #Sudip Shrestha all said this as well. #Sudip Shrestha and #pilchard also helped correct the deleteTodo function. Again, I really appreciate all the help. The code works now. I'll show the updates so people having a similar issue can compare:
import React from "react";
import "./App.css";
const App = () => {
const [todos, setTodos] = React.useState([]);
const [todo, setTodo] = React.useState("");
const handleSubmit = (e) => {
e.preventDefault();
const newTodo = {
id: new Date().getTime(),
text: todo.trim(),
completed: false,
};
if (newTodo.text.length > 0) {
setTodos(todos.concat(newTodo));
setTodo("");
} else {
alert("Enter a valid task");
setTodo("");
}
}
// update the state using setState, rathar than mutating it directly #Sudip Shrestha
const deleteTodo = id => {
setTodos(prevState => {
return prevState.filter(todo => todo.id !== id)
});
};
// line 51: button placed inside .map(), as per many suggestions below.
return (
<>
<h1>Todo List</h1>
<form onSubmit={handleSubmit}>
<input
type="text"
onChange={(e) => setTodo(e.target.value)}
placeholder="Add a new task..."
value={todo}
/>
</form>
{todos.map((todo) =>
<div>
ID: {todo.id} Task: {todo.text}
<button onClick={() => deleteTodo(todo.id)}>Delete</button>
</div>)}
</>
);
};
export default App;
const button = <button onClick={() => deleteTodo(todo.id)}>Delete</button>
You're creating this button element just once, and the todo variable it refers to is the todo state, which is a string (usually an empty string). Since todo is a string, todo.id is undefined, and deleteTodo can't do anything with that.
You need to create separate buttons for each item, so you should move this code down into your .map:
{todos.map((todo) => (
<div>
ID: {todo.id} Task: {todo.text}
<button onClick={() => deleteTodo(todo.id)}>Delete</button>
</div>
))}
Now each item has its own button, with its own onClick function. And in those functions, todo is the item of the array.
The button cannot access which todo it has I think you should put the code from the const button where you are referring to it or by changing it to const button = (todo) => <button onClick={ () => deleteTodo(todo.id); }>Delete</button> and access it by doing {button()}
const button = <button onClick={() => deleteTodo(todo.id)}>Delete</button>
This has the same callBack for each todo, you should move this inside your map so that todo.id refers to the iterator of the map():
{todos.map((todo) => (
<React.Fragment>
<div>ID: {todo.id} Task: {todo.text}</div>
<button onClick={() => deleteTodo(todo.id)}>Delete</button>
</React.Fragment>
))}
Updated Demo:
const { useState } = React;
const App = () => {
const [todos, setTodos] = useState([]);
const [todo, setTodo] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
const newTodo = {
id: new Date().getTime(),
text: todo.trim(),
completed: false,
};
if (newTodo.text.length > 0) {
setTodos([...todos].concat(newTodo));
setTodo("");
} else {
alert("Enter Valid Task");
setTodo("");
}
}
const deleteTodo = (id) => {
let updatedTodos = [...todos].filter((todo) => todo.id !== id);
setTodos(updatedTodos);
}
return (
<div>
<h1>To-do List</h1>
<form onSubmit={handleSubmit}>
<input
type="text"
onChange={(e) => setTodo(e.target.value)}
placeholder="Add a new task"
value={todo}
/>
<button type="submit">Add Todo</button>
</form>
{todos.map((todo) => (
<React.Fragment>
<div>ID: {todo.id} Task: {todo.text}</div>
<button onClick={() => deleteTodo(todo.id)}>Delete</button>
</React.Fragment>
))}
</div>
);
};
ReactDOM.render(<App />, document.getElementById("react"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>
Try this:
const button = (t) => <button onClick={() => deleteTodo(t.id)}>Delete</button>
and then, in the map
{todos.map((todo) => <div>ID: {todo.id} Task: {todo.text} {button(todo)}</div>)}
this way, the "delete todo" button will be bound to the specific todo ID, avoiding being bound to whatever the current value of todo is in the app.
Its better to update the state using setState. Muting the state directly breaks the primary principle of React's data flow (which is made to be unidirectional), making your app very fragile and basically ignoring the whole component lifecycle.
Also You need to change the delete from string to function and pass the id or place the jsx directly inside map function.
import React, { useState } from 'react'
const App = () => {
const [todos, setTodos] = useState([])
const [todo, setTodo] = useState('')
const handleSubmit = e => {
e.preventDefault()
const newTodo = {
id: new Date().getTime(),
text: todo.trim(),
completed: false,
}
if (newTodo.text.length > 0) {
setTodos([...todos].concat(newTodo))
setTodo('')
} else {
alert('Enter Valid Task')
setTodo('')
}
}
/*
* Changed Here
*/
const deleteTodo = id => {
setTodos(prevState => {
return prevState.filter(todo => todo?.id != id)
})
}
const button = id => <button onClick={() =>
deleteTodo(id)}>Delete</button>
return (
<div>
<h1>To-do List</h1>
<form onSubmit={handleSubmit}>
<input
type="text"
onChange={e => setTodo(e.target.value)}
placeholder="Add a new task"
value={todo}
/>
<button type="submit">Add Todo</button>
</form>
{todos.map(todo => (
<div key={todo.id}>
ID: {todo.id} Task: {todo.text} {button(todo.id)}
</div>
))}
</div>
)
}
export default App
Problem in:
const button = <button onClick={() => deleteTodo(todo.id)}>Delete</button>
You can use
const Button = (props) => {
return (
<button
className={`btn ${props.className}`}
title={`${props.title}`}
onClick={props.onClick ? () => props.onClick() : null}
>
{props.children}
</button>
);
};
after that, call it like this
<Button className="delete" title="delete" onClick={()=>deleteTodo(todo.id)}>Delete</Button>
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'm trying to make an app where an 'Owner' can have multiple 'cars', I have my App.js file where the Owner enters there name and can enter there car details ('Car name' and 'Car Type'), a Owner can have multiple Cars, and when they click 'Add car entry' a Component where they enter there car details called 'OwnersCars' is repeated. Like so
to
If an owner fills out the input boxes in this component (For X amount of cars) then clicks 'Save Owner' i want the owner aswell as a list of all there cars to be saved into one State.
Currently i have my app.js file like this (count is used to know the number of OwnersCars divs)
import './App.css';
import React, {useState, useRef} from 'react';
import OwnersCars from './ownersCars';
function App() {
const [count, setCount] = useState(1)
const [OwnerInput, SetOwnerInput] = useState({
id: "",
Name: "",
cars: []
});
const [newCarInput, SetnewCarInput] = useState({
id: "",
Type: "",
CarName: ""
});
const removeDiv = () => {
//console.log('sw\nag')
setCount(count - 1)
}
const repeatDiv = () => {
//console.log('sw\nag')
setCount(count + 1)
}
const displayCarInput = (e) => {
//console.log(count, "<--key")
return ( ([...Array(count)].map((e, i) => <OwnersCars onAddNameCar={addNewCarNameHandler} onAddTypeCar={addNewCarTypeHandler}></OwnersCars> )))
}
const displayRemove = (e) =>{
if (count > 1) {
return (<button className='removeAnimalButton' onClick={removeDiv}> <dt> Remove Last Animal Entry</dt></button>)
}
}
const NameHandler = (e) => {
//console.log(e.target.value)
SetOwnerInput((prevState) => {
return { ...prevState, Name: e.target.value };
});
}
const submitHandler = (event) => {
event.preventDefault();
const value = Math.random().toString()
const OwnerData = {
id: value,
Name: OwnerInput.Name,
cars: [newCarInput]
};
console.log(OwnerData, "<--- ownerdata with cars data");
}
const addNewCarNameHandler = (values) => {
//console.log(values, "<---5")
SetnewCarInput((prevState) => {
return { ...prevState, CarName: values };
});
};
const addNewCarTypeHandler = (values) => {
//console.log(values, "<---5")
SetnewCarInput((prevState) => {
return { ...prevState, Type: values };
});
};
return (
<div>
<div>
<div>
<label for="exampleInputPassword1"></label>
<button onClick={submitHandler} ><dt>Save Owner</dt></button>
</div>
</div>
<hr/>
<div className="wrapper">
<div class="new-owner-div">
<h5>Owner</h5>
<hr/>
<form>
<div>
<input type="name" id="exampleInputClinic" placeholder="Owner Name" onChange={NameHandler}/>
</div>
</form>
</div>
<div class="new-owner-div-2">
<h5>Owners Cars</h5>
<hr/>
{displayCarInput()}
<div>
<button onClick={repeatDiv}> <dt> Add Car Entry</dt></button>
{displayRemove()}
</div>
</div>
</div>
</div>
);
}
export default App;
and i have my ownersCars.js file with the OwnersCars component like this
import React, {useState, useRef} from 'react';
function OwnersCars(props) {
const CarNameHandler = (e) => {
console.log(e.target.value)
props.onAddNameCar(e.target.value)
}
const CarTypeHandler = (e) => {
console.log(e.target.value)
props.onAddTypeCar(e.target.value)
}
return(
<div>
<div>
<div>
<h3>Car name</h3>
<span></span>
<h3>Type</h3>
</div>
<div>
<div>
<input placeholder="Car Name" onChange={CarNameHandler}/>
</div>
<span class="span1-box"></span>
<div class="height">
<input class="input-box-OA-2" placeholder="Car Type" onChange={CarTypeHandler}/>
</div>
<span class="span1-box"></span>
</div>
</div>
</div>
)
}
export default OwnersCars
but when i click save user it only saves the latest car entry!
Would anyone be able to help?
Sorry for the mess and lack of css i removed a bunch of things from the original code so it was easier to follow on StackOverflow. Also im fairly new to react so im sure theres alot of things that need to be changed for this to work.
You need to push to owner cars, every time you add a new car. Please find the code below for App.js changes. check repeatDiv. Similarly, you need to pop from cars the particular car with remove div which I leave it to you
import React, { useState, useRef } from "react";
import OwnersCars from "./Owner";
function App() {
const [count, setCount] = useState(1);
const [OwnerInput, SetOwnerInput] = useState({
id: "",
Name: "",
cars: []
});
const [newCarInput, SetnewCarInput] = useState({
id: "",
Type: "",
CarName: ""
});
const removeDiv = () => {
//console.log('sw\nag')
setCount(count - 1);
};
const repeatDiv = () => {
//console.log('sw\nag')
OwnerInput.cars.push(newCarInput);
setCount(count + 1);
};
const displayCarInput = (e) => {
//console.log(count, "<--key")
return [...Array(count)].map((e, i) => (
<OwnersCars
onAddNameCar={addNewCarNameHandler}
onAddTypeCar={addNewCarTypeHandler}
></OwnersCars>
));
};
const displayRemove = (e) => {
if (count > 1) {
return (
<button className="removeAnimalButton" onClick={removeDiv}>
{" "}
<dt> Remove Last Animal Entry</dt>
</button>
);
}
};
const NameHandler = (e) => {
//console.log(e.target.value)
SetOwnerInput((prevState) => {
return { ...prevState, Name: e.target.value };
});
};
const submitHandler = (event) => {
event.preventDefault();
const value = Math.random().toString();
const OwnerData = {
id: value,
Name: OwnerInput.Name,
cars: OwnerInput.cars
};
console.log(OwnerData, "<--- ownerdata with cars data");
};
const addNewCarNameHandler = (values) => {
//console.log(values, "<---5")
SetnewCarInput((prevState) => {
return { ...prevState, CarName: values };
});
};
const addNewCarTypeHandler = (values) => {
//console.log(values, "<---5")
SetnewCarInput((prevState) => {
return { ...prevState, Type: values };
});
};
return (
<div>
<div>
<div>
<label for="exampleInputPassword1"></label>
<button onClick={submitHandler}>
<dt>Save Owner</dt>
</button>
</div>
</div>
<hr />
<div className="wrapper">
<div class="new-owner-div">
<h5>Owner</h5>
<hr />
<form>
<div>
<input
type="name"
id="exampleInputClinic"
placeholder="Owner Name"
onChange={NameHandler}
/>
</div>
</form>
</div>
<div class="new-owner-div-2">
<h5>Owners Cars</h5>
<hr />
{displayCarInput()}
<div>
<button onClick={repeatDiv}>
{" "}
<dt> Add Car Entry</dt>
</button>
{displayRemove()}
</div>
</div>
</div>
</div>
);
}
export default App;
And the output with cars saved
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'm starting studying React and I was following this YouTube tutorial of a TO DO LIST using React.
https://www.youtube.com/watch?v=E1E08i2UJGI
My form loads perfectly, but if I write something and press any button I get the message: "completedTask is not a function". The same goes for buttons that call a function 'removeTask' and 'setEdit'.
I don't understand the reason I'm getting such error message. In the tutorial it works when the buttons are clicked. I've read in some forums that it would be related to the fact that you can't use map on Objects (non-array elements), but I didn't understand it very well and I don't know how to fix it. And the most mysterious parte: why does his code work and mine do not?
Could anybody please explain it?
Obs1: I found in another post that return tasks.tasks.map((task, index) solved the problem for 'task.map is not a function' error message in TASK.JS. I'm using it instead of return tasks.map((task, index) but I also didn't understant the reason.
Obs2: I don't think it makes any difference for the error message, but I used the button tag instead using React Icons as the video suggests.
=== TASKLIST.JS ===
import React, { useState } from 'react'
import Task from './Task'
import TaskForm from './TaskForm'
function TaskList() {
const [tasks, setTasks] = useState([]);
const addTask = task => {
if(!task.text || /^\s*$/.test(task.text)) {
return
}
const newTasks = [task, ...tasks];
setTasks(newTasks);
};
const updateTask = (taskId, newValue) => {
if(!newValue.text || /^\s*$/.test(newValue.text)) {
return
}
setTasks(prev => prev.map(item => (item.id === taskId ? newValue : item)));
};
const removeTask = id => {
const removeArr = [...tasks].filter(task => task.id !== id);
setTasks(removeArr)
};
const completedTask = id => {
let updatedTasks = tasks.map(task => {
if (task.id === id) {
task.isComplete = !task.isComplete
}
return task
})
setTasks(updatedTasks);
};
return (
<div>
<h1>Cabeçalho</h1>
<TaskForm onSubmit={addTask}/>
<Task
tasks={tasks}
completedTask={completedTask}
removeTask={removeTask}
updateTask={updateTask} />
</div>
)
}
export default TaskList
=== TASK.JS ===
import React, { useState } from 'react'
import TaskForm from './TaskForm'
function Task(tasks, completedTask, removeTask, updateTask) {
const [edit, setEdit] = useState({
id: null,
value: ''
})
const submitUpdate = value => {
updateTask(edit.id, value)
setEdit({
id: null,
value: ''
})
}
if (edit.id) {
return <TaskForm edit={edit} onSubmit={submitUpdate} />;
}
return tasks.tasks.map((task, index) => (
<div className={task.isComplete ? 'task-row complete' : 'task-row'} key={index}>
{task.text}
<div className="buttons">
<button onClick={() => completedTask(task.id)} className='completed-icon'>done</button>
<button onClick={() => removeTask(task.id)} className='delete-icon'>delete</button>
<button onClick={() => setEdit({id: task.id, value: task.text})} className='edit-icon'>edit</button>
</div>
</div>
))
};
export default Task
=== TASKFORM.JS ===
import React, { useState, useEffect, useRef } from 'react'
function TaskForm(props) {
const [input, setInput] = useState(props.edit ? props.edit.value : '');
const inputRef = useRef(null);
useEffect(() => {
inputRef.current.focus()
})
const handleChange = e => {
setInput(e.target.value);
}
const handleSubmit = e => {
e.preventDefault();
props.onSubmit({
id: Math.floor(Math.random() * 1000),
text: input
});
setInput('');
};
return (
<form className="task-form" onSubmit={handleSubmit}>
{props.edit ? (
<>
<input type="text" placeholder="Update your task" value={input} name="text" className="task-input" onChange={handleChange} ref={inputRef}/>
<button className="task-button edit" onChange={handleChange}>Update a task</button>
</>
) : (
<>
<input type="text" placeholder="Add a new task" value={input} name="text" className="task-input" onChange={handleChange} ref={inputRef}/>
<button className="task-button" onChange={handleChange}>Add a task</button>
</>
)}
</form>
)
}
export default TaskForm
Try this:
function Task({ tasks, completedTask, removeTask, updateTask }) {
// ...
}
You can also do this (semantically equivalent):
function Task(props) {
const { tasks, completedTask, removeTask, updateTask } = props;
// ...
}
As mentioned here:
The first parameter will be props object itself. You need to destructure the object.
You can read more about object destructuring here.