I am trying to hide multiple divs with useSate.
They will be rendered random on the page, not from a list.
I have managed to do so by setting up different variables but couldn't find a more generic solution:
https://stackblitz.com/edit/react-t3shrc?file=src%2FApp.js
Also is there a way to close them when clicking outside?
Can you help please.
export default function App() {
const [isVisible, setIsVisible] = useState(false);
const [isVisible2, setIsVisible2] = useState(false);
const showInfo = (e, setIsVisible) => {
e.preventDefault();
setIsVisible(true);
};
const hideInfo = (e, setIsVisible) => {
e.preventDefault();
setIsVisible(false);
};
return (
<div>
<button
onClick={(e) => {
showInfo(e, setIsVisible);
}}
>
Show info 1
</button>
{isVisible && (
<div className="info">
Info 1
<button
onClick={(e) => {
hideInfo(e, setIsVisible);
}}
>
Close
</button>
</div>
)}
<br></br>
<br></br>
<button
onClick={(e) => {
showInfo(e, setIsVisible2);
}}
>
Show info 2
</button>
{isVisible2 && (
<div className="info">
Info 2
<button
onClick={(e) => {
hideInfo(e, setIsVisible2);
}}
>
Close
</button>
</div>
)}
</div>
);
}
I'm not 100% sure what you mean by a more 'generic' solution. Here is what comes to my mind:
First of all, we create a more complex object to basically hold all the variables / sections we encounter and use this as our state.
const initialVisibleAreas = {
area1: true,
area2: false
};
const [visibleAreas, setVisibleAreas] = useState(initialVisibleAreas);
Please note that this is propabably something you want to generate from your data using Object.keys(...) or mapping an array.
Next up, we create the functions for the buttons to use this new state accordingly:
// shows the element by given key
const showInfo = (event, key) => {
event.preventDefault();
setVisibleAreas({ ...visibleAreas, ...{ [key]: true } });
};
// hides the element by given key
const hideInfo = (event, key) => {
event.preventDefault();
setVisibleAreas({ ...visibleAreas, ...{ [key]: false } });
};
// sets every key to false to hide them all at once
const hideAllInfo = (event) => {
event.preventDefault();
const allFalse = Object.assign(
...Object.keys(visibleAreas).map((key) => ({ [key]: false }))
);
setVisibleAreas(allFalse);
};
Last but not least, we use them in jsx. This is basically one 'section':
<button
onClick={(e) => {
showInfo(e, 'area2');
}}
>
Show info 2
</button>
{
visibleAreas['area2'] && (
<div className="info">
Info 2
<button
onClick={(e) => {
hideInfo(e, 'area2');
}}
>
Close
</button>
</div>
);
}
To answer the last question; nothing is holding you to call 'hideAllInfo' inside a onClick handler of your surounding div. Sample is included in the modified stackblitz.
Have a complete look at the modified stackblitz
EDIT: In the case you want to close all areas by clicking the surrounding div, make sure to not propagate the button click event with:
event.stopPropagation();
I updated the stackblitz once again.
Related
I have a parent component. This parent component combines a lot of if else control and a lot of mini jsx. I added my case. If I click the NextBtnText in the Modal component. It doesn't fire the first click. It needs a second click.
How can I fire the first click? What is wrong?
const StepperComponent = ({closeModal}) => {
/**
* there is some ,useState, useEffect and conditional functions
*/
const test = () => setActiveStep((prevActiveStep) => prevActiveStep + 1);
const NextBtnText = () => {
return (<Button
onClick={test}
disabled={firmType}
className={styles.StepperNextButton}
>
<span>{t("createFirm.buttons.next")}</span>
<KeyboardArrowRightIcon />
</Button>
);
};
const BackBtnText = () => {
return (
<>
<span>{t("createFirm.buttons.back")}</span>
</>
);
}
const RequestAssignmentBtnText = () => {
return (
<Button
onClick={handleSubmit}
disabled={firmType}
className={styles.StepperRequestButton}
>
<span>{t("createFirm.buttons.requestAssignment")}</span>
</Button>
)
}
return (
<div className={styles.StepperContainer}>
<Stepper activeStep={activeStep} className={styles.Steps}>
{steps.map((label, index) => {
return (
<Step key={index}>
<StepLabel >{label}<span className={styles.StepCountMobile}>{`(${index + 1} / ${steps.length})`}</span></StepLabel>
</Step>
);
})}
</Stepper>
{getStepContent(activeStep)}
<div className={styles.StepperButtons}>
<Button
disabled={activeStep === 0}
onClick={handleBack}
className={styles.StepperBackButton}
>
<span>{t("createFirm.buttons.back")}</span>
</Button>
{activeStep === steps.length - 1 ? (<RequestAssignmentBtnText />) : (<NextBtnText />)}
</div>
</div>
);
}
Yo're not calling your function. Simply do:
<Button
onClick={(event) => handleSubmit(event)}
>
// Your function has to look like this
const handleSubmit = (event) => {
// your code...
}
Or you can do this:
<Button
onClick={handleSubmit()}
>
// Your function has to look like this
const handleSubmit = () => {
return (event) => {
// your code...
}
}
What is the default value you set in useState() for activeStep?
I believe the onClick handler is working but the state is not what you expect on the first click. Maybe set the default value to 1 or 0 (I am not sure what is suitable for your use case).
const [activeStep, setActiveStep] = useState(1);
If the onClick is actually not working with the first click, try using plain HTML <input /> with test onClick handler and see if that works. It might have something to do with the Button component itself.
I fixed the onClick problem. The cause is about rerendered after disabled={firmType}. My button has a disabled attribute. I need to control after the checkbox is true/false.
Before :
{activeStep === steps.length - 1 ? (<RequestAssignmentBtnText />) : (<NextBtnText />)}
Solution :
{activeStep === steps.length - 1
?
<Button
onClick={handleSubmit}
className={styles.StepperNextButton}
>
<span>{t("createFirm.buttons.requestAssignment")}</span>
</Button>
:
<Button
onClick={handleNext}
disabled={firmType}
className={styles.StepperNextButton}
>
<span>{t("createFirm.buttons.next")}</span>
<KeyboardArrowRightIcon />
</Button>
}
Actually, I want to know what is different between Before and Solution.
Maybe someone can explain the issue of solution.
I'm using react-days-picker for accessibility reasons.
I would like to replace the current span tags inside the navbar with button tags.
I could extend it with the navbarElement prop but then my custom component keeps re-rendering each time I click or press a key to change months. It would lose focus.
To change a month, I could only press the key down key once or I had to .press the tab key each time to get the focus again
I'm even following the code included in the documentation (https://react-day-picker.js.org/examples/elements-navbar) but the result is the same:
function Navbar({
nextMonth,
previousMonth,
onPreviousClick,
onNextClick,
className,
localeUtils,
}) {
const months = localeUtils.getMonths();
const prev = months[previousMonth.getMonth()];
const next = months[nextMonth.getMonth()];
const styleLeft = {
float: 'left',
};
const styleRight = {
float: 'right',
};
return (
<div className={className}>
<button style={styleLeft} onClick={() => onPreviousClick()}>
← {prev.slice(0, 3)}
</button>
<button style={styleRight} onClick={() => onNextClick()}>
{next.slice(0, 3)} →
</button>
</div>
);
}
export default function Example() {
return (
<div>
<DayPicker weekdayElement={<Weekday />} navbarElement={<Navbar />} />
</div>
);
}
I am having hard times with this one.
I created a modal (SetLevel) so the user can select a level and after that what I want is to just update my initial state which goes by the name of level . So I pass my prop in handleChange in the SetLevel component like this:
const Game = () => {
const [levelOpen,setlevelOpen]=useState(false);
const [level,setlevel]=useState(1);
const changedLevel = (newLevel)=>{
console.log('newLevel',newLevel);
setlevel(newLevel);
}
return (
<div>
<h1 className='title'>Find the icons </h1>
<div className='container'>
<button className='btn' onClick={() => setlevelOpen(true)}>
Select level
</button>
<SetLevel isOpen={levelOpen} handleChange={()=>changedLevel(level)}
onClose={()=>setlevelOpen(false)}/>
</div>
<ItemDrag newLevel={level}/>
</div>
);
};
SetLevel child component looks like this:
const SetLevel = ({isOpen,onClose,handleChange}) => {
if (isOpen === false) return null;
const close = (e) => {
e.preventDefault();
if (onClose) {
onClose();
}
};
const handleClick =(num,e)=>{
console.log(num);
handleChange(num)
close(e)
}
return (
<div className='modal-window'>
<div>
<div onClick={(e) => close(e)} className='modal-close'>
CLOSE
</div>
<h1>Select level!</h1>
<button className="btn" onClick={(e)=>handleClick(3,e)} >LEVEL3</button>
</div>
</div>
);
}
So here is how I do it if a user selects level 3 I pass that number to my handleClick function and this function should take care of that handleChange prop as you can see.
But whenever I do this my level is not updating how come?? The value i am getting back is always 1 , why is that? thanks.
You don't take the return value of the handleChange in the Game component.
Try it like this:
const Game = () => {
const [levelOpen,setlevelOpen]=useState(false);
const [level,setlevel]=useState(1);
const changedLevel = (newLevel)=>{
console.log('newLevel',newLevel);
setlevel(newLevel);
}
return (
<div>
<h1 className='title'>Find the icons </h1>
<div className='container'>
<button className='btn' onClick={() => setlevelOpen(true)}>
Select level
</button>
<SetLevel isOpen={levelOpen} handleChange={(newLevel)=>changedLevel(newLevel)}
onClose={()=>setlevelOpen(false)}/>
</div>
<ItemDrag newLevel={level}/>
</div>
);
};
I am learning while creating a test app that lets you create posts from a form. I have been stuck on the delete function going on five days now.
I was able to correctly map posts to print out the post_body to each card but when trying to delete it is always removing the last item in the database.
My guess is this has something to do with props and I have spent several days trying out different ways to pass props down through a functional component but no luck.
As seen in the screenshot, In the return I printed out the post_id for each card so you can see the proper ID is assigned for each card. However, once you get into the popover component the post_ID seems to always take on the value of the very bottom post.
Any direction is appreciated.
(note: I'm sure this code is pretty sloppy and I probably shouldn't be mapping over such a huge block of code. I may try refactoring once I figure out how these props should be working)
const ListPosts = (props) => {
const [posts, setPosts] = useState([]);
// Delete Post
const deletePost = async id => {
try {
const deletePost = await fetch(`http://localhost:3000/posts/${id}`, {
method: "DELETE"
});
setPosts(posts.filter(post => post.post_id !== id));
console.log(deletePost);
} catch (err) {
console.error(err.message);
}
}
// Options Popover
const [show, setShow] = useState(false);
const [target, setTarget] = useState(null);
const ref = useRef(null);
const handleClick = (event) => {
setShow(!show);
setTarget(event.target);
}
// Get Posts Function
const getPosts = async() => {
try {
const response = await fetch("http://localhost:3000/posts")
const jsonData = await response.json()
setPosts(jsonData);
} catch (err) {
console.error(err.message)
}
};
useEffect(() => {
getPosts();
}, []);
console.log(posts);
return (
<Fragment>
{/* Map Post Text */}
{posts.map(post => (
<Card className="post-card" style={{ marginTop: "15px" }} key={post.post_id}>
<Card.Body>
<div className="post-container">
<div className="post-header row">
<div className="user-photo-icon col-1"></div>
<div className="user-names col-9"></div>
<div className="options-menu col-2 ">
<div ref={ref}>
<Button className="options-btn-popover" onClick={handleClick}>
<FontAwesomeIcon icon={ faEllipsisH } color="#848484" size="1x" className="options-icon" />
</Button>
{/* Placed to show the point at which the ID's are still correct */}
{post.post_id}
<Overlay
show={show}
target={target}
placement="left"
container={ref.current}
>
<Popover className="shadow-sm" id="popover-contained" >
{/* Placed to show that now all id's show the post_id of the last post */}
{post.post_id}
<Popover.Content>
<div className="mb-2">
<Button className="options-btn-popover">
<FontAwesomeIcon icon={ faPencilAlt } size="1x" className="post-options-icon"/>
Edit Post
</Button>
</div>
<div>
<Button className="options-btn-popover" onClick={() => deletePost(post.post_id)}>
<FontAwesomeIcon icon={ faTrashAlt } color="" size="1x" className="post-options-icon" />
Delete Post
</Button>
</div>
</Popover.Content>
</Popover>
</Overlay>
</div>
</div>
</div>
<div className="post-text">{post.post_body}</div>
<div className="post-media"></div>
<div className="post-actions"></div>
<div className="post-comments">
<div className="post-subcomments"></div>
</div>
</div>
</Card.Body>
</Card>
))}
</Fragment>
)
};
Here's a screenshot:
post list with post Id's
Issue
I suspect it is because you've only a single show state that when toggled opens all the popovers. All the popovers open but they are all positioned relative to the target element, they all overlay one another and the last one is on top.
Solution
I suggest storing the current post id in the "show" state and conditionally check/match that to open a specific popover.
Start with null initial state:
const [show, setShow] = useState(null);
Update the click handler to consume a post id and curry the event object. Set the show state to the currently clicked on post id.
const handleClick = (postId) => (event) => {
setShow(postId);
setTarget(event.target);
}
Pass the post.post_id when mapping the buttons.
<Button
className="options-btn-popover"
onClick={handleClick(post.post_id)} // <-- pass post id
>
<FontAwesomeIcon
icon={faEllipsisH}
color="#848484"
size="1x"
className="options-icon"
/>
</Button>
Check the current post id when mapping the overlay/popover. If the current post id matched what is stored in show state then evaluate true, otherwise false.
<Overlay
show={show === post.post_id} // <-- compare post id to show state
target={target}
placement="left"
container={ref.current}
>
<Popover className="shadow-sm" id="popover-contained" >
<Popover.Content>
...
</Popover.Content>
</Popover>
</Overlay>
Clear the show state when delete operation has completed. Functional state update to correctly update from the previous state (not the state from the render cycle the callback was triggered in.
const deletePost = async id => {
try {
const deletePost = await fetch(`http://localhost:3000/posts/${id}`, {
method: "DELETE"
});
setPosts(posts => posts.filter(post => post.post_id !== id)); // <-- functional update
console.log(deletePost);
} catch (err) {
console.error(err.message);
} finally {
setShow(null); // <-- reset back to null
}
}
Could you try using currying to accomplish this?
Below is your code with a small tweak that I made. When we declare the deletePost you will notice that it takes the id as a param and then calls another function. Then when you call this delete function you no longer need the () => before deletePost.
TLDR: currying lets you pass in the values before execution time.
const ListPosts = (props) => {
const [posts, setPosts] = useState([]);
// Delete Post
const deletePost = id => async => {
try {
const deletePost = await fetch(`http://localhost:3000/posts/${id}`, {
method: "DELETE"
});
setPosts(posts.filter(post => post.post_id !== id));
console.log(deletePost);
} catch (err) {
console.error(err.message);
}
}
// Options Popover
const [show, setShow] = useState(false);
const [target, setTarget] = useState(null);
const ref = useRef(null);
const handleClick = (event) => {
setShow(!show);
setTarget(event.target);
}
// Get Posts Function
const getPosts = async() => {
try {
const response = await fetch("http://localhost:3000/posts")
const jsonData = await response.json()
setPosts(jsonData);
} catch (err) {
console.error(err.message)
}
};
useEffect(() => {
getPosts();
}, []);
console.log(posts);
return (
<Fragment>
{/* Map Post Text */}
{posts.map(post => (
<Card className="post-card" style={{ marginTop: "15px" }} key={post.post_id}>
<Card.Body>
<div className="post-container">
<div className="post-header row">
<div className="user-photo-icon col-1"></div>
<div className="user-names col-9"></div>
<div className="options-menu col-2 ">
<div ref={ref}>
<Button className="options-btn-popover" onClick={handleClick}>
<FontAwesomeIcon icon={ faEllipsisH } color="#848484" size="1x" className="options-icon" />
</Button>
{/* Placed to show the point at which the ID's are still correct */}
{post.post_id}
<Overlay
show={show}
target={target}
placement="left"
container={ref.current}
>
<Popover className="shadow-sm" id="popover-contained" >
{/* Placed to show that now all id's show the post_id of the last post */}
{post.post_id}
<Popover.Content>
<div className="mb-2">
<Button className="options-btn-popover">
<FontAwesomeIcon icon={ faPencilAlt } size="1x" className="post-options-icon"/>
Edit Post
</Button>
</div>
<div>
<Button className="options-btn-popover" onClick={deletePost(post.post_id)}>
<FontAwesomeIcon icon={ faTrashAlt } color="" size="1x" className="post-options-icon" />
Delete Post
</Button>
</div>
</Popover.Content>
</Popover>
</Overlay>
</div>
</div>
</div>
<div className="post-text">{post.post_body}</div>
<div className="post-media"></div>
<div className="post-actions"></div>
<div className="post-comments">
<div className="post-subcomments"></div>
</div>
</div>
</Card.Body>
</Card>
))}
</Fragment>
)
};
I think the problem is that you are creating Overlay/Popover component inside loop, try to move it out of the loop. You can then use const [selectedPost, selectPost] = useState() to track data which should be rendered in overlay. button is clicked. Also adjust onClick={handleClick} to call selectPost(post).
So I have this Display() function which takes events from the Google Calendar and the function returns a list of elements (each element is associated with a slider) to be rendered on the screen (see return statement of Display() function) and renders them as seen here. So each element comes with a Remove button so that I can remove an unwanted element from the page using the hideMe() function inside the Display() function. The hideMe() function does seem to do its work, however, it removes all the elements on the page as seen here. So I am struggling to figure out what I should fix so that when I click on the Remove button, it only removes the element and the slider associated to that remove button. I am new to React and JavaScript so please help. Any help is appreciated and thank you in advance.
function Display() {
const isMounted = useRef(true);
const [items, saveItems] = useState([]);
const [visible, setVisible] = useState(true);
const [fading, setFading] = useState(false);
useEffect(() => {
return () => {
isMounted.current = false;
};
}, []);
useEffect(() => {
(async () => {
const items = await fetchItems();
//Do not update state if component is unmounted
if (isMounted.current) {
saveItems(items);
}
})();
}, []);
function hideMe() {
setFading(true);
setTimeout(() => setVisible(false), 650);
}
return (
<Tab.Pane attached={false}>
<h5>Rate stress level for each event</h5>
<br/>
{items.map(item => (
<div key={item.id} isvisible={!fading}
style={visible ? null : { display: "none" }}>
<Typography id="discrete-slider-restrict" gutterBottom>
{item}
<button onClick={hideMe}>Remove</button>
</Typography>
<PrettoSlider aria-label="pretto slider" defaultValue={98} step={null}marks={stresslevel}/>
</div>
))}
</Tab.Pane>
)
}
It seems to me that this issue is happening because all elements are available in same state or i would say that they all share same state. So, this executes for all. If it is possible for you to extract it to a new component and use the hideMe function there. This will i am sure work for each individual elements.
It is my suggestion please go through below. May be you have to tweak a little bit.
You can extract the elements in a separate component like:
const Item = props => {
const [visible, setVisible] = useState(true);
const [fading, setFading] = useState(false);
function hideMe() {
setFading(true);
setTimeout(() => setVisible(false), 650);
}
return (
<div isvisible={!fading} style={visible ? null : { display: "none" }}>
<Typography id="discrete-slider-restrict" gutterBottom>
{item}
<button onClick={hideMe}>Remove</button>
</Typography>
<PrettoSlider aria-label="pretto slider" defaultValue={98}
step={null} marks={stresslevel}/>
</div>
);
};
export default Item;
Then you can use it like:
// import Item
{items.map(item => (
<Item key={item.id} itemObj={item} />
// in case if you need item obj then props.itemObj will get you the object.
))}
In this way you can manage the hideMe function with the separate specific Item component.