Increment counter and make other counters 0 - reactjs

I have created a counter component and using it in parent component four times.
Below is the code from Counter component
function NoTravellers({ label, chooseTraveller }) {
const [count, setCount] = useState(0);
const reduceCount = () => {
if (count === 0) {
return;
} else {
setCount((count) => count - 1);
}
};
const increaseCount = () => {
if (count >= 2) {
return;
} else {
setCount((count) => count + 1);
}
};
useEffect(() => {
if (count > 0) {
chooseTraveller(count);
}
}, [count]);
return (
<div className="flex justify-around mb-8 w-1/3">
<div>
<span className="font-semibold text-sm">{label}</span>
</div>
<div className="flex space-x-4 justify-center">
<MinusCircleIcon
className="w-6 h-6 text-[#33C5B4]"
onClick={reduceCount}
/>
<span className="text-base text-[#565656] font-semibold">{count}</span>
<PlusCircleIcon
className="w-6 h-6 text-[#33C5B4]"
onClick={increaseCount}
/>
</div>
</div>
);
}
export default NoTravellers;
And I'm using it in parent component like below -
<div className="flex flex-col justify-center">
<div className="flex justify-around">
<NoTravellers label="Adults" chooseTraveller={(traveller) => chooseTravellers(traveller, 1)} />
<NoTravellers label="Students" chooseTraveller={(traveller) => chooseTravellers(traveller, 2)} />
</div>
<div className="flex justify-around">
<NoTravellers label="Seniors" chooseTraveller={(traveller) => chooseTravellers(traveller, 3)} />
<NoTravellers label="Childrens" chooseTraveller={(traveller) => chooseTravellers(traveller, 4)} />
</div>
</div>
What I needed if users increase the value of one counter, the other three counters value will reset to 0.
How can I achieve this?
Thanks in advance....

The simplest solution is pulling the counts up into the parent that way you can easily modify them in one place. Then just pass the count down into each child.
If you store the state in the children modifying it together is difficult.
const Parent = () => {
const [counts, setCounts] = useState([0,0,0]);
const updateCount = (idx, count) => {
const newCounts = [0,0,0]
newCounts[idx] = count;
setCounts(newCounts);
};
return <>
{counts.map((count,idx) => <Child count={count} update={(count) => updateCount(idx, newCount)}/>}
</>
};
const Child = ({count, update}) => {
return <button onClick={() => update(count+1)}>{count}</button>
}
^ this is not tested code I didn't setup a real react project but you should be able to use the same concept in your setup.

Related

React is complaining that I'm causing too many re-renders

The renderDialogue function should render Dialogue when the window size is less than 654px, otherwise it should update the state of expandMenu to false. However, React is saying there are too many re-renders. How to solve this?
const [expandMenu, setExpandMenu] = useState(false);
const handleOpenMenu = () => {
setExpandMenu(!expandMenu);
};
const renderDialogue = () => {
if (window.innerWidth < 654) {
return (
<Dialog
open={expandMenu}
handler={() => handleOpenMenu()}
size={"xl"}
className={"bg-transparent shadow-none "}
>
<DialogBody>
<div className={"relative"}>
<img
src={"/imgFolder2/cloud.webp"}
className={"h-full float-center"}
/>
<ul className="flex flex-col justify-center h-[75%] gap-5 text-2xl text-center absolute top-[50%] left-[50%] translate-x-[-50%] translate-y-[-50%] w-[55%]">
{NavItems()}
</ul>
</div>
</DialogBody>
</Dialog>
);
} else {
setExpandMenu(false);
return <> </>;
}
};
Here is the NavItems function, which iterates through an array of links.
const NavItems = () => {
return paths.map((path, idx) => (
<li key={idx}>
<Link
href={getPath(idx)}
className={`text-black block ${
isSelectedPath(getName(idx, true))
? "border-b-4 border-buttonBG"
: ""
}`}
onClick={() => {
if (window.innerWidth < 654) setExpandMenu(!expandMenu);
}}
>
{getName(idx)}
</Link>
</li>
));
};
First of all, your component will never re-render when the window size changes. This means that your if-statement where you display something depending on the window width will only fire on first render. Which probably is fine, but not recommended.
Secondly, your error "too many re-renders" is because you invoke setExpandMenu directly in your component. Each time you update your expandMenu state, this component will re-render. Then you update it again, so it will re-render again. See the infinite loop here?
Below is a working example of what you want, including updating on window resize. I've added comments to explain what is happening:
const [expandMenu, setExpandMenu] = useState(false);
const [windowWidth, setWindowWidth] = useState(0)
useEffect(() => { // This will update your state depending on your window size
function updateSize() {
setWindowWidth(window.innerWidth)
}
window.addEventListener('resize', updateSize);
updateSize();
return () => window.removeEventListener('resize', updateSize);
}, []);
const handleOpenMenu = () => {
setExpandMenu(!expandMenu);
};
const renderDialogue = () => {
if (windowWidth > 654 || expandMenu) {
return (
<Dialog
open={expandMenu}
handler={() => handleOpenMenu()}
size={"xl"}
className={"bg-transparent shadow-none "}
>
<DialogBody>
<div className={"relative"}>
<img
src={"/imgFolder2/cloud.webp"}
className={"h-full float-center"}
/>
<ul className="flex flex-col justify-center h-[75%] gap-5 text-2xl text-center absolute top-[50%] left-[50%] translate-x-[-50%] translate-y-[-50%] w-[55%]">
{NavItems()}
</ul>
</div>
</DialogBody>
</Dialog>
);
} else {
// setExpandMenu(false); <--- This line is causing rerenders: Remove it!
return <> </>;
}

DnD-Kit Incorrect behavior while trying to move item to another container in ReactJs

I would appreciate any help with this case, so if you see any minor issue - please, write me. There will be rather a lot of code.
I was trying to implement 'dnd-kit/sortable' into my bug tracker app. I have Kanban board consisting of four repeating column components. I needed to implement dnd-kit to be able to move task cards not only inside of each column, but between columns as well. Current code with sorting task cards in column, but if you try to move a card to any other column - most of the time nothing happens, but sometimes you get the Uncaught TypeError: Cannot read properties of undefined (reading 'id') I red through documentation many times and looked through similar projects in open source, but couldn't find what could be the reason for this bug.
The tasks from TasksContext is object with keys backlog, todo, inProgress, inReview, done and contains array of object. Each object inside of array represents task card.
Dashboard.js
const Dashboard = () => {
const { tasks, setTasks } = useContext(TasksContext)
const [activeId, setActiveId] = useState(null);
const mouseSensor = useSensor(MouseSensor);
const touchSensor = useSensor(TouchSensor);
const sensors = useSensors(mouseSensor, touchSensor)
const fullArray = Array.from(Object.values(tasks).flat())
console.log(fullArray)
const handleDragStart = ({ active }) => setActiveId(active.id);
const handleDragCancel = () => setActiveId(null);
const handleDragEnd = ({active, over}) => {
const { containerId: activeContainer } = active.data.current.sortable
const { containerId: overContainer } = over.data.current.sortable
const oldIndex = tasks[activeContainer].findIndex(obj => obj.id === active.id);
const newIndex = tasks[overContainer].findIndex(obj => obj.id === over.id);
if (active.id !== over.id) {
setTasks((prevTasks) => ({
...prevTasks,
[overContainer]: arrayMove(prevTasks[overContainer], oldIndex, newIndex)
}));
}
setActiveId(null);
}
return (
<div className='relative grid grid-cols-4 gap-6 px-6 grow-0 shrink-0 basis-5/6 overflow-y-scroll'>
<DndContext sensors={sensors} collisionDetection={rectIntersection} onDragStart={handleDragStart} onDragCancel={handleDragCancel} onDragEnd={handleDragEnd}>
<TasksColumn key='to do' title='to do' id='todo' tasks={tasks.todo} />
<TasksColumn key='in progress' title='in progress' id='inProgress' tasks={tasks.inProgress} />
<TasksColumn key='in review' title='in review' id='inReview' tasks={tasks.inReview} />
<TasksColumn key='done' title='done' id='done' tasks={tasks.done} />
<DragOverlay>{activeId ? <TaskCard id={activeId} task={fullArray.filter(task => task?.id === activeId)[0]} /> : null}</DragOverlay>
</DndContext>
</div>
)
}
TasksColumn.js
const TasksColumn = ({ title, id, tasks }) => {
const { setNodeRef } = useDroppable({id});
return (
<div className=''>
<ColumnHeader title={title} id={id} />
<div className="h-3 w-full border-b-2 border-grayDark" />
<SortableContext items={tasks} id={id} strategy={verticalListSortingStrategy}>
<div ref={setNodeRef} className=''>
{tasks.map(task => (
<Draggable key={task.name} id={task.id} task={task} />
))}
</div>
</SortableContext>
</div>
)
}
Draggable.js
const Draggable = ({ id, task }) => {
const { setNodeRef, transform, transition, isDragging, } = useSortable({id});
const style = {
transform: CSS.Translate.toString(transform),
transition,
opacity: isDragging ? 0.5 : 1,
};
return (
<div ref={setNodeRef} style={style}>
<TaskCard id={id} task={task} />
</div>
)
}
TaskCard.js
const TaskCard = ({ id, task }) => {
const { attributes, listeners, setActivatorNodeRef } = useSortable({id});
return (
<div className="py-4 border-b-2 border-grayLight">
<div className="">
<p className="">{task.deadline}</p>
<p className="">{task.priority}</p>
</div>
<ArrowsPointingOutIcon className='rotate-45 w-5 h-5 outline-none' ref={setActivatorNodeRef} {...listeners} {...attributes} />
<p className="">{task.name}</p>
<div className="">
<p className="">{task.author}</p>
<p className="">{task.time}</p>
</div>
</div>
)
}

Passing setState from useState hook as a prop in a firebase function or any other component

Currently not facing any problems I'm just wondering what the best practices for passing setState to a function or component, specifically a firebase function that is declared on another file or a form that is designed to be reusable.
This is a function is from my firebase file
`
export function getResumes({ applicationtState: applicationState }) {
onSnapshot(
query(collection(db, 'applications'), orderBy('timestamp', 'desc')),
(querySnapshot) => {
const arrays = []
querySnapshot.forEach((snap) => {
arrays.push(snap.data())
})
applicationState(arrays)
}
)
}
`
this is how i call the function on my resumes file
`
const [application, setApplication] = useState([])
useEffect(() => {
getResumes({ applicationtState: setApplication })
}, [])
`
In another instance I have a form component which whis i am calling on multiple pages
`
function EducationBox({
highSchoolState,
highSchoolValue,
highSchoolCourseOfStudyState,
highSchoolCourseOfStudyValue,
highSchoolGraduateState,
highSchoolNumberOfYearsCompletedState,
highSchoolNumberOfYearsCompletedValue,
highSchoolHonorsReceivedState,
highSchoolHonorsReceivedValue,
collegeState,
collegeValue,
collegeCourseOfStudyState,
collegeCourseOfStudyValue,
collegeGraduateState,
// collegeGraduateValue,
collegeNumberOfYearsCompletedState,
collegeNumberOfYearsCompletedValue,
collegeHonorsReceivedState,
collegeHonorsReceivedValue,
GradState,
GradValue,
GradCourseOfStudyState,
GradCourseOfStudyValue,
GradGraduateState,
// GradGraduateValue,
GradNumberOfYearsCompletedState,
GradNumberOfYearsCompletedValue,
GradHonorsReceivedState,
GradHonorsReceivedValue,
tradeState,
tradeValue,
tradeCourseOfStudyState,
tradeCourseOfStudyValue,
tradeGraduateState,
// tradeGraduateValue,
tradeNumberOfYearsCompletedState,
tradeNumberOfYearsCompletedValue,
tradeHonorsReceivedState,
tradeHonorsReceivedValue,
}) {
return (
<div className=" flex w-full grid-rows-4 flex-col rounded-[20px] outline outline-2 outline-[#b5b5b5]">
<SchoolItem
SchoolState={highSchoolState}
SchoolValue={highSchoolValue}
CourseOfStudyState={highSchoolCourseOfStudyState}
SchoolCourseOfStudyValue={highSchoolCourseOfStudyValue}
SchoolGraduateState={highSchoolGraduateState}
title={'High School'}
NumberOfYearsCompletedState={highSchoolNumberOfYearsCompletedState}
NumberOfYearsCompletedValue={highSchoolNumberOfYearsCompletedValue}
HonorsReceivedState={highSchoolHonorsReceivedState}
HonorsReceivedValue={highSchoolHonorsReceivedValue}
/>
<SchoolItem
SchoolState={collegeState}
SchoolValue={collegeValue}
CourseOfStudyState={collegeCourseOfStudyState}
SchoolCourseOfStudyValue={collegeCourseOfStudyValue}
SchoolGraduateState={collegeGraduateState}
title={'College'}
NumberOfYearsCompletedState={collegeNumberOfYearsCompletedState}
NumberOfYearsCompletedValue={collegeNumberOfYearsCompletedValue}
HonorsReceivedState={collegeHonorsReceivedState}
HonorsReceivedValue={collegeHonorsReceivedValue}
/>
<SchoolItem
SchoolState={GradState}
SchoolValue={GradValue}
CourseOfStudyState={GradCourseOfStudyState}
SchoolCourseOfStudyValue={GradCourseOfStudyValue}
SchoolGraduateState={GradGraduateState}
title={'Grad School'}
NumberOfYearsCompletedState={GradNumberOfYearsCompletedState}
NumberOfYearsCompletedValue={GradNumberOfYearsCompletedValue}
HonorsReceivedState={GradHonorsReceivedState}
HonorsReceivedValue={GradHonorsReceivedValue}
/>
<SchoolItem
SchoolState={tradeState}
SchoolValue={tradeValue}
CourseOfStudyState={tradeCourseOfStudyState}
SchoolCourseOfStudyValue={tradeCourseOfStudyValue}
SchoolGraduateState={tradeGraduateState}
title={'Trade School'}
NumberOfYearsCompletedState={tradeNumberOfYearsCompletedState}
NumberOfYearsCompletedValue={tradeNumberOfYearsCompletedValue}
HonorsReceivedState={tradeHonorsReceivedState}
HonorsReceivedValue={tradeHonorsReceivedValue}
/>
</div>
)
}
function SchoolItem({
SchoolState,
SchoolValue,
CourseOfStudyState,
SchoolCourseOfStudyValue,
SchoolGraduateState,
title,
NumberOfYearsCompletedState,
NumberOfYearsCompletedValue,
HonorsReceivedState,
HonorsReceivedValue,
}) {
return (
<div className=" flex w-full grid-cols-6 flex-col items-center justify-center px-10 text-center md:flex-row">
<h4 className=" flex text-lg font-bold"> {title}</h4>
<div className=" mx-3 w-full">
<TextInput
value={SchoolValue}
widthPercentage="w-full"
placeHolder="School Name"
onChange={(text) => {
SchoolState(text.target.value)
}}
/>
</div>
<div className=" mx-3 w-full">
<TextInput
widthPercentage="w-full"
placeHolder="Course Of Study"
onChange={(text) => {
CourseOfStudyState(text.target.value)
}}
value={SchoolCourseOfStudyValue}
/>
</div>
<div className=" mx-3 w-full">
<h5>Graduated?</h5>
<RadioButton answerState={SchoolGraduateState} />
</div>
<div className=" mx-3 w-full">
<TextInput
value={HonorsReceivedValue}
widthPercentage="w-full"
placeHolder="Honors Received"
onChange={(text) => {
HonorsReceivedState(text.target.value)
}}
/>
</div>
<div className=" mx-3 w-full">
<TextInput
value={NumberOfYearsCompletedValue}
widthPercentage="w-full"
placeHolder="# of years Completed"
onChange={(text) => {
NumberOfYearsCompletedState(text.target.value)
}}
/>
</div>
</div>
)
}
export default EducationBox
`
I just want to know what best practices is. Please Help!
you can take help of promises and use something like this. and this way your function could become more reusable could not be dependent on any args or state :-
export function getResumes() {
return new Promise((resolve, reject) => {
onSnapshot(
query(collection(db, 'applications'), orderBy('timestamp', 'desc')),
(querySnapshot) => {
const arrays = []
querySnapshot.forEach((snap) => {
arrays.push(snap.data())
})
resolve(arrays)
}
)
})
}
const [application, setApplication] = useState([])
useEffect(() => {
getData()
}, [])
const getData = async () => {
try {
const resumes = await getResumes()
setApplication(resumes)
} catch (err) {
console.log("error", err);
}
}

why cannot I see my modal when I increased number?

Can you please show me why my Modal is not show up when I increased number?
Counting is working but Modal is not working.
I want to show Modal when I increase number. Thank you
function App() {
const [count, setCoutn] = useState(0)
const [showModal, setShowModal] = useState(false)
const increase = () => {
setCoutn(count + 1)
setShowModal(true)
}
return (
<>
{ showModal && < Modal /> }
<p className="text-center mt-5 mt-5 fs-5 count">{count}</p>
<div className="btn-box">
<button className="btn btn-outline-primary" onClick={()=>increase()}>increase</button>
</div>
</>
);
}
const Modal = () => {
return (
<div>
<p className='modal'>Modal</p>
</div>
)
}
Check your demo
instead of :
onClick={()=>increase()}
But I recommend using:
onClick={increase}
Because it will call the function increase directly when clicking the button.
**You cant see modal,because showModal is false default!**
function App() {
const [count, setCoutn] = useState(0)
const [showModal, setShowModal] = useState(false)
const increase = () => {
setCoutn(count + 1)
setShowModal(true)
}
if(showModal) {
return (
<>
<p className="text-center mt-5 mt-5 fs-5 count">{count}</p>
<div className="btn-box">
<button className="btn btn-outline-primary" onClick={()=>increase()}>increase</button>
</div>
</>
);
}else{
return (
<div>
<p className='modal'>Modal</p>
</div>
)
}
}

Use State not updating as expected

Fairly new to react and trying to build a clone of The Movie Database site. I want this toggle switch to change my api call from movies to tv. It starts working after a couple clicks, but then it throws everything off and it's not displaying the correct items anyway. Not really sure what's going on here...or even why it starts working after two clicks. Anyone know whats up with this?
import React, { useState, useEffect } from "react";
import axios from "axios";
import API_KEY from "../../config";
const Popular = ({ imageUri }) => {
// GET POPULAR MOVIES
const [popularMovies, setPopularMovies] = useState("");
const [genre, setGenre] = useState("movie");
console.log(genre);
const getPopular = async () => {
const response = await axios.get(
`https://api.themoviedb.org/3/discover/${genre}?sort_by=popularity.desc&api_key=${API_KEY}`
);
setPopularMovies(response.data.results);
};
useEffect(() => {
getPopular();
}, []);
const listOptions = document.querySelectorAll(".switch--option");
const background = document.querySelector(".background");
const changeOption = (el) => {
let getGenre = el.target.dataset.genre;
setGenre(getGenre);
getPopular();
listOptions.forEach((option) => {
option.classList.remove("selected");
});
el = el.target.parentElement.parentElement;
let getStartingLeft = Math.floor(
listOptions[0].getBoundingClientRect().left
);
let getLeft = Math.floor(el.getBoundingClientRect().left);
let getWidth = Math.floor(el.getBoundingClientRect().width);
let leftPos = getLeft - getStartingLeft;
background.setAttribute(
"style",
`left: ${leftPos}px; width: ${getWidth}px`
);
el.classList.add("selected");
};
return (
<section className="container movie-list">
<div className="flex">
<div className="movie-list__header">
<h3>What's Popular</h3>
</div>
<div className="switch flex">
<div className="switch--option selected">
<h3>
<a
data-genre="movie"
onClick={(e) => changeOption(e)}
className="switch--anchor"
>
In Theaters
</a>
</h3>
<div className="background"></div>
</div>
<div className="switch--option">
<h3>
<a
data-genre="tv"
onClick={(e) => changeOption(e)}
className="switch--anchor"
>
On TV
</a>
</h3>
</div>
</div>
</div>
<div className="scroller">
<div className="flex flex--justify-center">
<div className="flex flex--nowrap container u-overScroll">
{popularMovies &&
popularMovies.map((movie, idX) => (
<div key={idX} className="card">
<div className="image">
<img src={imageUri + "w500" + movie.poster_path} />
</div>
<p>{movie.title}</p>
</div>
))}
</div>
</div>
</div>
</section>
);
};
export default Popular;
You're using the array index as your key prop when you're mapping your array.
You should use an id that is specific to the data that you're rendering.
React uses the key prop to know which items have changed since the last render.
In your case you should use the movie id in your key prop instead of the array index.
popularMovies.map((movie) => (
<div key={movie.id} className="card">
<div className="image">
<img src={imageUri + 'w500' + movie.poster_path} />
</div>
<p>{movie.title}</p>
</div>
));
Also
You're calling the api directly after setGenre. However state changes aren't immediate. So when you're making your api call you're still sending the last movie genre.
Two ways of fixing this:
You could call your function with the genre directly, and change your function so it handles this case:
getPopular('movie');
Or you could not call the function at all and add genre as a dependency of your useEffect. That way the useEffect will run each time the genre change.
useEffect(() => {
getPopular();
}, [genre]);
PS: You should consider splitting your code into more component and not interacting with the DOM directly.
To give you an idea of what it could look like, I refactored a bit, but more improvements could be made:
const Popular = ({ imageUri }) => {
const [popularMovies, setPopularMovies] = useState('');
const [genre, setGenre] = useState('movie');
const getPopular = async (movieGenre) => {
const response = await axios.get(
`https://api.themoviedb.org/3/discover/${movieGenre}?sort_by=popularity.desc&api_key=${API_KEY}`
);
setPopularMovies(response.data.results);
};
useEffect(() => {
getPopular();
}, [genre]);
const changeHandler = (el) => {
let getGenre = el.target.dataset.genre;
setGenre(getGenre);
};
const isMovieSelected = genre === 'movie';
const isTvSelected = genre === 'tv';
return (
<section className="container movie-list">
<div className="flex">
<MovieHeader>What's Popular</MovieHeader>
<div className="switch flex">
<Toggle onChange={changeHandler} selected={isMovieSelected}>
In Theaters
</Toggle>
<Toggle onChange={changeHandler} selected={isTvSelected}>
On TV
</Toggle>
</div>
</div>
<div className="scroller">
<div className="flex flex--justify-center">
<div className="flex flex--nowrap container u-overScroll">
{popularMovies.map((movie) => {
const { title, id, poster_path } = movie;
return (
<MovieItem
title={title}
imageUri={imageUri}
key={id}
poster_path={poster_path}
/>
);
})}
</div>
</div>
</div>
</section>
);
};
export default Popular;
const Toggle = (props) => {
const { children, onChange, selected } = props;
const className = selected ? 'switch--option selected' : 'switch--option';
return (
<div className={className}>
<h3>
<a
data-genre="movie"
onClick={onChange}
className="switch--anchor"
>
{children}
</a>
</h3>
<div className="background"></div>
</div>
);
};
const MovieHeader = (props) => {
const { children } = props;
return (
<div className="movie-list__header">
<h3>{children}</h3>
</div>
);
};
const MovieItem = (props) => {
const { title, imageUri, poster_path } = props;
return (
<div key={idX} className="card">
<div className="image">
<img src={imageUri + 'w500' + poster_path} />
</div>
<p>{title}</p>
</div>
);
};

Resources