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

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

Related

How Do I give dynamic colors to the each list here

import React, { useState, useEffect } from "react";
import "./style.css";
const getLocalItem = () => {
let list = localStorage.getItem("lists");
console.log(list);
if (list) {
return JSON.parse(list);
} else {
return [];
}
};
function App() {
const [text, setText] = useState("");
const [task, setTask] = useState(getLocalItem());
const changeText = (e) => {
setText(e.target.value);
};
const submitHandler = (e) => {
console.log("submited");
e.preventDefault();
setTask([...task, text]);
setText("");
};
const removeTask = (a) => {
const finalData = task.filter((curEle, index) => {
return index !== a;
});
setTask(finalData);
};
useEffect(() => {
localStorage.setItem("lists", JSON.stringify(task));
}, [task]);
return (
<>
<form onSubmit={submitHandler} className='form'>
<div className="action" >
<div >
<input
className="input"
type="text"
value={text}
onChange={changeText}
placeholder='add task...'
/>
</div>
<button type="submit" className="button" >
Add todo
</button>
</div>
<div className="listsData">
{task.map((value, index) => {
return (
<>
<div key={index}>
{value}
</div>
</>
);
})}
</div>
</form>
</>
);
}
export default App;
On adding each item I want a different color for each list. Currently, I am fetching list data from localstorage while fetching also it should remain same. which is working but the dynamic colors is what I need for each list. Any ideas or dynamic logics??
Let me know if u need more details regarding my code if u doont understand something

Show and hide selected div element via .map()

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

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.

While on click on the span, how can we get the correct span name by using useRef()

I am getting the nominee_name as the last span name even after clicking on the right span element. How can I get the correct span name from here <span className="countname" key={data.nomineename} ref={nominee_name} onClick={handleClick}>{data.nomineename}</span>.
The above span is iterated based on the data received.
import React, { useRef, useEffect, useState } from "react";
import Axios from "axios";
const Dashboard = props => {
const [nominationCount, setNominationCount] = useState([]);
const [nameText, setNameText] = useState("");
let nominee_name = useRef(null);
const isMounted = useRef(false);
useEffect(() => {
isMounted.current = true;
return () => isMounted.current = false;
}, []);
useEffect(() => {
const fetchData = async () => {
try {
const res = await Axios.get('http://localhost:8000/service/nominationcount');
if (isMounted.current) {
setNominationCount(res.data);
console.log("Nomination count data from server :" + res.data);
}
} catch (e) {
console.log(e);
}
}
fetchData();
}, []);
const handleClick = () => {
setNameText(nominee_name.current.outerText);
}
return (
<div className="space_1 tile">
<h3>Nominations Count</h3>
<div className="grid-container">
{
!nominationCount.length && (<div className="nonominationdata">No nominations count to display !</div>)
}
{
nominationCount.map(data => (
<div key={data.id}>
<div onClick={() => {setOpen(!open); }} className="count badge" >
<span className="badgenumber" value={data.count} key={data.count}>{data.EmailCount}</span>
<span className="countname" key={data.nomineename} ref={nominee_name} onClick={handleClick}>{data.nomineename}</span>
</div>
</div>
))
}
</div>
</div>
<Modal
open={open}
onClose={() => {
setOpen(false);
}}
className={classes.modal}>
<form className={classes.form}>
<label className={classes.label}>Confirm winner {nameText}</label>
<input className={classes.submit} type="submit" value="Confirm" />
</form>
</Modal>
)
}
not sure you want to use ref here.
Just pass the name into your click handler:
(
<span className="countname" key={data.nomineename}
onClick={()=>setNameText(data.nomineename)}>{data.nomineename}</span>
)

React: How to update state for just one element, rather than batch update

I am a beginner with React. I have a project I'm working on with some sample travel tours. I would like to use a "read more/show less" feature for the description of each tour. The read more/show less button is toggling, but it's showing more or less description for all of the tours when clicked, when I want it to just toggle the tour that's clicked. In other words, it's updating the state for ALL tours, rather than just the one that's clicked. Hopefully that makes sense. Please help! Thanks in advance.
import React, { useState, useEffect } from 'react';
import './index.css';
const url = 'https://course-api.com/react-tours-project';
const Tour = () => {
const [tourItem, setTourItem] = useState('');
const removeItem = (id) => {
let newList = tourItems.filter((item) => item.id !== id);
setTourItem(newList);
};
const [fetchingData, setFetchingData] = useState(true);
useEffect(() => {
const abortController = new AbortController();
const fetchUrl = async () => {
try {
const response = await fetch(url, {
signal: abortController.signal,
});
if (fetchingData) {
const data = await response.json();
setTourItem(data);
}
setFetchingData(false);
} catch (e) {
console.log(e);
}
};
fetchUrl();
return () => {
//cleanup!
abortController.abort();
};
});
const tourItems = Object.values(tourItem);
const [readMore, setReadMore] = useState(false);
return (
<>
{tourItems.map((item) => {
return (
<div key={item.id}>
<article className='single-tour'>
<img src={item.image} alt={item.name} />
<footer>
<div className='tour-info'>
<h4>{item.name}</h4>
<h4 className='tour-price'>
${item.price}
</h4>
</div>
{readMore ? (
<p>
{item.info}
<button
onClick={() => setReadMore(false)}
>
Show Less
</button>
</p>
) : (
<p>
{item.info.slice(0, 450) + '...'}
<button
onClick={() => setReadMore(true)}
>
Read More
</button>
</p>
)}
<button
className='delete-btn'
onClick={() => removeItem(item.id)}
>
Not Interested
</button>
</footer>
</article>
</div>
);
})}
</>
);
};
export default Tour;
Good question! It happened because you share the readMore state with all of the tour items. You can fix this by encapsulating the tour items into a component.
It should look something like this;
The component that encapsulates each tour items
import React, {useState} from "react";
import "./index.css";
const SpecificTourItems = ({item, removeItem}) => {
const [readMore, setReadMore] = useState(false);
return (
<div key={item.id}>
<article className="single-tour">
<img src={item.image} alt={item.name} />
<footer>
<div className="tour-info">
<h4>{item.name}</h4>
<h4 className="tour-price">${item.price}</h4>
</div>
{readMore ? (
<p>
{item.info}
<button onClick={() => setReadMore(false)}>Show Less</button>
</p>
) : (
<p>
{item.info.slice(0, 450) + "..."}
<button onClick={() => setReadMore(true)}>Read More</button>
</p>
)}
<button className="delete-btn" onClick={() => removeItem(item.id)}>
Not Interested
</button>
</footer>
</article>
</div>
);
};
export default SpecificTourItems;
the component that fetch & maps all the tour items (your old component :))
import React, {useState, useEffect} from "react";
import SpecificTourItems from "./SpecificTourItems";
const url = "https://course-api.com/react-tours-project";
const Tour = () => {
const [tourItem, setTourItem] = useState("");
const removeItem = (id) => {
let newList = tourItems.filter((item) => item.id !== id);
setTourItem(newList);
};
const [fetchingData, setFetchingData] = useState(true);
useEffect(() => {
const abortController = new AbortController();
const fetchUrl = async () => {
try {
const response = await fetch(url, {
signal: abortController.signal,
});
if (fetchingData) {
const data = await response.json();
setTourItem(data);
}
setFetchingData(false);
} catch (e) {
console.log(e);
}
};
fetchUrl();
return () => {
//cleanup!
abortController.abort();
};
});
const tourItems = Object.values(tourItem);
const [readMore, setReadMore] = useState(false);
return (
<>
{tourItems.map((item, key) => {
return (
<SpecificTourItems item={item} removeItem={removeItem} key={key} />
);
})}
</>
);
};
export default Tour;
I hope it helps, this is my first time answering question in Stack Overflow. Thanks & Good luck!

Resources