Show and hide selected div element via .map() - reactjs

Every video I've seen regarding to showing or hiding a div, is quite not effective at all if you're making use of a state that's based on true or false, thus when a button is clicked through the .map() all elements that are hidden would be shown, therefore it wouldn't be in great use of all, I guess that's why the element's index should be in use to determine which element should shown or hidden right?
Scenario
So I'm building a social platform for a learning experience, where I map through all my posts in an array, once I click my comment Icon, the comments should be shown for that post, but unfortunately I'm unable to find a solution regarding to the use of functional components.
this is what I have:
import React, { useState, useEffect, useReducer, useRef, useMemo } from "react";
import axios from "axios";
import Cookies from "universal-cookie";
import "../../styles/private/dashboard.css";
import DashboardHeader from "../../components/private/templates/header";
import DashboardSidebar from "../../components/private/templates/sidebar";
import ImageSearchIcon from "#material-ui/icons/ImageSearch";
import VideoLibraryIcon from "#material-ui/icons/VideoLibrary";
import FavoriteIcon from "#material-ui/icons/Favorite";
import SendIcon from "#material-ui/icons/Send";
import { Avatar } from "#material-ui/core";
import { useSelector, useDispatch } from "react-redux";
import { newPost } from "../../redux/actions/posts/new-post";
import { likePost } from "../../redux/actions/posts/like-post";
import { getPosts } from "../../redux/actions/posts/get-posts";
import { unlikePost } from "../../redux/actions/posts/unlike-post";
import { getPostLikes } from "../../redux/actions/posts/get-likes";
import { likePostComment } from "../../redux/actions/posts/like-comment";
import { unlikePostComment } from "../../redux/actions/posts/unlike-comment";
import { newPostComment } from "../../redux/actions/posts/new-post-comment";
import ChatBubbleOutlineIcon from "#material-ui/icons/ChatBubbleOutline";
import LoopIcon from "#material-ui/icons/Loop";
import FavoriteBorderIcon from "#material-ui/icons/FavoriteBorder";
import MoreHorizIcon from "#material-ui/icons/MoreHoriz";
import Pusher from "pusher-js";
import FlipMove from "react-flip-move";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import io from "socket.io-client";
const socket = io.connect("http://localhost:5000");
function Dashboard({
history,
getPost,
getLike,
getAllPosts,
getAllLikes,
likePosts,
unlikePosts,
}) {
const [participants, setParticipants] = useState({});
const cookies = new Cookies();
const [toggle, setToggle] = useState(false);
const [messages, setMessages] = useState("");
const [media, setMedia] = useState(null);
const [posts, setPosts] = useState([]);
const [error, setError] = useState("");
const [comment, setComment] = useState();
const userLogin = useSelector((state) => state.userLogin);
const { user } = userLogin;
const [uname, setUname] = useState(user.name);
const [upic, setUpic] = useState(user.pic);
const dispatch = useDispatch();
const config = {
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${user.token}`,
},
};
useEffect(() => {
if (!cookies.get("authToken")) {
history.push("/login");
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [history]);
useEffect(() => {
axios.get("/api/post/posts", config).then((response) => {
setPosts(response.data);
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
const handler = (item) => {
setPosts((oldPosts) => {
const findItem = oldPosts.find((post) => post._id === item._id);
if (findItem) {
return oldPosts.map((post) => (post._id === item._id ? item : post));
} else {
return [item, ...oldPosts];
}
});
};
socket.on("posts", handler);
return () => socket.off("posts", handler);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const postHandler = (e) => {
e.preventDefault();
dispatch(newPost(uname, upic, messages, media));
setMessages("");
};
const LikePost = (postId) => {
likePosts(postId, user._id, user.name, user.pic);
};
const UnlikePost = (postId) => {
unlikePosts(postId);
};
const submitComment = (postId) => {
dispatch(newPostComment(postId, uname, upic, comment));
setComment("");
};
const LikeCommentPost = (postId, commentId) => {
dispatch(likePostComment(postId, commentId, user._id, user.name, user.pic));
};
const UnlikeCommentPost = (postId, commentId) => {
dispatch(unlikePostComment(postId, commentId));
};
return error ? (
<span>{error}</span>
) : (
<div className="dashboard">
<DashboardHeader />
<div className="dashboard__container">
<div className="dashboard__sidebar">
<DashboardSidebar />
</div>
<div className="dashboard__content">
<div className="dashboard__contentLeft">
<div className="dashboard__messenger">
<div className="dashboard__messengerTop">
<Avatar src={user.pic} className="dashboard__messengerAvatar" />
<input
type="text"
placeholder={`What's on your mind, ${user.name}`}
value={messages}
onChange={(e) => setMessages(e.target.value)}
/>
<SendIcon
className="dashboard__messengerPostButton"
onClick={postHandler}
/>
</div>
<div className="dashboard__messengerBottom">
<ImageSearchIcon
className="dashboard__messengerImageIcon"
value={media}
onChange={(e) => setMedia((e) => e.target.value)}
/>
<VideoLibraryIcon className="dashboard__messengerVideoIcon" />
</div>
</div>
<div className="dashboard__postsContainer">
<FlipMove>
{posts.map((post, i) => (
<div className="dashboard__post" key={i}>
<MoreHorizIcon className="dashboard__postOptions" />
<div className="dashboard__postTop">
<Avatar
className="dashboard__postUserPic"
src={post.upic}
/>
<h3>{post.uname}</h3>
</div>
<div className="dashboard__postBottom">
<p>{post.message}</p>
{media === null ? (
""
) : (
<div className="dashboard__postMedia">{media}</div>
)}
</div>
<div className="dashboard__postActions">
{toggle ? (
<ChatBubbleOutlineIcon
onClick={() => setToggle(!toggle)}
className="dashboard__actionComment"
/>
) : (
<ChatBubbleOutlineIcon
onClick={() => setToggle(!toggle)}
className="dashboard__actionComment"
/>
)}
<label
id="totalLikes"
className="dashboard__comments"
style={{ color: "forestgreen" }}
>
{post.commentCount}
</label>
{post.likes.find((like) => like.uid === user._id) ? (
<FavoriteIcon
onClick={() => UnlikePost(post._id)}
className="dashboard__actionUnlike"
/>
) : (
<FavoriteBorderIcon
onClick={() => LikePost(post._id)}
className="dashboard__actionLike"
/>
)}
<label
id="totalLikes"
className="dashboard__likes"
style={{ color: "forestgreen" }}
>
{post.likeCount}
</label>
</div>
<div
className={
toggle
? "dashboard__commentContent toggle"
: "dashboard__commentContent"
}
>
<div className="dashboard__postComments">
{post.comments.map((comment) => (
<div
key={comment.toString()}
className="dashboard__postComment"
>
<div className="dashboard__postCommentTop">
<Avatar src={comment.upic} />
<h4>{comment.uname}</h4>
</div>
<p>{comment.message}</p>
<div className="dashboard__postCommentActions">
{comment.likes.find(
(like) => like.uid === user._id
) ? (
<FavoriteIcon
onClick={() =>
UnlikeCommentPost(post._id, comment._id)
}
className="dashboard__actionUnlike"
/>
) : (
<FavoriteBorderIcon
onClick={() =>
LikeCommentPost(post._id, comment._id)
}
className="dashboard__actionLike"
/>
)}
<label
id="totalLikes"
className="dashboard__likes"
style={{ color: "forestgreen" }}
>
{comment.likeCount}
</label>
</div>
</div>
))}
</div>
<div className="dashboard__commentInput">
<input
type="text"
placeholder="Comment post"
value={comment}
onChange={(e) => setComment(e.target.value)}
/>
<button onClick={() => submitComment(post._id)}>
Send
</button>
</div>
</div>
</div>
))}
</FlipMove>
</div>
</div>
<div className="dashboardContentRight"></div>
</div>
</div>
</div>
);
}
Dashboard.propTypes = {
getLike: PropTypes.arrayOf(PropTypes.string),
getPost: PropTypes.arrayOf(PropTypes.string),
likePost: PropTypes.arrayOf(PropTypes.string),
unlikePost: PropTypes.arrayOf(PropTypes.string),
};
function mapStateToProps(state) {
return {
getPost: getPosts(state),
getLike: getPostLikes(state),
likePosts: likePost(state),
unlikePosts: unlikePost(state),
};
}
function mapDispatchToProps(dispatch) {
return {
getAllPosts: (posts) => dispatch(getPosts(posts)),
getAllLikes: (likes) => dispatch(getPostLikes(likes)),
likePosts: (like) => dispatch(likePost(like)),
unlikePosts: (like) => dispatch(unlikePost(like)),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Dashboard);
Extra
here is a unlisted video from youtube, just for incase that you do not understand.

You have a lot of code going there... You should refactor that i smaller component, more easier to read and maintainability.
You can check there the answer i posted, it's the same core issue:
The axios delete functionality is only deleting last user from table, not the one I click on
your toggle is based on the global state, so each post doesn't have its proper way to see if it's open or not.That's why it will open everything
You need to tell which one is open and which ones aren't.
Here i refactored your code to make it work with multiple boxes open, i didn't test it on codesandbox, i let you try, but it wasn't big changes, so it should works.
Please pay attention to these news changes:
useState property openBoxes
Method toggleCommentBox
Method isCommentBoxOpen
I then replaced in your jsx the way you check if the comment box is open or not.
function Dashboard({
history,
getPost,
getLike,
getAllPosts,
getAllLikes,
likePosts,
unlikePosts,
}) {
const [participants, setParticipants] = useState({});
const cookies = new Cookies();
const [openBoxes, setOpenBoxes] = useState([]);
const [messages, setMessages] = useState("");
const [media, setMedia] = useState(null);
const [posts, setPosts] = useState([]);
const [error, setError] = useState("");
const [comment, setComment] = useState();
const userLogin = useSelector((state) => state.userLogin);
const {user} = userLogin;
const [uname, setUname] = useState(user.name);
const [upic, setUpic] = useState(user.pic);
const dispatch = useDispatch();
const config = {
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${user.token}`,
},
};
useEffect(() => {
if (!cookies.get("authToken")) {
history.push("/login");
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [history]);
useEffect(() => {
axios.get("/api/post/posts", config).then((response) => {
setPosts(response.data);
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
const handler = (item) => {
setPosts((oldPosts) => {
const findItem = oldPosts.find((post) => post._id === item._id);
if (findItem) {
return oldPosts.map((post) => (post._id === item._id ? item : post));
} else {
return [item, ...oldPosts];
}
});
};
socket.on("posts", handler);
return () => socket.off("posts", handler);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
/*
Toggle state of post boxes
*/
const toggleCommentBox = postId => {
if(openBoxes.includes(postId)){
setOpenBoxes(openBoxes.filter(x => x !== postId))
} else {
setOpenBoxes([...openBoxes, postId])
}
}
/*
Returns boolean if comment box is open on this post
*/
const isCommentBoxOpen = postId => openBoxes.includes(postId)
const postHandler = (e) => {
e.preventDefault();
dispatch(newPost(uname, upic, messages, media));
setMessages("");
};
const LikePost = (postId) => {
likePosts(postId, user._id, user.name, user.pic);
};
const UnlikePost = (postId) => {
unlikePosts(postId);
};
const submitComment = (postId) => {
dispatch(newPostComment(postId, uname, upic, comment));
setComment("");
};
const LikeCommentPost = (postId, commentId) => {
dispatch(likePostComment(postId, commentId, user._id, user.name, user.pic));
};
const UnlikeCommentPost = (postId, commentId) => {
dispatch(unlikePostComment(postId, commentId));
};
return error ? (
<span>{error}</span>
) : (
<div className="dashboard">
<DashboardHeader/>
<div className="dashboard__container">
<div className="dashboard__sidebar">
<DashboardSidebar/>
</div>
<div className="dashboard__content">
<div className="dashboard__contentLeft">
<div className="dashboard__messenger">
<div className="dashboard__messengerTop">
<Avatar src={user.pic} className="dashboard__messengerAvatar"/>
<input
type="text"
placeholder={`What's on your mind, ${user.name}`}
value={messages}
onChange={(e) => setMessages(e.target.value)}
/>
<SendIcon
className="dashboard__messengerPostButton"
onClick={postHandler}
/>
</div>
<div className="dashboard__messengerBottom">
<ImageSearchIcon
className="dashboard__messengerImageIcon"
value={media}
onChange={(e) => setMedia((e) => e.target.value)}
/>
<VideoLibraryIcon className="dashboard__messengerVideoIcon"/>
</div>
</div>
<div className="dashboard__postsContainer">
<FlipMove>
{posts.map((post, i) => (
<div className="dashboard__post" key={i}>
<MoreHorizIcon className="dashboard__postOptions"/>
<div className="dashboard__postTop">
<Avatar
className="dashboard__postUserPic"
src={post.upic}
/>
<h3>{post.uname}</h3>
</div>
<div className="dashboard__postBottom">
<p>{post.message}</p>
{media === null ? (
""
) : (
<div className="dashboard__postMedia">{media}</div>
)}
</div>
<div className="dashboard__postActions">
{isCommentBoxOpen(post.id) ? (
<ChatBubbleOutlineIcon
onClick={() => toggleCommentBox(post._id)}
className="dashboard__actionComment"
/>
) : (
<ChatBubbleOutlineIcon
onClick={() => toggleCommentBox(post._id)}
className="dashboard__actionComment"
/>
)}
<label
id="totalLikes"
className="dashboard__comments"
style={{color: "forestgreen"}}
>
{post.commentCount}
</label>
{post.likes.find((like) => like.uid === user._id) ? (
<FavoriteIcon
onClick={() => UnlikePost(post._id)}
className="dashboard__actionUnlike"
/>
) : (
<FavoriteBorderIcon
onClick={() => LikePost(post._id)}
className="dashboard__actionLike"
/>
)}
<label
id="totalLikes"
className="dashboard__likes"
style={{color: "forestgreen"}}
>
{post.likeCount}
</label>
</div>
<div
className={
toggle
? "dashboard__commentContent toggle"
: "dashboard__commentContent"
}
>
<div className="dashboard__postComments">
{post.comments.map((comment) => (
<div
key={comment.toString()}
className="dashboard__postComment"
>
<div className="dashboard__postCommentTop">
<Avatar src={comment.upic}/>
<h4>{comment.uname}</h4>
</div>
<p>{comment.message}</p>
<div className="dashboard__postCommentActions">
{comment.likes.find(
(like) => like.uid === user._id
) ? (
<FavoriteIcon
onClick={() =>
UnlikeCommentPost(post._id, comment._id)
}
className="dashboard__actionUnlike"
/>
) : (
<FavoriteBorderIcon
onClick={() =>
LikeCommentPost(post._id, comment._id)
}
className="dashboard__actionLike"
/>
)}
<label
id="totalLikes"
className="dashboard__likes"
style={{color: "forestgreen"}}
>
{comment.likeCount}
</label>
</div>
</div>
))}
</div>
<div className="dashboard__commentInput">
<input
type="text"
placeholder="Comment post"
value={comment}
onChange={(e) => setComment(e.target.value)}
/>
<button onClick={() => submitComment(post._id)}>
Send
</button>
</div>
</div>
</div>
))}
</FlipMove>
</div>
</div>
<div className="dashboardContentRight"></div>
</div>
</div>
</div>
);
}
Let me know if it works or you need more explanation

Related

Enter the selected item in the search bar in React.js

I have the following code:
function App() {
const [countries,setCountries]= useState([]);
const [search, setSearch] = useState('');
//Take data from API with useEffect, async/await and try/catch
useEffect(() => {
const fetchData = async () => {
try {
const response = await axios.get('https://restcountries.com/v2/all');
setCountries(response.data);
} catch (error) {
console.error(error);
}
}
fetchData();
}, []);
const filteredCountries = countries.filter((country) =>
country.name.toLowerCase().includes(search.toLowerCase())
);
const handleSelect = (country) => {
setSearch(country.name);
}
return (
<>
<div>
<SearchBar onChange={(e)=> setSearch(e.target.value)} />
{
<ul className="list">
{search.length > 0 && filteredCountries.map((country) => (
<li key={country.name} onClick={() => handleSelect(country)}>
{country.name}
</li>
))}
</ul>
}
</div>
<div className="map-container">
</div>
</>
)
}
export default App;
The result is this:
List image
How can I select an item from the list, e.g. if I search for Ital, Italy appears and I would like to select it and have it appear in the search bar.
I would like to create a search bar to find a country and select it, it should appear in the search bar after being selected.
CodeSandBox Link: https://codesandbox.io/p/github/pierre1590/Population-Tracker/draft/gallant-gagarin?file=%2Fsrc%2Fcomponents%2FMap%2FMap.js
Add value={search} in your <SearchBar/> component.
eg: <SearchBar value={search} onChange={(e)=> setSearch(e.target.value)} />
Below is the full code (I've used a normal input tag in place of your SearchBar component)
import { useState, useEffect } from "react";
import axios from 'axios';
function App() {
const [countries,setCountries]= useState([]);
const [search, setSearch] = useState('');
console.log(search)
//Take data from API with useEffect, async/await and try/catch
useEffect(() => {
const fetchData = async () => {
try {
const response = await axios.get('https://restcountries.com/v2/all');
setCountries(response.data);
} catch (error) {
console.error(error);
}
}
fetchData();
}, []);
const filteredCountries = countries.filter((country) =>
country.name.toLowerCase().includes(search.toLowerCase())
);
const handleSelect = (country) => {
setSearch(country.name);
}
return (
<>
<div>
<input value={search} onChange={(e)=> setSearch(e.target.value)} />
{
<ul className="list">
{search.length > 0 && filteredCountries.map((country) => (
<li key={country.name} onClick={() => handleSelect(country)}>
{country.name}
</li>
))}
</ul>
}
</div>
<div className="map-container">
</div>
</>
)
}
export default App;
CodeSandBox Link - https://codesandbox.io/s/enter-the-selected-item-in-the-search-bar-in-react-js-582rez?file=/src/App.js

Whi i get the error "token must be a string"?

I'm trying to use useEffect to set the behaviour for my room and at first, i wanna use the token and roomName to connect to the Twilio Video service. However, i got the error as "token must be a string" in my Room.js. I don't know what's wrong? can anyone help me?
VideoChat.js:
import React, { useState, useCallback } from "react";
import Lobby from "./Lobby";
import Room from "./Room"
function VideoChat() {
const [username, setUsername] = useState("");
const [roomName, setRoomName] = useState("");
const [token, setToken] = useState(null);
const handleUsernameChange = useCallback((event) => {
setUsername(event.target.value);
}, []);
const handleRoomNameChange = useCallback((event) => {
setRoomName(event.target.value);
}, []);
const handleSubmit = useCallback(
async (event) => {
event.preventDefault();
const data = await fetch("/video/token", {
method: "POST",
body: JSON.stringify({
identity: username,
room: roomName,
}),
headers: {
"Content-Type": "application/json",
},
}).then((res) => res.json());
setToken(data.token);
},
[username, roomName]
);
const handleLogout = useCallback((event) => {
setToken(null);
}, []);
return (
<div>
if (token){" "}
{
<div>
<Room roomName={roomName} token={token} handleLogout={handleLogout} />
</div>
}{" "}
else{" "}
{
<Lobby
username={username}
roomName={roomName}
handleUsernameChange={handleUsernameChange}
handleRoomNameChange={handleRoomNameChange}
handleSubmit={handleSubmit}
/>
}{" "}
</div>
);
}
export default VideoChat;
Lobby.js:
import React from "react";
const Lobby = ({
username,
handleUsernameChange,
roomName,
handleRoomNameChange,
handleSubmit
}) => {
return (
<form onSubmit={handleSubmit}>
<h2> Enter a room </h2>{" "}
<div>
<label htmlFor="name"> Name: </label>{" "}
<input
type="text"
id="field"
value={username}
onChange={handleUsernameChange}
required
/>
</div>{" "}
<div>
<label htmlFor="room"> Room name: </label>{" "}
<input
type="text"
id="room"
value={roomName}
onChange={handleRoomNameChange}
required
/>
</div>{" "}
<button type="submit"> Submit </button>{" "}
</form>
);
};
export default Lobby;
Room.js:
import React, { useState, useEffect } from "react";
import Video from "twilio-video";
const Room = ({ roomName, token, handleLogout }) => {
const [room, setRoom] = useState(null);
const [participants, setParticipants] = useState([]);
const remoteParticipants = participants.map(participant=> {
<p key={participant.sid}>{participant.identity}</p>
})
useEffect(() => {
const participantConnected = participant => {
setParticipants(prevParticipants => [...prevParticipants, participant]);
};
const participantDisconnected = participant => {
setParticipants(prevParticipants =>
prevParticipants.filter(p => p !== participant)
);
};
Video.connect(token, {
name: roomName
}).then(room => {
setRoom(room);
room.on('participantConnected', participantConnected);
room.on('participantDisconnected', participantDisconnected);
room.participants.forEach(participantConnected);
});
});
return (
<div className="room">
<h2>Room: {roomName}</h2>
<button onClick={handleLogout}>Log out</button>
<div className="local-participant">
{room ? (<p key={room.localParticipant.sid}>{room.localParticipant.identity}</p>) : ''}
</div>
<h3>Remote Participants</h3>
<p>{remoteParticipants}</p>
</div>
)
};
export default Room;
Sandbox link: https://codesandbox.io/s/beautiful-bhaskara-oo12s?file=/src/Lobby.js

React functional array delay to update

Helllo everyone, I have this issue where I am successfully sorting the array state of an object alphabetically using their cities but the problem is that the array that is getting visualized only updates after I search something on the UI.
I tried to look it up but still lost here is the video of what is happening
https://drive.google.com/file/d/17pAwTeo8IZ6mw3dd2pxDxbfY-ToL7cjG/view?usp=sharing
here is the code
full code here
import React, { useState, useEffect } from "react";
import "./body.css";
import Axios from "axios";
import { Button } from "#material-ui/core";
function SearchBar() {
const [filteredData, setFilteredData] = useState([]);
const [search, setSearch] = useState("");
async function getUsers() {
Axios.get("https://jsonplaceholder.typicode.com/users")
.then((response) => {
setFilteredData(response.data);
})
.catch((err) => {
console.log(err);
});
}
useEffect(() => {
getUsers();
}, []);
function handleReset() {
getUsers();
setSearch("");
handleClear();
}
const handleClear = () => {
Array.from(document.querySelectorAll("input")).forEach(
(input) => (input.value = "")
);
};
const delItem = (id) => {
setFilteredData(filteredData.filter((e) => e.name.localeCompare(id) !== 0));
};
const sort = () => {
setFilteredData(
filteredData.sort((a, b) => {
return a.address.city.localeCompare(b.address.city);
})
);
// console.log(sorted);
// setFilteredData(sorted);
console.log(filteredData);
};
return (
<div>
<form class="search-bar">
<input
type="input"
name="search"
pattern=".*\S.*"
// requiredw
autoComplete="off"
placeholder="Input Text Here"
onChange={(e) => setSearch(e.target.value)}
></input>
</form>
<Button onClick={handleReset}>Reset</Button>
<Button onClick={sort}>Sort</Button>
<div>
<ul>
{filteredData
.filter((user) => {
var dynamicSearch = search;
if (
user.name.toLowerCase().includes(dynamicSearch.toLowerCase()) ||
user.email
.toLowerCase()
.includes(dynamicSearch.toLowerCase()) ||
user.phone
.toLowerCase()
.includes(dynamicSearch.toLowerCase()) ||
user.address.city
.toLowerCase()
.includes(dynamicSearch.toLowerCase())
) {
return true;
}
})
.map((val, index) => (
<li className="li" key={val.id}>
<p className="list">
{"Name: "}
{val.name} <br />
{"Email: "}
{val.email}
<br />
{"Phone: "}
{val.phone}
<br />
{"City: "}
{val.address.city}
</p>
<button className="delButton" onClick={() => delItem(val.name)}>
x
</button>
</li>
))}
</ul>
</div>
</div>
);
}
export default SearchBar;
Just try with this one:
const sort = () => {
const sortedData = filteredData.sort((a, b) => {
return a.address.city.localeCompare(b.address.city);
});
setFilteredData([...sortedData]);
};
Problem is once you are updating the sorting data in setFilteredData function its not able to observe the changes that needs to be rendered. So always make the copy of the state variables when you are updating the values.

Why the wrong element is being updated only when uploading files?

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

How to pass the useReducer State to the child component?

I have a simple feedback application that contains multiple component.
app.js - parent component
data.js - this contain a dummy data the have name and the listofreview
component
user - this component will display the name and the thumbnail
review - this will display the list of review about the user and also have button that say leave a review.
modal - after the user click the leave a review this modal will appear that have a list of element that will update the list of review
I used useReducer to update the state. But the problem is the review.js don't show the updated state. maybe because the useReducer is located on the modal.js. What should i do so that i can also update the data that been display on the review.js
App.js
function App() {
return (
<div className="main-container">
<DisplayUser />
<div className="mainContent-container">
<DisplayReview />
</div>
</div>
);
}
User.js
import { data } from '../../src/data';
const CommentHandler = () => {
const [user] = React.useState(data);
return (
<>
{user.map((person) => {
const { id, name, thumbnail } = person;
return (
<div key={id} className='user-container'>
<h2 className="user-name">{name}</h2>
<img src={thumbnail} alt={name} title={name} className='user-icon' />
</div>
);
})}
</>
);
};
Review.js
import DisplayModal from './Modal'
import Modal from 'react-modal';
import { useState } from 'react';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
import { data } from '../../src/data';
const ReviewHandler = () => {
const [user] = useState(data);
const [showModal, setShowModal] = useState(false);
return (
<>
{user.map((person) => {
const { listOfReview } = person;
return (
<div key={person.id} className='review-container active'>
<div className="content-container">
{listOfReview.map((sub) => {
const { reviewId, rating, name, occupation, review } = sub;
return (
<div key={reviewId} className="content-container">
<div className='reviewer-rating'>
<div className="static-review">
{[...Array(5)].map((star, index) => {
return <FontAwesomeIcon key={index} className="star" icon="star" />
})}
</div>
<div className="dynamic-review">
{[...Array(rating)].map((star, index) => {
return <FontAwesomeIcon key={index} className="star" icon="star" />
})}
</div>
</div>
<div className="user-description">
<h3>{occupation}</h3>
<h4>{name}</h4>
<p>{review}</p>
</div>
</div>
)
})}
<button className="submit" onClick={() => setShowModal(true)}>LEAVE AREVIEW</button>
</div>
</div>
);
})}
<Modal isOpen={showModal} ariaHideApp={false}>
<DisplayModal onClick={(value) => setShowModal(value)} />
</Modal>
</>
);
};
Modal.js
import { useState, useReducer } from 'react';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
import { data } from '../../src/data';
import { reducer } from './reducer';
const ModalHandler = props => {
const [rating, setRating] = useState(null);
const [name, setName] = useState('');
const [occupation, setOccupation] = useState('');
const [reviews, setReviews] = useState('');
const [state, dispatch] = useReducer(reducer, data);
const [hoverRating, setHoverRating] = useState(null);
const handelSubmit = (e) => {
e.preventDefault();
if (name && occupation && reviews) {
const newReview = { reviewId: new Date().getTime(), name, occupation, rating, reviews };
dispatch({
type: 'ADD_REVIEW_ITEM',
payload: newReview
});
}
}
return (
<div className="modal-container">
<div className="modal-backdrop">
<form className="modal-inner" onSubmit={handelSubmit}>
<h2>Feel feel to send us your review!</h2>
<div className='revieweRating-container'>
<h3>How was your experience?</h3><p onClick={() => props.onClick(false)}>X</p>
<div className="dynamic-review">
{[...Array(5)].map((star, i) => {
const ratingValue = i + 1;
return (
<label>
<input type="radio"
name="review-star"
value={ratingValue}
onMouseEnter={() => setHoverRating(ratingValue)}
onMouseLeave={() => setHoverRating(ratingValue)}
onClick={() => setRating(ratingValue)}>
</input>
<FontAwesomeIcon
icon="star"
onMouseEnter={() => setHoverRating(ratingValue)}
onMouseLeave={() => setHoverRating(ratingValue)}
color={ratingValue <= (hoverRating || rating) ? "#FAD020" : "#BCC5D3"}
className="review-star" />
</label>
)
})}
</div>
</div>
<input
type="text"
name="name"
className="name"
placeholder="Name"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<input
type="text"
name="occupation"
className="alioccupationas"
placeholder="Aloccupationias"
value={occupation}
onChange={(e) => setOccupation(e.target.value)}
/>
<textarea
name="review"
cols="30"
rows="6"
className="review"
placeholder="Enter your review here!"
value={reviews}
onChange={(e) => setReviews(e.target.value)}>
</textarea>
<button type="submit" className="submit">SEND A REVIEW</button>
</form>
<div>
{state.map((data) => (
<div key={data.id}>
{data.listOfReview.map((review) => (
<div key={review.reviewId}>
<h3>{review.name}</h3>
<p>{review.occupation}</p>
</div>
))}
</div>
))}
</div>
</div>
</div >
);
}
reducer.js
export const reducer = (state, action) => {
switch (action.type) {
case "ADD_REVIEW_ITEM":
console.log(state);
return state.map((data) => {
if (data) {
const newReview = [...data.listOfReview, action.payload];
return {
...data,
listOfReview: newReview
};
}
return data;
});
default:
return state;
}
};
data.js
export const data = [
{
id: 1607089645363,
name: 'Andress Bonifacio',
noOfReview: 1,
listOfReview: [
{
reviewId: 1607089645361,
name: 'Juan Dela Cruz',
occupation: 'Father of Phil. Revolution',
rating: 5,
review: 'Numquam labore or dolorem enim but accusantium and autem ratione.',
}
]
}
];
If you want to get the updated data across all component, then make sure to have this line of code const [state, dispatch] = useReducer(reducer, data); available in every component that use them like in:-
in User.js
import { data } from '../../src/data';
import { reducer } from './reducer';
const CommentHandler = () => {
// not this
const [user] = React.useState(data);
// instead this
const [state, dispatch] = useReducer(reducer, data);
return (
<>
{state.map((person) => {
const { id, name, thumbnail } = person;
return (
<div key={id} className='user-container'>
<h2 className="user-name">{name}</h2>
<img src={thumbnail} alt={name} title={name} className='user-icon' />
</div>
);
})}
</>
);
};
in Review.js
import { useState, useReducer } from 'react';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
import { data } from '../../src/data';
import { reducer } from './reducer';
const ReviewHandler = () => {
// not this
// const [user] = useState(data);
// instead this
const [state, dispatch] = useReducer(reducer, data);
const [showModal, setShowModal] = useState(false);
return (
<>
{state.map((person) => {
const { listOfReview } = person;
return (
<div key={person.id} className='review-container active'>
<div className="content-container">
{listOfReview.map((sub) => {
const { reviewId, rating, name, occupation, review } = sub;
return (
<div key={reviewId} className="content-container">
<div className='reviewer-rating'>
<div className="static-review">
{[...Array(5)].map((star, index) => {
return <FontAwesomeIcon key={index} className="star" icon="star" />
})}
</div>
<div className="dynamic-review">
{[...Array(rating)].map((star, index) => {
return <FontAwesomeIcon key={index} className="star" icon="star" />
})}
</div>
</div>
<div className="user-description">
<h3>{occupation}</h3>
<h4>{name}</h4>
<p>{review}</p>
</div>
</div>
)
})}
<button className="submit" onClick={() => setShowModal(true)}>LEAVE AREVIEW</button>
</div>
</div>
);
})}
<Modal isOpen={showModal} ariaHideApp={false}>
<DisplayModal onClick={(value) => setShowModal(value)} />
</Modal>
</>
);
};
I would suggest you try and use context api instead of just relying on useReducer.

Resources