Active object of the mapped array show center in scroll items react - reactjs

I have mapped my data into timetable and showed them date wise (30 days) in horizontal scroll. I have set current date data as active element. But when the date is far like 22nd position and the view is only bound for 5 objects, how can I show the active object data (22nd object) in the center of my screen through smooth scroll on page load? (picture reference attached)
Here is my current code:
import React, { useRef, useEffect } from "react";
const DashboardData = ({
timetable,
sahriToday,
iftarToday,
currentN,
currentD,
setCurrentD,
}) => {
const handleClick = (id) => {
setCurrentD(id);
};
const dateFunc = (dhur, id) => {
let eDate = new Date(dhur);
if (currentN.getDate() === eDate.getDate()) {
setCurrentD(id);
}
}
const myRef = useRef(currentD);
useEffect(() => {
myRef.current?.scrollIntoView ({
behavior: "smooth",
block: "end"
});
}, [currentD])
console.log(currentD);
return (
<>
<div className="mother">
{timetable.map((timetable) => (
<>
<div
className={ currentD === timetable.id ? "dayboom active" : "dayboom" }
onClick={() => handleClick(timetable.id)}
ref={myRef}
>
<h3 className="weekday">{timetable.weekday}</h3>
<h3 className="monthdate">{timetable.day}</h3>
{dateFunc(timetable.edate, timetable.id)}
</div>
</>
))}
</div>
<div className="timeToday">
<div className="sahriToday">
<div>
<h2>
Sahri <span>Time</span>
</h2>
<h3>{sahriToday[0].sahri}</h3>
</div>
</div>
<div className="iftarToday">
<div>
<h2>
Iftar <span>Time</span>
</h2>
<h3>{iftarToday[0].iftar}</h3>
</div>
</div>
</div>
</>
);
};
export default DashboardData;
I have tried scrollIntoView() but that works on the full map data, not the specific one that is active.

If you don't need to save the reference for each of the elements in the map you can try adding a ref only for the element you want the function scrollIntoView do its thing. Something like:
ref={currentD === timetable.id - 2 ? myRef : null}

Related

React - setState in function gets executed X times - Updated

The bounty expires in 2 days. Answers to this question are eligible for a +50 reputation bounty.
Cinc-B is looking for an answer from a reputable source:
Fixing the re-render situation.
Quick Overview:
I have a context with meals in it. That meals gets used in the MealDetails-Component. In this component is a list filled with a certain amount of Food-Components. If you click on the X in the Food component it gets removed otherwise you go to the FoodDetailsPage
Then when I press the button a function called deleteFood gets executed which sets the meals state new without that food. The problem is that the setState inside the function doesn't get called once but twice in this component. I tried using it in 2 other components and once it executed only once and in the other it executed 4 times.
Update
My addFood function has a pretty similiar problem. But this one gets called in another component. It adds Food to 2 different states in 2 different contexts and in both contexts the added Value is doubled. One thing I could "find" was that the console in my browser prints the a certain value I console logged inside the setState function a second time, but not via the MealsContext, but via react_devtools_backend.js.
And this only happens with those 2 functions where I have an error.
Update 2
I'm displaying the Food-Components inside the MealsDetails Component which has meals in it which comes from the MealsContext. Could this be the problem?
Update 3
I missed saying it. I have a lot more functions in this MealsContext that are changing the state. One of them is called changeFoodWeight and it gets called in the same component where addFood gets called, but it doesn't show any problems plus the react_devtools_backend.js logs nothing which is great. I added the function to the MealsContext down below.
MealsDetails.tsx
const MealDetails = () => {
const navigate = useNavigate();
let { id } = useParams();
const { meals } = useMeals();
const style = { "--percentage": "75%" } as React.CSSProperties;
return (
<div className="MealDetails">
<header className="BarcodeScannerHeader">
<ArrowBackIcon
onClick={() => {
navigate("/");
}}
sx={{ fontSize: 35 }}
></ArrowBackIcon>
<div className="HeaderText">{meals.meals[Number(id)].name}</div>
</header>
<div className="MealDetailsContent">
<span className="label">{meals.meals[Number(id)].calories} ate</span>
<hr className="SolidMealDetails"></hr>
{meals.meals[Number(id)] != null
? meals.meals[Number(id)].food.map((food: any, index: number) => <Food key={index} food={food}/>)
: "Not loading"}
</div>
<Link className="Link" to={`/AddFood/${id!}`}>
<div className="MealDetailsAddFoodButton">
<img className="SVG" id="SVG" src={SVG} alt="+" />
</div>
</Link>
</div>
);
};
MealsContext.tsx
let initialState: MealsType = {
calories: 0,
carbs: 0,
meals: [
{
calories: 0,
carbs: 0,
food: []
}
],
};
const MealsProvider = ({ children }: any) => {
const [meals, setMeals] = useState<MealsType>(initialState);
const addFood = async (
id: number,
addedFood: FoodType,
selectedLocation: string,
date: Date
) => {
//Removed the fetch for readability
console.log("func")
setMeals((prevMeals) => {
console.log("state");
const updatedMeals = prevMeals;
let foodExists = false;
updatedMeals.calories += addedFood.kcal;
updatedMeals.carbs += addedFood.carbs;
updatedMeals.meals[id].calories += addedFood.kcal;
updatedMeals.meals[id].carbs += addedFood.carbs;
updatedMeals.meals[id].food.forEach((food : FoodType) => {
if(food.code === addedFood.code){
food.kcal += addedFood.kcal;
food.carbs += addedFood.carbs;
foodExists = true;
}
})
if(!foodExists){
updatedMeals.meals[id].food.push(addedFood);
}
return {...prevMeals,updatedMeals};
});
setUser(prevUser => {
const updatedStorage = prevUser.storage;
updatedStorage.map((storage : any) => {
if(storage.location === selectedLocation){
storage.storedFood.map((storedFood : any) => {
if(storedFood.code === addedFood.code){
storedFood.weight -= addedFood.weight;
}
})
}
})
return {...prevUser, updatedStorage};
})
};
const changeFoodWeight = async (
id: number,
foodDiff: any,
selectedLocation: string,
date: Date,
newWeight: number
) => {
//Removed the fetch for readability
console.log("func");
setMeals((prevMeals) => {
console.log("state");
const updatedMeals = prevMeals;
updatedMeals.calories += foodDiff.kcalDiff;
updatedMeals.carbs += foodDiff.carbsDiff;
updatedMeals.meals[id].calories += foodDiff.kcalDiff;
updatedMeals.meals[id].carbs += foodDiff.carbsDiff;
updatedMeals.meals[id].food.forEach((food) => {
if (food.code === foodDiff.code) {
food.kcal += foodDiff.kcalDiff;
food.carbs += foodDiff.carbsDiff;
}
});
return {...prevMeals,updatedMeals};
});
setUser(prevUser => {
const updatedStorage = prevUser.storage;
updatedStorage.map((storage : any) => {
if(storage.location === selectedLocation){
storage.storedFood.map((storedFood : any) => {
if(storedFood.code === foodDiff.code){
storedFood.weight -= foodDiff.weightDiff;
}
})
}
})
return {...prevUser, updatedStorage};
})
};
const deleteFood = (id: number, deletedFood: FoodType) => {
setMeals((prevMeals) => {
const updatedMeals = prevMeals;
updatedMeals.calories -= deletedFood.kcal;
updatedMeals.carbs -= deletedFood.carbs;
updatedMeals.meals[id].calories -= deletedFood.kcal;
updatedMeals.meals[id].carbs -= deletedFood.carbs;
for(let i = 0; i < updatedMeals.meals[id].food.length; i++){
if(updatedMeals.meals[id].food[i].code === deletedFood.code){
updatedMeals.meals[id].food.splice(i, 1);
}
}
return {...prevMeals,updatedMeals};
});
};
return (
<MealsContext.Provider
value={{
meals,
deleteFood
}}
>
{children}
</MealsContext.Provider>
);
};
const useMeals = () => useContext(MealsContext);
export { MealsProvider, useMeals };
};
Food.tsx
const Food = ({ food }: any) => {
const location = useLocation();
const {deleteFood} = useMeals();
return (
<Link className="FoodContent Link" to={`/FoodDetails/`} state= {{ food: food , location: location.pathname}} >
<div className="Food">
<div className="FoodDetail">
<div className="FoodNameFoodDelete">
<div className="FoodName">{food.name}</div>
<img className="FoodDelete SVG" onClick={(e : any) => {e.preventDefault(); deleteFood(parseInt(location.pathname[location.pathname.length - 1]), food)}} src={SVG}></img>
</div>
<div className="FoodGram-FoodKcal">
<div className="FoodBrand-FoodGram">
<div className="FoodBrand">{food.brand + ","} </div>
<div className="FoodGram">
{food.weight ? food.weight + " g" : 100 + " g"}{" "}
</div>
</div>
<div className="FoodKcal">{food.kcal} kcal</div>
</div>
</div>
<div className="FoodNutritions">
<div className="FoodNutrition">
<div className="FoodNutritionGrams">{Math.round(food.carbs*10)/10} g</div>
<div className="FoodNutritionText">Carbs</div>
</div>
<div className="FoodNutrition">
<div className="FoodNutritionGrams">{Math.round(food.protein*10)/10} g</div>
<div className="FoodNutritionText">Protein</div>
</div>
<div className="FoodNutrition">
<div className="FoodNutritionGrams">{Math.round(food.fat*10)/10} g</div>
<div className="FoodNutritionText">Fat</div>
</div>
</div>
</div>
</Link>
);
};
The issue stems from wrapping your entire component with a Link tag which is causing some internal state-collision between your setMeals call and the Link state pass.
<Link className="FoodContent Link" to={`/FoodDetails`} state= {{ food: food , location: location.pathname}}>
<div>Block-level Elements...</div>
</Link>
Link is an inline element and is simply meant to wrap some text (or a word) that's to be used as a link somewhere. The resulting view can then access the passed-in state.
From the React Router Link v6 documentation on how to handle passing state:
The state property can be used to set a stateful value for the new location which is stored inside history state. This value can subsequently be accessed via useLocation().
<Link to="new-path" state={{ some: "value" }} />
You can access this state value while on the "new-path" route:
let { state } = useLocation();
In your situation, the Link wrap is trying to passing the food state object into the entire Food component as you are updating your entire context state (useMeals).
To better visualize the problem, I reproduced your issue "before" and "after".
In the "before" sandbox, clicking Delete Cookies removes more items than anticipated due to this issue. In the "after" sandbox the same steps work as expected. This is because only the actual link is wrapped in Link.
Essentially, you need to use your Link wrap more like this so the state it's trying to pass doesn't overwrite the state that's in the component:
const Food = ({ food }: any) => {
const location = useLocation();
const {deleteFood} = useMeals();
return (
<div className="FoodContent">
<div className="Food">
<div className="FoodDetail">
<div className="FoodNameFoodDelete">
<Link className="FoodName Link" to={`/FoodDetails/`} state= {{ food: food , location: location.pathname}} >{food.name}</Link> {/* <--- Just wrap the actual link */}
<img className="FoodDelete SVG" onClick={(e : any) => {e.preventDefault(); deleteFood(parseInt(location.pathname[location.pathname.length - 1]), food)}} src={SVG}></img>
</div>
<div className="FoodGram-FoodKcal">
<div className="FoodBrand-FoodGram">
<div className="FoodBrand">{food.brand + ","} </div>
<div className="FoodGram">
{food.weight ? food.weight + " g" : 100 + " g"}{" "}
</div>
</div>
<div className="FoodKcal">{food.kcal} kcal</div>
</div>
</div>
<div className="FoodNutritions">
<div className="FoodNutrition">
<div className="FoodNutritionGrams">{Math.round(food.carbs*10)/10} g</div>
<div className="FoodNutritionText">Carbs</div>
</div>
<div className="FoodNutrition">
<div className="FoodNutritionGrams">{Math.round(food.protein*10)/10} g</div>
<div className="FoodNutritionText">Protein</div>
</div>
<div className="FoodNutrition">
<div className="FoodNutritionGrams">{Math.round(food.fat*10)/10} g</div>
<div className="FoodNutritionText">Fat</div>
</div>
</div>
</div>
</div>
);
};
Here are the sandboxes for reference:
"Before" Sandbox
"After" Sandbox

unable to select another option after selection with react select

Updated code: Im trying to first display carsList and only when selectedMake is selected, I would update the state with the result from filter and show another array. I tried storing carsList in updatedCarsList so it has all the cars on page load but Im missing something here.
CarOffers.jsx
const CarOffers = () => {
const [carsList, setCarsList] = useState([]);
const [updatedCarsList, setUpdatedCarsList] = useState([]);
const [selectedMake, setSelectedMake] = useState(undefined);
const getCars = () => {
axios.get(url)
.then((response) => {
return setCarsList(response.data)
})
}
const handleMakeChange = (select) => {
setSelectedMake(select.value)
}
const applyFilters = () => {
let updatedCarsList = carsList
if(selectedMake) {
updatedCarsList = carsList.filter(car => car.make === selectedMake)
setUpdatedCarsList(updatedCarsList);
} else {
setUpdatedCarsList(carsList)
}
}
useEffect(() => {
getCars()
applyFilters()
}, [ selectedMake ]);
return (
<div className="mka__wrapper-car-offers">
<div className="mka__container">
<div className="mka__content-car-offers">
<div className="mka__content-grid-offers">
<div className="item1">
< CarSlider/>
<div className="mka-responsive-item">
< DisplayCars/>
< SortingCars/>
< CarAlignment/>
</div>
</div>
<div className="item2">
<div className="mka__side-bar-divider">
< Search
carsList={carsList}/>
</div>
<div>
< FilterSideBar
carsList={carsList}
handleMakeChange={handleMakeChange} />
</div>
</div>
<div className="item3">
<Cars updatedCarsList={updatedCarsList}/>
</div>
</div>
</div>
</div>
</div>
)
}
export default CarOffers;
Cars.jsx
const Cars = ({ updatedCarsList }) => {
return (
<div className='mka__cars-grid'>
{updatedCarsList.map(car =>
<CarsItem key={car.id} car={car}/>)}
</div>
)
}
export default Cars
CarItem.jsx
const CarsItem = ({car: {year,month,transmission,mileage,price,title,link}}) => {
return (
<Fragment>
<div className="cars-item_wrapper">
<div className="cars-item_image">
<img src={link} alt="car" />
</div>
<div>
<a
className="cars-item_car-title"
href="/"
>
{title}
</a>
</div>
<div className=" cars-item_separator"></div>
<p className="cars-item_car-text">{price}</p>
</div>
</Fragment>
)
}
export default CarsItem
Move your applyFilters above getCars
Does Select need to be in <>
distinctBy... urgh.. use Set const unique = [...new Set(data.map(item => item.value))]
applyFilters... axios is async, but your setting a value so state doesn't update so no re-render? Maybe.
selectedMake - don't use null as a default, use undefined.
Hope that helps, feels like a state management issue.
... think its this ....
You are using carsList as your list of cars, however you are setting the value of carsList with setCarsList(updatedCarsList)... updatedCarsList is a filtered list of cars... only car => car.make === selectedMake so once you've selected a make your carList is only cars with the selected make.
Solution is to
Either separate the list from the filtered list
or preferably keep list, but pass the filtered state to the component that needs it... but not update state of the original list by calling setCarsList(updatedCarsList);
if (selectedMake){
updatedCarsList = updatedCarsList.filter(
car => car.make === selectedMake
)
};
setCarsList(updatedCarsList);

Toggle click on a mapped data React JS

Here is my main component in which I render a component based on the data I receive from API:
<div className="PlayersContainer">
{players.map((player) => {
return (
<PlayerPhoto
key={player._id}
{...player}
selected={selected}
something={(e) => {
setSelected(!selected);
}}
/>
);
})}
</div>
and here is inside my component:
export default function PlayerPhoto({ name, img, something, selected }) {
return (
<article
className={selected ? styles.Fade : styles.PlayerBox}
onClick={something}
>
<img src={img} alt={name} className={styles.PlayerPhoto} />
<p className={styles.PlayerName}>{name}</p>
</article>
);
}
What I'm trying to do is that when the user clicks on a player it shoud take the fade class and become fade, and when the user clicked again it should returns to its normal class.
the problem is when the user clicks on a player all players get the fade class and become selected. How can I get their id and add the fade class to that specific player id?
Why are you not move this logic to PlayerPhoto?
export default function PlayerPhoto({ name, img }) {
const [selected, setSelected] = React.useState(false);
return (
<article
className={selected ? styles.Fade : styles.PlayerBox}
onClick={()=>setSelected((prevValue)=>!prevValue}
>
<img src={img} alt={name} className={styles.PlayerPhoto} />
<p className={styles.PlayerName}>{name}</p>
</article>
);
}
Create a state to maintain the selected id and then compare the selectedId and player id for the selected prop boolean value. if the id matches , it will change the className.
const [selectedId, setSelectedId] = useState(null);
<div className="PlayersContainer">
{players.map((player) => {
return (
<PlayerPhoto
key={player._id}
{...player}
selected={player._id === selectedId}
something={() => setSelected(player._id)}
/>
);
})}
</div>;

How to Create Show/Hide button when Looping in React

I am working on a development site and am having an issue. The issue is that I am looping over the data file in order to create some project cards. Each project card has a show more/show less button to display/hide card descriptions.
My problem is that the current setup is mapping over the data and causing it so that whenever one gets clicked, all three either open or close simultaneously. Please help me to fix this issue. Relevant code is shown below:
Data example:
{
name: "Hot in the Biscuit",
id: "3a34",
image: "/images/bonnie.jpg",
description: "A multi-page front-end business website for a local restaurant in Koh Samui, Thailand. Custom design built with vanilla JavaScript, HTML and CSS.",
link: "https://www.xxxxxxxxxxxxx.com",
date: "2021",
github: "https://github.com/xxxxxxxxxxxxxxxxxxxxxx"
},
Hero file where Showcase Component is rendered:
<h1>Featured Projects</h1>
<div>
<Showcase/>
</div>
Showcase where cards are created (UNNECCESSARY CODE REMOVED - classes and image):
const Showcase = () => {
const {readMore, setReadMore} = useContext(HeroContext)
const {toggleMenu} = useContext(NavbarContext)
return(
<>
{showcase.map((item) => {
const {id, name, image, link, github, description, date} = item;
return (
<div key={id}>
<div>
{!toggleMenu &&
<div>
<Image/>
</div>
}
</div>
<div>
<div>
<h2>{name} | {date}</h2>
</div>
<div>
<h4>{ readMore[id] ? <-- THIS IS WHERE YOU NEED AN ID
description :
`${description.substring(0, 100)}...`
} <button key={id} onClick={() => setReadMore(!readMore)}>{readMore[id] ? "Show Less" : "Show More"}</button>
</h4>
</div>
<div>
<a href={github}>
<FiGithub/>
</a>
<a href={link}>
<h4 >See For Yourself! →</h4>
</a>
</div>
</div>
</div>
)
})}
</>
)
}
export default Showcase
So I just need some help on figuring out how to set it up so that each button knows which card is being clicked and only that button open. Thank you very much for helping me. I appreciate your time and help immensely.
Bodi
It will be easier if you split showcase item to a new component.
const ShowCaseItem = ({ data }) => {
const { toggleMenu } = useContext(NavbarContext)
const [readMore, setReadMore] = useState(false)
const { id, name, image, link, github, description, date } = data;
return (
<div key={id}>
<div>
{!toggleMenu &&
<div>
<Image />
</div>
}
</div>
<div>
<div>
<h2>{name} | {date}</h2>
</div>
<div>
<h4>{readMore ?
description :
`${description.substring(0, 100)}...`
} <button key={id} onClick={() => setReadMore(!readMore)}>{readMore ? "Show Less" : "Show More"}</button>
</h4>
</div>
<div>
<a href={github}>
<FiGithub />
</a>
<a href={link}>
<h4 >See For Yourself! →</h4>
</a>
</div>
</div>
</div>
)
}
const Showcase = () => {
const { readMore, setReadMore } = useContext(HeroContext)
return (
<>
{showcase.map((item) => <ShowCaseItem data={item} />)}
</>
)
}
export default Showcase
You should update the HeroContext state to hold a reference to the ids that are shown/hidden.
Example:
const [readMore, setReadMore] = useState({});
const readMoreToggler = (id) => setReadMore(state => ({
...state,
[id]: !state[id], // <-- toggle boolean value
}));
// context value
{
readmore,
setReadMore: readMoreToggler, // pass readMoreToggler as setReadMore
}
...
const { readMore, setReadMore } = useContext(HeroContext);
...
{showcase.map((item) => {
const {id, name, image, link, github, description, date} = item;
return (
<div key={id}>
<div>
...
</div>
<div>
...
<div>
<h4>
{readMore[id] // <-- check by id if toggled true|false
? description
: `${description.substring(0, 100)}...`
}
<button
onClick={() => setReadMore(id)} // <-- pass id to toggle
>
{readMore[id] ? "Show Less" : "Show More"} // <-- check by id if toggled true|false
</button>
</h4>
</div>
<div>
...
</div>
)
})}

Reset pagination to the first page by clicking a button outside the component

I'm using material UI usePagination hook to create a custom pagination component, so far so good, the functionality works as expected but I was wondering how I can be able to reset the pagination to the first page by triggering a button that is not part of the pagination component.
Does anyone has an idea on how to trigger that?
This is my component.
import React from "react";
import PropTypes from "prop-types";
import { usePagination } from "hooks";
function arrow(type) {
return (
<i
className={`fa fa-chevron-${
type === "next" ? "right" : "left"
} page-icon`}
/>
);
}
function Pagination({ data, itemCount, onChange }) {
const { items } = usePagination({
count: Math.ceil(data.length / itemCount, 10),
onChange
});
return (
<nav aria-label="Paginator">
<ul className="pagination-component">
{items.map(({ page, type, selected, ...item }, index) => {
let children;
if (type === "start-ellipsis" || type === "end-ellipsis") {
children = "…";
} else if (type === "page") {
children = (
<button
type="button"
automation-tag={`page-${page}`}
className={`page-button ${selected ? "selected" : ""}`}
{...item}
>
{page}
</button>
);
} else {
children = (
<button
automation-tag={type}
className="page-button"
type="button"
{...item}
>
<span className="d-none">{type}</span>
{arrow(type)}
</button>
);
}
return (
// eslint-disable-next-line react/no-array-index-key
<li key={index} className="page-item">
{children}
</li>
);
})}
</ul>
</nav>
);
}
What I'm trying is to create a select component that the onChange function will sort the data, depending on the selection, but when the data is sorted I want to return the pagination component to the first page
const TableVizContainer = props => {
const [currentPage, setCurrentPage] = useState(1);
const [sortColumn, setSortColumn] = useState(1);
const [range, setRange] = useState({
start: 0,
end: 25
});
const onChangePage = (_event, page) => {
setCurrentPage(page);
setRange({
start: 25 * (page - 1),
end: 25 * page
});
};
const onSelectChange = event => {
const { value } = event.target;
setCurrentPage(1);
setSortColumn(parseInt(value, 10));
};
return (
<div
className="table-viz-container container-fluid my-4 float-left"
automation-tag={`table-viz-${automationId}`}
>
<div className="d-flex justify-content-between mb-3 leaderboard-meta">
<span className="leaderboard-title">{visualization.title}</span>
<div className="mr-5">
<label htmlFor="sort-table-select">
Sort By:
<select
id="sort-table-select"
onChange={onSelectChange}
value={sortColumn}
>
{visualization.columns.map((column, index) => {
const uniqueId = uuidv1();
return (
<option key={uniqueId} value={index}>
{setSelectValue(column, visualization.metrics)}
</option>
);
})}
</select>
</label>
</div>
</div>
<div className="d-block d-sm-flex justify-content-between align-items-center my-2 px-2">
<span className="page-items-count" automation-tag="pagination-count">
{`Showing ${range.start === 0 ? 1 : range.start + 1} - ${
range.end <= visualization.rows.length
? range.end
: visualization.rows.length
} of ${visualization.rows.length}.`}
</span>
<Pagination
currentPage={currentPage}
data={visualization.rows}
itemCount={25}
onChange={onChangePage}
/>
</div>
</div>
);
};
Does anyone has an idea on how to reset and move the pagination page to the first one without clicking the component?
There are two ways.
1. Passing Props
Let's just say you have a function called jump() and passing 1 as an argument will reset the pagination. So, you can pass the jump function as a property and reuse that on other components.
function jump(){
setCurrentPage(1)
}
<MyCompnent resetPage={jump} />
// MyComponent
function MyComponent({resetPage}){
return (
<button onClick={resetPage(1)}></button>
)
}
2. On Changing Route
You can reset your pagination when your route will change. For example, you are using a router npm package and that package has a method called onChange or routeChangeStart. With those methods or after creating that method you can implement a function like below.
Router.events.on("routeChangeStart", () => {
jump(1);
});

Resources