Cannot set useState hook value to opposite boolean - reactjs

I'm creating a DatePicker and using useState hook to manage it's visibility. On div click I've added the event listener which changes value, but it didn't work as I expected. It works only the first time, so initial value changes to true, but on second and third clicks this value stays to true and DatePicker stays visible on click.
This is DatePicker
import React, { useState } from 'react';
import OutsideClickHandler from 'react-outside-click-handler';
import { renderInfo, getWeeksForMonth } from './utils';
import {
renderMonthAndYear,
handleBack,
handleNext,
weekdays,
} from '../../utils';
const DatePicker = ({
isOpen,
setIsOpen,
selected,
setSelected,
dayClick,
dayClass,
}) => {
const startDay = new Date().setHours(0, 0, 0, 0);
const [current, setCurrent] = useState(new Date(startDay));
const weeks = getWeeksForMonth(current.getMonth(), current.getFullYear());
function handleClick(date) {
if (date > startDay) {
setSelected(date);
setCurrent(date);
setIsOpen(false);
if (dayClick) {
dayClick(date);
}
}
}
return (
<div className="DatePicker-container">
<div
tabIndex="0"
role="button"
className="DatePicker-info"
onKeyPress={e => {
if (e.which === 13) {
setIsOpen(!isOpen);
}
}}
onClick={e => {
setIsOpen(!isOpen);
}}
>
{renderInfo(selected)}
</div>
{isOpen && (
<OutsideClickHandler onOutsideClick={() => setIsOpen(false)}>
<div className="DatePicker">
<div className="DatePicker__header">
<span
role="button"
onClick={() => handleBack(current, setCurrent)}
className="triangle triangle--left"
/>
<span className="DatePicker__title">
{renderMonthAndYear(current)}
</span>
<span
role="button"
onClick={() => handleNext(current, setCurrent)}
className="triangle triangle--right"
/>
</div>
<div className="DatePicker__weekdays">
{weekdays.map(weekday => (
<div
key={weekday}
className="DatePicker__weekday"
>
{weekday}
</div>
))}
</div>
{weeks.map((week, index) => (
<div
role="row"
key={index}
className="DatePicker__week"
>
{week.map((date, index) =>
date ? (
<div
role="cell"
key={index}
onClick={() => handleClick(date)}
className={dayClass(date)}
>
{date.getDate()}
</div>
) : (
<div
key={index}
className="DatePicker__day--empty"
/>
),
)}
</div>
))}
</div>
</OutsideClickHandler>
)}
</div>
);
};
export default DatePicker;
DateRangePicker which uses two.
import React, { useState } from 'react';
import DatePicker from './DatePicker';
import DatePickerContext from './DatePickerContext';
import './DatePicker.scss';
const DateRangePicker = () => {
const startDay = new Date().setHours(0, 0, 0, 0);
const [isOpen, setIsOpen] = useState(false);
const [isSecondOpen, setIsSecondOpen] = useState(false);
const [selected, setSelected] = useState(new Date(startDay));
const [secondSelected, setSecondSelected] = useState(new Date(startDay));
function dayClass(date) {
if (
selected.getTime() === date.getTime() ||
(date >= selected && date <= secondSelected)
) {
return 'DatePicker__day DatePicker__day--selected';
}
if (date < startDay || date < selected) {
return 'DatePicker__day DatePicker__day--disabled';
}
return 'DatePicker__day';
}
function dayClick(date) {
setSecondSelected(date);
setIsSecondOpen(true);
}
return (
<DatePickerContext.Provider>
<div className="DatePicker-wrapper">
<DatePicker
key={1}
isOpen={isOpen}
setIsOpen={setIsOpen}
selected={selected}
setSelected={setSelected}
dayClick={dayClick}
dayClass={dayClass}
/>
<DatePicker
key={2}
isOpen={isSecondOpen}
setIsOpen={setIsSecondOpen}
selected={secondSelected}
setSelected={setSecondSelected}
dayClass={dayClass}
/>
</div>
</DatePickerContext.Provider>
);
};
export default DateRangePicker;

a) It is a good practice to use functional updates to make sure to use correct "current" value when the next state is dependent on the previous (== current) state:
setIsOpen(currentIsOpen => !currentIsOpen)
b) It's very hard to reason about the next state when it gets updated by multiple handlers executed for the same event. Following 2 handlers might execute on the same click (the 1st div is "outside"):
<div ... onClick={e => setIsOpen(!isOpen)}>
<OutsideClickHandler onOutsideClick={() => setIsOpen(false)}>
If onOutsideClick executes first, then React re-renders with isOpen=false, and then onClick executes second, it would set isOpen=true as you observe - I don't see how the re-render could happen between, but maybe OutsideClickHandler is doing something nefarious or your code is more complicated than in the question ¯\_(ツ)_/¯
To enforce only 1 event handler:
<OutsideClickHandler onOutsideClick={(e) => {
e.stopPropagation();
setIsOpen(false);
}}>

Related

How export usestate, calculate, react, 3 files

I have 3 files that make up a component. I am trying to calculate a bmi result which is split into several files. When I click calculate, the page changes. I have tried to find that I am new to React in general, and these are things that are probably easy but not yet obvious to me
import React, { useState } from "react";
import style from './BMI.module.css';
import Calculator from './Calculator'
import Result from './Result'
function BMI() {
const [page, setPage] = useState(0);
const checkPage = () => {
if (page === 0) {
return (
<button className={`${style.button}`}
disabled={page === 1}
onClick={() => {
setPage((currPage) => currPage + 1);
}}>Calculate</button>
)
} else {
return (
<button className={`${style.button}`} disabled={page === 0} onClick={() => {
setPage((currPage) => currPage - 1);
}}>Back</button>
)
}
}
const PageDisplay = () => {
if (page === 0) {
return <Calculator />;
} else {
return <Result />;
}
};
return (
<div className={`${style.wrapper}`}>
<div className={`${style.box}`}>
<div className={`${style.img}`}></div>
<div className={`${style.topSection}`}>
<h1 className={`${style.title}`}>{(page === 0 ? 'BMI Calculator' : 'Result')}</h1>
</div>
<div className="body">{PageDisplay()}</div>
<div>{checkPage()}</div>
</div>
</div>
)
}
export default BMI
import React, { useState } from "react";
import style from './Calculator.module.css'
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome';
import { faMars, faVenus } from '#fortawesome/free-solid-svg-icons';
const Input = ({ label, id, handleChange, name, type, placeholder }) => (
<>
<label className={`${style.label}`} htmlFor={id}>{label}</label>
<input className={`${style.input}`} type={type || "number"} id={id} name={name || id} placeholder={placeholder} onChange={(e) => handleChange(e.target.value)}></input>
<br />
</>
);
function Calculator() {
const [height, setHeight] = useState(0)
const [weight, setWeight] = useState(0)
const [age, setAge] = useState(0)
function removeSelected() {
const selectedBox = document.querySelectorAll('#head')
console.log(selectedBox.classList)
selectedBox.forEach(item => {
item.classList.remove(`${style.activeBox}`)
})
}
const handleToggle = (e) => {
if (!e.currentTarget.className.includes("activeBox")) {
removeSelected()
e.target.classList.add(`${style.activeBox}`)
} else {
e.target.classList.remove(`${style.activeBox}`)
}
};
return (
<>
<div className={`${style.content}`}>
<div className={`${style.middleSection}`}>
<h3 className={`${style.formTitle}`}>Choose your gender</h3>
<div className={`${style.genders}`}>
<div id="head" onClick={handleToggle} className={`${style.genderBox}`}>
<FontAwesomeIcon icon={faMars} className={`${style.genderIcon}`} />
<h3 className={`${style.genderBoxTitle}`}>Male</h3>
</div>
<div id="head" onClick={handleToggle} className={`${style.genderBox}`}>
<FontAwesomeIcon icon={faVenus} className={`${style.genderIcon}`} />
<h3 className={`${style.genderBoxTitle}`}>Female</h3>
</div>
</div>
</div>
<div className={`${style.bottomSection}`}>
<Input handleChange={setWeight} placeholder='Weight' label='Your weight (kg)'>{weight}</Input>
<Input handleChange={setHeight} placeholder='Height' label='Your height (cm)'>{height}</Input>
<Input handleChange={setAge} placeholder='Age' label='Your age'>{age}</Input>
</div>
</div>
</>
)
}
export default Calculator
import React, { useState } from 'react'
import style from './Result.module.css'
function Result() {
const [bmiScore, setBmiScore] = useState(0)
const [bmiDesc, setBmiDesc] = useState('')
return (
<div className={`${style.content}`}>
<div className={`${style.img}`}></div>
<div className={`${style.descriptions}`}>
<p className={`${style.bmiScoreDesc}`}>Your BMI is <span className={`${style.bmiScoreNumber}`}>{bmiScore}</span>, indication your weight is in the <span className={`${style.bmiScoreDesc}`}>{bmiDesc}</span> category for adults of your height</p>
<p className={`${style.descriptionBottom}`}>Maintaining a healthy weight may reduce the risk of chronic diseases associated with overweight and obesity.</p>
</div>
</div>
)
}
export default Result
So when i click Calculate which i have in the BMI.js take inputs value from Calculator.js calculate the score and put into paragraph in Result.js
What you will want to do in this case is at the top level, in BMI, that's where your bmiScore state variable should live. You can pass it to both toe Calculator (where it will be calculated) and the Result (where it will be displayed) via props, like so:
<Calculator bmi={bmiScore} /> and <Result bmi={bmiScore} />
Then the method signatures in each will look like:
function Calculator({ bmi }) and function Result({ bmi })
And you can use that bmi variable in both.
This is all because React passes variables down, not up. The only way to share across components is if a parent component holds the variable.

Dynamic react toggle button for multiple elements

I need to toggle multiple blocks with true/false in react state, but true and false works for all blocks at the same time.
import { useState } from "react";
...
const [toggleThisElement, setToggleThisElement] = useState(false);
...
{
data.map((d, id) => {
return (
<div className="single-history" key={id}>
<div className="h-head">
click this div for toggle h-info block
</div>
<div className="h-info">toggling block</div>
</div>
)
})
}
Currently, all your toggle items share the same state. To avoid that create a separate component for toggling logic called ToggleItem as below.
import { useState } from "react";
const ToggleItem = ({ discription, id }) => {
const [toggleThisElement, setToggleThisElement] = useState(false);
return (
<div className="single-history" key={id}>
<button
className="h-head"
onClick={() => setToggleThisElement((prev) => !prev)}
>
click this btn for toggle h-info block {id}
</button>
{toggleThisElement && <div className="h-info">{discription}</div>}
</div>
);
};
export default function App() {
const data = ["first", "second", "third"];
return (
<>
{data.map((d, id) => {
return <ToggleItem id={id} discription={d} />;
})}
</>
);
}

How to add multiple events in one tag?

I'm making a counting timer which is described below with this react functional component
import {useEffect, useState, useRef} from 'react'
function Content() {
const [countdown, setCountdown] = useState(10)
const [show, setShow] = useState(true)
const ref = useRef()
function handleStart() {
ref.current = setInterval(() => {
setCountdown(prev => prev - 1)
}, 1000)
}
function handleStop() {
clearInterval(ref.current)
}
return (
<div>
<h2 style={{padding: 20}}>Time remaining: {countdown}</h2>
<button onClick={handleStart}>Start</button>
<button onClick={handleStop}>Stop</button>
</div>
)
}
export default Content;
How do I hide these two buttons after clicking one of the two.
Assuming show is the variable to control whether the buttons are visible or not.
<div>
<h2 style={{padding: 20}}>Time remaining: {countdown}</h2>
{show && <>
<button onClick={() => {
setShow(false)
handleStart()
}}>Start</button>
<button onClick={() => {
setShow(false)
handleStop()
}}>Stop</button>
</>}
</div>
React children need to return one element, so you can either wrap it in a div, or an empty element, <> </>, so you can return multiple nodes without adding a div, span, etc.
show && <></> means if show is true, the right-hand side will render, otherwise, it won't be rendered.
First, you have to introduce new state variable, you need one ror the start btn and another for the stop btn.
You have to setShow to false on either click and render the buttons conditionally depending on show variable:
const [countdown, setCountdown] = useState(10)
const [showStart, setShowStart] = useState(true)
const [showStop, setShowStop] = useState(true);
const ref = useRef()
function handleStart() {
setShowStart(false);
ref.current = setInterval(() => {
setCountdown(prev => prev - 1)
}, 1000)
}
function handleStop() {
setShowStop(false);
clearInterval(ref.current)
}
return (
<div>
<h2 style={{padding: 20}}>Time remaining: {countdown}</h2>
{showStart && <button onClick={handleStart}>Start</button>}
{showStop && <button onClick={handleStop}>Stop</button>}
</div>
)
Hope the Below Code Solver Your Problem
import React, { useEffect, useState, useRef } from 'react';
function Example() {
const [countdown, setCountdown] = useState(10);
const [show, setShow] = useState(true);
const ref = useRef();
function handleStart() {
setShow(!show);
ref.current = setInterval(() => {
setCountdown((prev) => prev - 1);
}, 1000);
}
function handleStop() {
setShow(!show);
clearInterval(ref.current);
}
return (
<div>
<h2 style={{ padding: 20 }}>Time remaining: {countdown}</h2>
{show && (
<div>
<button onClick={handleStart}>Start</button>
<button onClick={handleStop}>Stop</button>
</div>
)}
</div>
);
}
export default Example;

how to handle onClick event with multiple component with the same class name in React?

I'm new to react.js and I want to apply the toggle feature at 'place-box' by using 'isOpen' state and my intention is it only works when I click single place-box div so I added onClick event at 'place-box' div. but all of the elements are toggled at the same time.
I guess it's because they all have the same class name.
how can I fix this?
import React, { useState, useEffect } from "react";
import { useQuery } from "#apollo/client";
import { FETCH_CITIES_QUERY } from "../../server/Data/RentQueries";
import PlaceResult from "../Rent/PlaceResult";
const CityResult = (props) => {
const [placeId, setPlaceId] = useState();
const [isOpen, setIsOpen] = useState(false);
const { loading, error, data } = useQuery(FETCH_CITIES_QUERY, {
variables: { cityName: cityName },
});
const showPlaceInfo = (placeId, e) => {
e.preventDefault();
setPlaceId(placeId);
setIsOpen((isOpen) => !isOpen);
};
return (
<div>
{data &&
data.cities.map((city) => {
return (
<div className="city-box">
{city.places.map((place) => {
return (
// this is place-box div and I added onClick event here
<div
className="place-box"
key={place.id}
onClick={(e) => {
e.stopPropagation();
showPlaceInfo(place.id, e);
}}
>
<li className="place-name">{place.name}</li>
{isOpen && (
<PlaceResult className="place-indiv" placeId={placeId} />
)}
{!isOpen && (
<div className="place-info-box">
<li>{place.address}</li>
{conditionCheck(city.condition)}
<li>{place.phone}</li>
</div>
)}
</div>
);
})}
</div>
);
})}
</div>
);
};
export default CityResult;
Your variable isOpen is used for all cities. If you change isOpen to true all place-boxes are opened. You should store the id of the currently opened city inside a variable and compare against it to check if the current city in the for loop should be opened.
import React, { useState, useEffect } from "react";
import { useQuery } from "#apollo/client";
import { FETCH_CITIES_QUERY } from "../../server/Data/RentQueries";
import PlaceResult from "../Rent/PlaceResult";
const CityResult = (props) => {
const [placeId, setPlaceId] = useState();
const [openedPlaceId, setOpenedPlaceId] = useState(undefined);
const { loading, error, data } = useQuery(FETCH_CITIES_QUERY, {
variables: { cityName: cityName },
});
const showPlaceInfo = (placeId, e) => {
e.preventDefault();
setPlaceId(placeId);
setOpenedPlaceId(placeId);
};
return (
<div>
{data &&
data.cities.map((city) => {
return (
<div className="city-box">
{city.places.map((place) => {
return (
// this is place-box div and I added onClick event here
<div
className="place-box"
key={place.id}
onClick={(e) => {
e.stopPropagation();
showPlaceInfo(place.id, e);
}}
>
<li className="place-name">{place.name}</li>
{openedPlaceId === place.id && (
<PlaceResult className="place-indiv" placeId={placeId} />
)}
{!(openedPlaceId === place.id) && (
<div className="place-info-box">
<li>{place.address}</li>
{conditionCheck(city.condition)}
<li>{place.phone}</li>
</div>
)}
</div>
);
})}
</div>
);
})}
</div>
);
};
export default CityResult;
This way only the clicked place will be opened.

Button 'more' in a popup box doesn’t work right with React hook useState

I created a component (sort of popup box) which displays a sign of horoscope, there’s an image and description. The popup box works correctly. I added a button ‘more’ to see more description, so I used a useState for it, but it doesn’t work, when I click on it doesn't show the rest of the text.
Thanks for your help !
const Modal = ({
children, visible, hide, fermer, more,
}) => {
const popup = `popup ${visible ? 'block' : 'hidden'}`;
return (
<div className={popup}>
{fermer ? null : (
<button className="close" onClick={hide} type="button">X</button>
)}
{children}
<button className="more" onClick={more} type="button">more</button>
</div>
);
};
export default Modal;
import './App.css';
import { useState } from 'react';
import Element from './Element';
import Modal from './Modal';
import Bd from './Bd';
function App() {
const bd = Bd.map((element) => (
<Element
nom={element.nom}
image={element.image}
description={element.description}
modulo={element.modulo}
/>
));
const [year, setYear] = useState('');
function handleChange(event) {
setYear(event.target.value);
}
const [signe, setSigne] = useState([]);
const [vis, setVis] = useState(false);
const [desc, setDesc] = useState(true);
function handleSubmit() {
setVis(true);
const yearModulo = Number(year) % 12;
Bd.map((element) => (
yearModulo === element.modulo ? setSigne(
[<h1>{element.nom}</h1>,
<div>{element.description.substr(0, 150)}</div>,
desc ? <div />
: <div>{element.description.substr(150, 600)}</div>,
<img src={`/images/${element.image}`} alt="" />,
],
) : false
));
}
return (
<div>
<div>
<input
className="text-center font-bold"
type="number"
id="year"
name="year"
value={year}
onChange={handleChange}
/>
<button type="submit" onClick={handleSubmit}>Valider</button>
</div>
<div className="flex flex-wrap">{bd}</div>
<Modal
visible={vis}
hide={() => setVis(false)}
more={() => setDesc(false)}
>
<div>
<div>{signe}</div>
</div>
</Modal>
</div>
);
}
export default App;
I would avoid storing in a local state a component (setSigne([<h1>{element.nom}</h1>,...). Prefer storing in the state the values that cannot be computed from other existing states, and generate the elements at rendering.
const [signe, setSigne] = useState(null);
function handleSubmit() {
setVis(true);
const yearModulo = Number(year) % 12;
setSigne(Bd.find(element => yearModulo === element.modulo));
}
// ...
<div>
{signe && <div>
<h1>{signe.nom}</h1>
<div>{signe.description.substr(0, 150)}</div>
{desc ? <div /> : <div>{signe.description.substr(150, 600)}</div>}
<img src={`/images/${signe.image}`} alt="" />
</div>}
</div>
Also, don’t forget to add a key prop when generating elements from an array:
const bd = Bd.map(element => (
<Element
key={element.nom}
// ...

Resources