In this small code that I've written I have created a dynamic object upon setting state and adding files to it. However, the new file overwrites the previous file. With no luck, I have tried using the spread operator inside the array brackets next to mappedFiles. It seems upon setting the state dynamically, I am not able to concatenate or push to the array inside the files object.
Here is the code...
import React, { useCallback, useState, useContext } from "react";
import { ImageUploadContext } from "../../Store";
import styles from "./commentssection.module.css";
import { useDropzone } from "react-dropzone";
function ImageUploader({ title }) {
const [files, setFiles] = useContext(ImageUploadContext);
const maxSize = 5048576;
//ISSUE IS HERE. CREATING THE SETSTATE INSIDE THIS CALLBACK
const onDrop = useCallback(
(acceptedFiles) => {
const mappedFiles = acceptedFiles.map((file) =>
Object.assign(file, {
preview: URL.createObjectURL(file),
})
);
// This setstate function adds to dynamic array but doesn't return previous one. The array is being overwritten
setFiles((state) => ({ ...state, [title]: [mappedFiles] }));
},
[files]
);
const {
isDragActive,
getRootProps,
getInputProps,
isDragReject,
acceptedFiles,
rejectedFiles,
} = useDropzone({
onDrop,
accept: "image/*",
minSize: 0,
maxSize: 10000000,
});
console.log(files);
const isFileTooLarge = rejectedFiles
? rejectedFiles.length > 0 && rejectedFiles[0].size > maxSize
: null;
return (
<div>
<p>Please include comments in notes</p>
<hr className={styles["HR"]} />
<form className={styles["UploadForm"]}>
<div className={styles["UploadWrapper"]}>
<h5>Upload photos of issues found in the {title}</h5>
<section className={styles["Container"]}>
<div className={styles["ImageInput"]} {...getRootProps()}>
<input {...getInputProps()} />
{!isDragActive && "Click here or drop a file to upload!"}
{isDragActive && !isDragReject && "Drop it like it's hot!"}
{isDragReject && "File type not accepted, sorry!"}
{isFileTooLarge && (
<div className="text-danger mt-2">File is too large.</div>
)}
</div>
</section>
<div>
{files[title]
? files[title].map((object, index) =>
object.map((subObject, subIndex) => {
return (
<img
style={{ height: "80px" }}
className={styles["RenderedImage"]}
key={index}
src={subObject.preview}
/>
);
})
)
: null}
</div>
<p>
Please take a picture of any issues that you find and upload them
here. NOTE: it is only necessary to upload pictures of problems that
you find.
</p>
</div>
<div className={styles["CommentWrapper"]}>
<h5>Notes of the {title}</h5>
<textarea className={styles["Textarea"]} />
</div>
</form>
</div>
);
}
export default ImageUploader;
Edit:
I was able to figure it out thanks to #lawrence-witt1 . Here is the code for the arrays to be parent component specific.
const onDrop = useCallback(
(acceptedFiles) => {
const mappedFiles = acceptedFiles.map((file) =>
Object.assign(file, {
preview: URL.createObjectURL(file),
})
);
return files[title]
? setFiles((state) => ({
...state,
[title]: [...state[title], mappedFiles],
}))
: setFiles((state) => ({
...state,
[title]: [mappedFiles],
}));
},
[files, title]
);
I think I spotted the issue - you need to include title in the dependency array of onDrop:
const onDrop = useCallback(
(acceptedFiles) => {
...
},
[files, title]
);
Otherwise you run the risk of having a stale value for title which would overwrite your state object's property.
Edit:
I think this is what you were looking for:
setFiles((state) => ({ ...state, [title]: [...state[title], ...mappedFiles]}));
If your state object with array looks like below
var std={students:{names:["AAA","BBB","CCC"]}}
State
const[students,setstudents]=useState(std)
setState
setstudents({ ...students, ...students.names.push("DDD") })
Related
So this is my code, testing out a chatbot. useEffect is not working when I refresh, the automatic scroll doesn't work when new message is being received or sent.. what am I missing?
import './App.css';
import './normal.css';
import { useState, useRef, useEffect } from 'react';
function App() {
const messagesEndRef = useRef(null);
const [input, setInput] = useState("");
const [chatLog, setChatLog] = useState([{
user: "gpt",
message: "Hello World"
}])
function clearChat(){
setChatLog([]);
}
useEffect(() => {
messagesEndRef.current.scrollIntoView({ behavior: "smooth" })
}, [chatLog]);
async function handleSubmit(e){
e.preventDefault();
let chatLogNew = [...chatLog, { user: "me", message: `${input}` } ]
await setInput("");
setChatLog(chatLogNew)
const messages = chatLogNew.map((message) => message.message).join("\n")
const response = await fetch("http://localhost:3080/", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
message: messages
})
});
const data = await response.json();
setChatLog([...chatLogNew, { user: "gpt", message: `${data.message}`}])
}
return (
<div className="App">
<aside className ="sidemenu">
<div className="title">
<h1>Title</h1>
</div>
<div className="side-menu-button" onClick={clearChat}>
<span>+</span>
New chat
</div>
</aside>
<section className="chatbox">
<div className="chat-log">
{chatLog.map((message, index) => (
<ChatMessage key={index} message={message} />
))}
</div>
<div ref={messagesEndRef} />
<div className="chat-input-holder">
<form onSubmit={handleSubmit}>
<input
rows="1"
value={input}
onChange={(e)=> setInput(e.target.value)}
className="chat-input-textarea">
</input>
</form>
</div>
</section>
</div>
);
}
const ChatMessage = ({ message }) => {
return (
<div className={`chat-message ${message.user === "gpt" && "chatgpt"}`}>
<div className="chat-message-center">
<div className={`avatar ${message.user === "gpt" && "chatgpt"}`}>
{message.user === "gpt" && "AI"}
{message.user === "me" && "Me"}
</div>
<div className="message">
{message.message}
</div>
</div>
</div>
)
}
export default App;
Defined the messagesEndRef and inserted the useEffect and put in the dummy div to the last rendered message.
Any ideas? Am I formatting it wrong?
EDIT:
It's working now but I have to have chat-log set to "overflow:scroll"
otherwise it doesn't kick in.. for example "overflow:auto" doesn't
work.
When it DOES work, it also doesn't scroll to the very end of the box
but slightly above.
Any solution to this?
Instead of using chatLog in the second argument array of useEffect(), use [JSON.stringify(chatLog)] or chatLog.length
useEffect(() => {
messagesEndRef.current.scrollIntoView({ behavior: "smooth" })
}, [JSON.stringify(chatLog)]);
scrollIntoViewOptions are:
behavior (Optional)
Defines the transition animation. One of auto or smooth. Defaults to auto.
block (Optional)
Defines vertical alignment. One of start, center, end, or nearest. Defaults to start.
inline (Optional)
Defines horizontal alignment. One of start, center, end, or nearest. Defaults to nearest.
So, you will need to add block option to end.
useEffect(() => {
messagesEndRef.current.scrollIntoView({ behavior: "smooth", block: "end" })
}, [chatLog]);
Solved it by adding this code:
useEffect(() => {
const chatLogElement = messagesEndRef.current;
const currentScrollTop = chatLogElement.scrollTop;
const targetScrollTop = chatLogElement.scrollHeight - chatLogElement.clientHeight;
const scrollDiff = targetScrollTop - currentScrollTop;
let startTime;
function scroll(timestamp) {
if (!startTime) {
startTime = timestamp;
}
const elapsedTime = timestamp - startTime;
const progress = elapsedTime / 200;
chatLogElement.scrollTop = currentScrollTop + (scrollDiff * progress);
if (progress < 1) {
window.requestAnimationFrame(scroll);
}
}
window.requestAnimationFrame(scroll);
}, [chatLog.length]);
Now it works, even when overflow is set to "auto"
I have this what may seem like a simple problem for more experienced developers but it has been irritating me for quite a while.
I keep having .map is not a function, although it clearly is. see the code below
I am iterating over the results state, but it doesn't seem to work
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<App.js>>>>>>>>>>>>>>>>>>>>>>>>>>>>
import "./App.css";
import React, { useEffect, useState } from "react";
import ContactCard from "./ContactCard";
const App = () => {
const [results, setResults] = useState([]);
useEffect(() => {
fetch("https://randomuser.me/api/?results=5")
.then((response) => response.json())
.then((data) => {
console.log(data);
setResults(data);
});
}, []);
return (
<div>
{results.map((result, i) => {
return (
<ContactCard
key={i}
avatarUrl={result.picture.large}
name={result.name}
email={result.email}
age={result.dob.age}
/>
);
})}
</div>
);
};
export default App;
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>
import React, { useState } from "react";
const ContactCard = (props) => {
const [showAge, setShowAge] = useState(false);
const showAgefn=()=>{
setShowAge(!showAge)
}
return (
<div className="contact-card">
<img src={props.avatarUrl} alt="profile" />
<div className="user-details">
<p>Name: {props.name}</p>
<p>Email: {props.email}</p>
<button onClick={showAgefn}>Show age</button>
{showAge && <p>Age: {props.age}</p>}
</div>
</div>
);
};
export default ContactCard;
Try this
{results.results.map((result, i) => {
return (
<ContactCard
key={i}
avatarUrl={result.picture.large}
name={result.name}
email={result.email}
age={result.dob.age}
/>
);
}
i prefer to rename my state to [data, setData] then I can use data.results instead of results.results
This issue is that the response you are getting from https://randomuser.me/api/?results=5 is like as follows
{
"results": [...],
"info": {...}
}
So in your useEffect just modify the following
useEffect(() => {
fetch("https://randomuser.me/api/?results=5")
.then((response) => response.json())
.then((data) => {
console.log(data);
setResults(data.results); // Just modify this line
});
}, []);
All Other things are perfectly fine
Hope it Helps
First results keyword is the state and Second results keyword is for the array.
Don't Forget to use ?.map as if the map is null it won't return any error. It's a check if there is any data in map or not.
{results.results?.map((result, i) => {
return (
<ContactCard
key={i}
avatarUrl={result.picture.large}
name={result.name}
email={result.email}
age={result.dob.age}
/>
);
}
Saving the state like so [users, setUsers]
Then adding Array.from inside the curly brackets seem to have solved the issue
return (
<div>
{Array.from(users.map((user, i) => {
return (
<ContactCard
key={i}
avatarUrl={user.picture.large}
name={user.first}
email={user.email}
age={user.dob.age}
/>
);
}))}
</div>
);
I have a problem and I need you to help me understand it. I am using ReactJS and I am building a simple CRUD Todo App. I Want to store my todos in local storage.
The data is saved there and I can see it but after the refresh it is emptying my local storage.
What am I doing wrong?
Something that I notice is that from the first time when I open the app (first rendering), local storage is creating the storage space without adding a todo.
Could I have missed something in my code that makes it reset it or empty it when the page is rendered?
import React, { useState, useEffect } from "react";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import {
faCheck,
faPen,
faPlus,
faTrashCan,
} from "#fortawesome/free-solid-svg-icons";
import "./App.css";
import { faCircleCheck } from "#fortawesome/free-regular-svg-icons";
function App() {
const [todos, setTodos] = useState([]);
const [todo, setTodo] = useState("");
const [todoEditing, setTodoEditing] = useState(null);
const [editingText, setEditingText] = useState("");
useEffect(() => {
const json = window.localStorage.getItem("todos");
const loadedTodos = JSON.parse(json);
if (loadedTodos) {
setTodos(loadedTodos);
}
}, []);
useEffect(() => {
const json = JSON.stringify(todos);
window.localStorage.setItem("todos", json);
}, [todos]);
function handleSubmit(e) {
e.preventDefault();
const newTodo = {
id: new Date().getTime(),
text: todo,
completed: false,
};
setTodos([...todos].concat(newTodo));
setTodo("");
}
function deleteTodo(id) {
const updatedTodos = [...todos].filter((todo) => todo.id !== id);
setTodos(updatedTodos);
}
function toggleComplete(id) {
let updatedTodos = [...todos].map((todo) => {
if (todo.id === id) {
todo.completed = !todo.completed;
}
return todo;
});
setTodos(updatedTodos);
}
function submitEdits(id) {
const updatedTodos = [...todos].map((todo) => {
if (todo.id === id) {
todo.text = editingText;
}
return todo;
});
setTodos(updatedTodos);
setTodoEditing(null);
}
return (
<div className="App">
<div className="app-container">
<div className="todo-header">
<form onSubmit={handleSubmit}>
<input
type="text"
name="todo-input-text"
placeholder="write a todo..."
onChange={(e) => {
setTodo(e.target.value);
}}
value={todo}
/>
<button>
<FontAwesomeIcon icon={faPlus} />
</button>
</form>
</div>
<div className="todo-body">
{todos.map((todo) => {
return (
<div className="todo-wrapper" key={todo.id}>
{todo.id === todoEditing ? (
<input
className="edited-todo"
type="text"
onChange={(e) => setEditingText(e.target.value)}
/>
) : (
<p className={todo.completed ? "completed" : "uncompleted"}>
{todo.text}
</p>
)}
<div className="todo-buttons-wrapper">
<button onClick={() => toggleComplete(todo.id)}>
<FontAwesomeIcon icon={faCircleCheck} />
</button>
{todo.id === todoEditing ? (
<button onClick={() => submitEdits(todo.id)}>
<FontAwesomeIcon icon={faCheck} />
</button>
) : (
<button onClick={() => setTodoEditing(todo.id)}>
<FontAwesomeIcon icon={faPen} />
</button>
)}
<button
onClick={() => {
deleteTodo(todo.id);
}}
>
<FontAwesomeIcon icon={faTrashCan} />
</button>
</div>
</div>
);
})}
</div>
</div>
</div>
);
}
export default App;
You should be loading todos from localStorage on the Component mount if they are available in localStorage like this,
const loadedTodos = localStorage.getItem("todos")
? JSON.parse(localStorage.getItem("todos"))
: []; // new
const [todos, setTodos] = useState(loadedTodos); // updated
And then you don't have to mutate the state using setTodos(loadedTodos) in the useEffect.
Just remove this useEffect , from the code:
// that useEffect should be removed
useEffect(() => {
const json = window.localStorage.getItem("todos");
const loadedTodos = JSON.parse(json);
if (loadedTodos) {
setTodos(loadedTodos);
}
}, []);
You can check this in the working CodeSandbox as well.
I think your second useEffect is causing it to reset.
Move that the useEffect logic to a separate function.
And instead of calling setTodos, call that function, update the storage, and then call setTodos from that function.
If you call the setTodos function with a callback function and spread operator like this it should work:
useEffect(() => {
const json = window.localStorage.getItem("todos");
const loadedTodos = JSON.parse(json);
if (loadedTodos) {
// set local storage like this
setTodos( prevTodos => [...prevTodos, ...loadedTodos] );
}}, []);
I'm a newbie to react, only been using it for a few days, so forgive me if this is a stupid question.
I have a file input component and an image thumbnail component, I use two duplicate file input components to update two different states then display the image from the different states in two different thumbnail components. I have unique keys set on all of the components, but only the state for the first component in the Dom is updated. When I add an image using the second file input, it updates the state belonging to the first file input.
I've tried looking for solutions and all of them state to use unique keys, which I think I have done properly.
let [certification, setCertification] = useState(null)
let [photoId, setPhotoId] = useState(null)
let handleUpdateCertificate = (e) =>{
let file = e.target.files[0]
console.log(file)
let path = URL.createObjectURL(file)
let newCertificate = {
'file': file,
'path' : path
}
setCertification(newCertificate)
}
let handleUpdatePhotoId = (e) => {
let file = e.target.photoidinput.files[0]
let path = URL.createObjectURL(file)
let newPhotoID = {
'file': file,
'path' : path
}
setPhotoId(newPhotoID)
}
My return html is:
<div className='justify-content-center margin-20' key='certificate-wrapper'>
<ImgThumbnail key={'certificate'} name={'certificate'} image=
{certification?.path} wrapperClass={'justify-content-center margin-20'}/>
</div>
<div className='justify-content-center margin-20'>
<FileInput key={'certificateinput'} name={'certificateinput'} labelText={<p
className='text-paragraph edit-btn-text'>Add Certificate</p>}
onChange={handleUpdateCertificate}
classWrapper={'edit-profile-responsive-btn-wrapper'}/>
</div>
<div className='justify-content-center margin-20 ' key='photo-Id'>
<ImgThumbnail key={'photoid'} name={'photoId'} image={photoId?.path}
wrapperClass={'justify-content-center margin-20'}/>
</div>
<div className='justify-content-center margin-20' key='photo-id-input-wrapper'>
<FileInput key={'photoidinput'} name={'photoidinput'} labelText={<p
className='text-paragraph edit-btn-text'>Add Photo ID</p>}
onChange={handleUpdatePhotoId}
classWrapper={'edit-profile-responsive-btn-wrapper'}/>
</div>
Okay I'll give you some hints and then give you the working example:
You don't need to set key attribute if you are writing JSX elements like that, you need that only if you render a list of elements from an array, to prevent useless re-rendering when the array updates.
use const instead of let when a variable is static, there is a lint rule about it !
Try to use DRY, your update Handlers share a lot of logic, if you are going to add more inputs that would be all code repetition.
Now the code:
import React, { useState } from 'react';
import './style.css';
export default function App() {
const [certification, setCertification] = useState(null);
const [photoId, setPhotoId] = useState(null);
const updateData = (file, cb) => {
const path = URL.createObjectURL(file);
const data = {
file: file,
path: path,
};
cb(data);
};
const handleUpdateCertificate = (e) => {
updateData(e.target.files[0], setCertification);
};
const handleUpdatePhotoId = (e) => {
updateData(e.target.files[0], setPhotoId);
};
return (
<div>
{certification && (
<div className="justify-content-center margin-20">
<ImgThumbnail
name={'certificate'}
image={certification?.path}
wrapperClass={'justify-content-center margin-20'}
/>
</div>
)}
<div className="justify-content-center margin-20">
<FileInput
id="certificate"
name={'certificateinput'}
labelText={
<p className="text-paragraph edit-btn-text">Add Certificate</p>
}
onChange={handleUpdateCertificate}
classWrapper={'edit-profile-responsive-btn-wrapper'}
/>
</div>
{photoId && (
<div className="justify-content-center margin-20 " key="photo-Id">
<ImgThumbnail
name={'photoId'}
image={photoId?.path}
wrapperClass={'justify-content-center margin-20'}
/>
</div>
)}
<div
className="justify-content-center margin-20"
key="photo-id-input-wrapper"
>
<FileInput
id="photo"
name={'photoidinput'}
labelText={
<p className="text-paragraph edit-btn-text">Add Photo ID</p>
}
onChange={handleUpdatePhotoId}
classWrapper={'edit-profile-responsive-btn-wrapper'}
/>
</div>
</div>
);
}
const FileInput = ({ id, labelText, ...props }) => (
<label htmlFor={id}>
{labelText}
<input id={id} style={{ display: 'none' }} type="file" {...props} />
</label>
);
const ImgThumbnail = ({ name, image }) => (
<div>
<img style={{ width: '100px', height: '100px' }} src={image} alt={name} />
</div>
);
This example works right, you were probably doing something wrong inside FileInput Component, remember that a label has to have an htmlFor attribute with the id of the input element you want to trigger.
Now, this code can be optimized and made more React style, since you might have more file inputs in the future, let's see how it can be optimized by creating reusable Components and compose them properly:
import React, { useState } from 'react';
import './style.css';
/* INPUTS IMAGE TYPES */
const inputs = [
{ type: 'photo', name: 'photo', label: 'Photo' },
{ type: 'certificate', name: 'certificate', label: 'Certificate' },
{ type: 'anotherType', name: 'anotherName', label: 'Another Input' },
];
export default function App() {
return (
<div>
{inputs.map((data) => (
<ImagePreviewer key={data.type} data={data} />
))}
</div>
);
}
const FileInput = ({ id, labelText, ...props }) => (
<label htmlFor={id}>
{labelText}
<input id={id} style={{ display: 'none' }} type="file" {...props} />
</label>
);
const ImgThumbnail = ({ name, image }) => (
<div>
<img src={image} alt={name} />
</div>
);
const ImagePreviewer = ({ data: { type, name, label } }) => {
const [image, setImage] = useState(null);
const updateData = (file, cb) => {
const path = URL.createObjectURL(file);
const data = {
file: file,
path: path,
};
cb(data);
};
const handleUpdate = (e) => {
updateData(e.target.files[0], setImage);
};
return (
<div>
{image && (
<div>
<ImgThumbnail name={'name'} image={image?.path} />
</div>
)}
<div>
<FileInput
id={name}
name={name}
labelText={<p>Add {label}</p>}
onChange={handleUpdate}
/>
</div>
</div>
);
};
A working demo HERE.
I have built a component CreatePost which is used for creating or editing posts,
the problem is if I render this component twice even if I upload a file from the second component they are changed in the first one, why? Here is the code:
import FileUpload from "#components/form/FileUpload";
import { Attachment, Camera, Video, Writing } from "public/static/icons";
import styles from "#styles/components/Post/CreatePost.module.scss";
import { useSelector } from "react-redux";
import { useInput, useToggle } from "hooks";
import { useRef, useState } from "react";
import StyledButton from "#components/buttons/StyledButton";
import Modal from "#components/Modal";
import { post as postType } from "types/Post";
import Removeable from "#components/Removeable";
interface createPostProps {
submitHandler: (...args) => void;
post?: postType;
isEdit?: boolean;
}
const CreatePost: React.FC<createPostProps> = ({ submitHandler, post = null, isEdit = false }) => {
console.log(post);
const maxFiles = 10;
const [showModal, setShowModal, ref] = useToggle();
const [description, setDescription] = useInput(post?.description || "");
const user = useSelector((state) => state.user);
const [files, setFiles] = useState<any[]>(post?.files || []);
const handleFileUpload = (e) => {
const fileList = Array.from(e.target.files);
if (fileList.length > maxFiles || files.length + fileList.length > maxFiles) {
setShowModal(true);
} else {
const clonedFiles = [...files, ...fileList];
setFiles(clonedFiles);
}
e.target.value = "";
};
const removeHandler = (id) => {
const filtered = files.filter((file) => file.name !== id);
setFiles(filtered);
};
return (
<div className={styles.createPost}>
<div className={styles.top}>
<span>
<img src="/static/images/person1.jpg" />
</span>
<textarea
onChange={setDescription}
className="primaryScrollbar"
aria-multiline={true}
value={description}
placeholder={`What's on your mind ${user?.name?.split(" ")[0]}`}
></textarea>
{description || files.length ? (
<StyledButton
background="bgPrimary"
size="md"
className={styles.submitButton}
onClick={() => {
if (!isEdit)
submitHandler({
files: files,
author: { name: user.name, username: user.username },
postedTime: 52345,
id: Math.random() * Math.random() * 123456789101112,
comments: [],
likes: [],
description,
});
else {
submitHandler({
...post,
description,
files,
});
}
setDescription("");
setFiles([]);
}}
>
{isEdit ? "Edit" : "Post"}
</StyledButton>
) : null}
</div>
<div className={styles.middle}>
<div className={styles.row}>
{files.map((file) => {
return (
<Removeable
key={file.name + Math.random() * 100000}
removeHandler={() => {
removeHandler(file.name);
}}
>
{file.type.includes("image") ? (
<img src={URL.createObjectURL(file)} width={150} height={150} />
) : (
<video>
<source src={URL.createObjectURL(file)} type={file.type} />
</video>
)}
</Removeable>
);
})}
</div>
</div>
<div className={styles.bottom}>
<FileUpload
id="uploadPhoto"
label="upload photo"
icon={
<span>
<Camera /> Photo
</span>
}
className={styles.fileUpload}
multiple
onChange={handleFileUpload}
accept="image/*"
/>
<FileUpload
id="uploadVideo"
label="upload video"
icon={
<span>
<Video /> Video
</span>
}
className={styles.fileUpload}
multiple
onChange={handleFileUpload}
accept="video/*"
/>
<FileUpload
id="writeArticle"
label="write article"
icon={
<span>
<Writing /> Article
</span>
}
className={styles.fileUpload}
multiple
onChange={handleFileUpload}
/>
</div>
{showModal && (
<Modal size="sm" backdrop="transparent" ref={ref} closeModal={setShowModal.bind(null, false)} yPosition="top">
<p>Please choose a maximum of {maxFiles} files</p>
<StyledButton size="md" background="bgPrimary" onClick={setShowModal.bind(null, false)}>
Ok
</StyledButton>
</Modal>
)}
</div>
);
};
export default CreatePost;
Now on my main file I have:
const Main = () => {
const [posts, setPosts] = useState<postType[]>([]);
const addPost = (post: postType) => {
setPosts([post, ...posts]);
};
const editPost = (post: postType) => {
const updated = posts.map((p) => {
if (post.id === post.id) {
p = post;
}
return p;
});
setPosts(updated);
};
const deletePost = (id) => {
const filtered = posts.filter((post) => post.id !== id);
setPosts(filtered);
};
return (
<>
<CreatePost submitHandler={addPost} key="0" />
<CreatePost submitHandler={addPost} key="1"/>
{posts.map((post) => {
return <PostItem {...post} editHandler={editPost} key={post.id} deleteHandler={deletePost.bind(null, post.id)} />;
})}
</>
);
};
export default Main;
I tried to add/remove the key but doesn't change anything, also tried to recreate this problem in a simpler way in sandbox but I can't it works fine there. And the problem is only when I upload files not when I write text inside the <textarea/>
Note: The second in reality is shown dynamically inside a modal when clicked edit in a post, but I just showed it here for simplicity because the same problem occurs in both cases.
Okay after some hours of debugging I finally found the problem.
Because my <FileUpload/> uses id to target the input inside the <CreatePost/> the <FileUpload/> always had same it, so when I used <CreatePost/> more than 1 time it would target the first element that found with that id that's why the first component was being updated