Next.js show/hide divs on buttons - javascript

I am trying to show and hide divs on buttons in Next JS.
This post helped me a lot:
Next.js toggle display of a div tag
I used this solution using useState in my case and it works perfectly. The difference is that I have many buttons and each of them supposes to show different div and hide active div.
This is what I am trying to achieve
https://codepen.io/CthuKi/pen/YqZvRv
export default function Header(){
const [showMe, setShowMe] = useState(false);
function toggle(){
setShowMe(!showMe);
}
return (
<>
<button onClick={toggle}>Button 1</button>
<button onClick={toggle}>Button 2</button>
<button onClick={toggle}>Button 3</button>
<div>
<div style={{display: showMe?"block":"none"}}>
Show this div on button 1 and hide other active
</div>
<div style={{display: showMe?"block":"none"}}>
Show on button 2 and hide other active
</div>
<div style={{display: showMe?"block":"none" }}>
Show on button 3 and hide other active
</div>
</div>
</>
);
}
I will appreciate any help

One solution would be to have multiple toggle functions to toggle multiple divs. But there is a better way, create only one state variable showAll and one function toggleAll to show all the content.
To toggle one button to show specific content, create an array of data that you want to show, and just change the index to show the current data from the array.
For example, see the codesandbox for demo
import React, { useState } from "react";
export default () => {
const [data, setData] = useState(["hello", "hi there", "holla"]);
const [showAll, setShowAll] = useState(false);
const [currentIdx, setCurrentIdx] = useState(0);
const [showCurrent, setShowCurrent] = useState(false);
const toggleAll = () => {
setShowAll(val => !val);
setShowCurrent(false);
};
const toggleCurrent = () => {
if (!showCurrent) {
setShowCurrent(true);
setShowAll(false);
return;
}
};
const setCurrent = index => {
setCurrentIdx(index);
toggleCurrent();
};
return (
<div>
<div>
<button onClick={toggleAll}>{showAll ? "Hide All" : "Show All"}</button>
<button onClick={() => setCurrent(0)}>First</button>
<button onClick={() => setCurrent(1)}>Second</button>
<button onClick={() => setCurrent(2)}>Third</button>
</div>
<div>
{showAll && data.map((el, i) => <p key={`content-${i}`}>{el}</p>)}
</div>
{showCurrent ? <div>{data[currentIdx]}</div> : null}
</div>
);
};

Related

In react, how to pass states down as props?

This is a very noob question but I've been trying all day do implement this. Please help me out.
Sorry for the length, just tried to put out the whole thing I am struggling with
I am trying to build custom buttons and to do so, I created a component so I can create as many buttons that I want. For that I declared a state and passed down some information as props, which is as follows:
import React, {useState} from 'react'
import Button from '../components/Button'
function CustomButton() {
const [clicked, setClicked] = useState(false)
return (
<div className='CustomButton'>
<Navbar />
<Button setClicked={setClicked} name="Button One" clicked={clicked}/>
<Button setClicked={setClicked} name="Button Two" clicked={clicked}/>
<Button setClicked={setClicked} name="Button Three" clicked={clicked}/>
</div>
)
}
export default CustomButton
As you can see, we passed the state and name of that button down. To render this Buttons, following component has been created:
import React from 'react'
import Modal from './Modal/Modal'
function Button({setClicked, name, clicked}) {
return (
<div>
<button onClick={() => {setClicked(true)}}>{name}</button>
{clicked && <Modal closeModal={setClicked} name={`You Clicked ${name}`} />}
</div>
)
}
export default Button
And lastly, when once a button is clicked, we want to perform some action. That action is to pop the Modal on a screen. And to do so, we created a Modal and passed down few props. Code for the same is as follows:
import React from 'react'
function Modal({closeModal, name}) {
return (
<div className='modal'>
<div className='modalContainer'>
<p>{name}</p>
<div>
<button onClick={() => {closeModal(false)}}>×</button>
</div>
</div>
</div>
)
}
export default Modal
The expected result is for a Modal to pop with "You clicked button One", supposing we clicked one something similar to this.
The actual result is that all three Modals pop up one above the other when any of the three buttons are passed. The result:
I realize that I am passing the states wrong way. When any of the button is clicked all three get set to true. I simply don't realize how. Don't they create a method for each one?
Also, can you guys please teach me a better/understandable way to write clicked logic. Like maybe
if(clicked){
<Modal closeModal={setClicked} name={`You Clicked ${name}`} />
}
Because you bind all three buttons with one state, You need a state as array, with items equal to the number of buttons.
const [clicked, setClicked] = useState([false, false, false])
return (
<div className='CustomButton'>
<Navbar />
{
clicked.map((button, i) => {
return <Button setClicked={setClicked} name="Button Three" clicked={clicked[i]} index={i}/>
})
}
</div>
)
Then in the button component.
function Button({setClicked, name, clicked, index}) {
return (
<div>
<button onClick={() => {setClicked(prev => prev.map((item, i) => {
return i === index ? true : item
}))}}>{name}</button>
{clicked && <Modal closeModal={setClicked} name={`You Clicked ${name}`} />}
</div>
)
}
And the modal component.
function Modal({ closeModal, name, index }) {
return (
<div className="modal">
<div className="modalContainer">
<p>{name}</p>
<div>
<button
onClick={() => {
closeModal((prev) =>
prev.map((item, i) => {
return i === index ? false : item;
})
);
}}
>
×
</button>
</div>
</div>
</div>
);
}
You can find a working example on this link.
https://codesandbox.io/s/old-wood-zgjno9
You can implement multiple modals like this:
import { useState } from "react";
export default function App() {
const [showModal1, setShowModal1] = useState(false);
const [showModal2, setShowModal2] = useState(false);
return (
<div className="App">
<button onClick={(e) => setShowModal1(true)}>Button 1</button>
<button onClick={(e) => setShowModal2(true)}>Button 2</button>
{showModal1 && (
<Modal text="Modal 1" onClose={(e) => setShowModal1(false)} />
)}
{showModal2 && (
<Modal text="Modal 2" onClose={(e) => setShowModal2(false)} />
)}
</div>
);
}
const Modal = ({ text, onClose }) => {
return (
<div>
{text}
<button onClick={onClose}>Close</button>
</div>
);
};
Working example

How to attach close function on dyanmic children button?

I have a Tooltip component which is a wrapper for dynamic content.
I am trying to use it as a popup, which will have Delete and Cancel buttons.
I am passing the delete and cancel button as children prop, the problem is cus Close and Open state is in the Tooltip component.
I need to attach the close function on Cancel button which lives in Children prop.
Need help to find an elegant solution to this.
Tooltip component:
export const Tooltip: FC<TooltipProps> = ({
content,
helperText,
children,
...props
}) => {
const [visible, setVisible] = useState(false);
const show = () => setVisible(true);
const hide = () => setVisible(false);
return (
<div>
<Tippy
content={content}
visible={visible}
onClickOutside={hide}
interactive
{...props}
>
<div onClick={visible ? hide : show}>
// =====>>> **Close button which be in children prop, need to attach hide() function**
{children}
</div>
</Tippy>
</div>
);
};
This is Call of Tooltip component and passing buttons as Children:
<Tooltip
content={
<div className="popover-buttons">
// Need to attach here **hide()** function from Tooltip coomponent
<button>
Cancel
</button>
<button>
Delete
</button>
</div>
</Tooltip>
You can make the content prop passed to Tippy a component that has hide() function passed to it as a prop
export const Tooltip: FC<TooltipProps> = ({
content: Content,
helperText,
children,
...props
}) => {
const [visible, setVisible] = useState(false);
const show = () => setVisible(true);
const hide = () => setVisible(false);
return (
<div>
<Tippy
content={<Content hide={hide} />}
visible={visible}
onClickOutside={hide}
interactive
{...props}
>
<div onClick={visible ? hide : show}>
// =====>>> **Close button which be in children prop, need to attach hide() function**
{children}
</div>
</Tippy>
</div>
);
};
Then you have:
<Tooltip
content={ ({ hide }) =>
<div className="popover-buttons">
// Need to attach here **hide()** function from Tooltip coomponent
<button onClick={hide}>
Cancel
</button>
<button>
Delete
</button>
</div>
</Tooltip>

How to load more items from a map function on click of a button

I have this jsx expression that renders a maximum of 5 items from an array using the map function. How can I make it so that the rest of the items render on click of the button? The list of items in the array is unknown as it's coming from an API, so sometimes it can be 10 items or 15 items, etc.
const Second = ({container}) => {
return(
<div>
{container?.slice(0, 5).map((container) => (
<h3>{container}</h3>
))}
<button type="button" class="btn btn-primary">Primary</button>
</div>
)
}
You can have a state that determines that and then set the state to true on click of the button: Someting like this
const Second = ({container}) => {
const [showMore, setShowMore] = useState(false);
return(
<div>
{container?.slice(0, 5).map((container) => (
<h3>{container}</h3>
))}
{showMore && container?.slice(5).map((container) => (
<h3>{container}</h3>
))} //this would show the remaining values
<button type="button" class="btn btn-primary" onClick={() => setShowMore(true)}>Primary</button>
</div>
)
}
you can declare a boolean state variable to determine if you want to show all the items or only the first five, this variable will be updated when you click on the button :
function Second({container = []}) {
const [showAll, setShowAll] = useState(false);
function handleClick() {
setShowAll(prevShowAll => !prevShowAll);
}
const items = showAll ? container : container.slice(0, 5);
return(
<div>
{items.map(item => <h3>{item}</h3>)}
<button type="button" class="btn btn-primary" onClick={handleClick}>
{showAll ? "Show first five items" : "Show all items"}
</button>
</div>
)
}

how to toggle between two css classes view types with react

I have a List and Grid type display. How do I toggle between them in React. I want to toggle between jsGridView and jsListView classes.
This is the vanilla js of the toggling of classes
const listView = document.querySelector('.list-view');
const gridView = document.querySelector('.grid-view');
const projectsList = document.querySelector('.project-boxes');
listView.addEventListener('click', () => {
gridView.classList.remove('active');
listView.classList.add('active');
projectsList.classList.remove('jsGridView');
projectsList.classList.add('jsListView');
});
gridView.addEventListener('click', () => {
gridView.classList.add('active');
listView.classList.remove('active');
projectsList.classList.remove('jsListView');
projectsList.classList.add('jsGridView');
});
** this is my react file where I have the display items and buttons to toggle. how do I implement the toggle event listeners into my react file**
How do I toggle between the two classes - jsGridVew and jsListView
const [isActive, setIsActive] = useState(false)
const listToggle = () => {
setIsActive(!isActive)
}
<button key={isActive} className="view-btn list-view" title="List View" onClick={listToggle}>
<i className="fal fa-list-ul fa-2x"></i>
</button>
<button className="view-btn grid-view active" title="Grid View">
<i className="fal fa-th-large fa-2x"></i>
</button>
<div className="project-boxes jsGridView">
{!loading && records.length === 0 ? (<h4 style={{ margin: '20px' }} className='center'>No
records, sorry</h4>) : records.map((record, key) => (
<RecordItem key={key} record={record} isFilter={isFilter} filterByWhat={filterByWhat} />
))}
</div>
EDIT: > I also want to add an 'active class on each button on click. I've tried somethings but it doesn't work
I am assuming this div is where you want to toggle between jsGridView and jsListView
<div className="project-boxes jsGridView">
So why not use a state variable to store the class name? Then use the onClick even to set it.
const [cName, setClassName] = useState('jsGridView');
return (
<Fragment>
<button className="view-btn list-view" title="List View" onClick={() => setClassName('jsListView')}>
List View
</button>
<button className="view-btn list-view" title="Grid View" onClick={() => setClassName('jsGridView')}>
Grid View
</button>
<div className={"project-boxes "+cName}>
{!loading && records.length === 0 ? (<h4 style={{ margin: '20px' }} className='center'>No
records, sorry</h4>) : records.map((record, key) => (
<RecordItem key={key} record={record} isFilter={isFilter} filterByWhat={filterByWhat} />
))}
</div>
</Fragment>
)
So here you set your class to jsGridView initially so it renders in grid view by default. But you also have 2 buttons that can flip it between grid and list view.
You can also add an active class to the button if you want.
<button className={"view-btn list-view"+(cName === 'jsListView' ? ' active_btn':'')} title="List View" onClick={() => setClassName('jsListView')}>
List View
</button>
<button className={"view-btn list-view"+(cName === 'jsGridView' ? ' active_btn':'')} title="Grid View" onClick={() => setClassName('jsGridView')}>
Grid View
</button>
If one class is on, and the other is off, you can do
function toggleClass(elem) {
const classList = elem.classList;
classList.toggle('class1');
classList.toggle('class2');
}
Now the on/off status of the two classes is reversed
Also, in your Styles / CSS file, you can add :not on one class and then not need to use the other class, like
#elem.class1 {
color: ...;
font-size: ...;
}
#elem.class2, #elem:not(.class1) {
color: ...;
font-size: ...;
}
So that :not(.class1) has the same styling effects as adding class2
In React, it's uncommon to mix "vanilla" imperative JS and React like this. React provides a clean solution for toggling displayed elements that I would advise you use instead.
Take the following as an example:
// list-view.jsx
const ListView = ({ items=[] }) => {
const itemsElements = items.map(item => {
return (<li>{item}</li>)
});
return (<ul>{itemsElements}</ul>)
}
// grid-view.jsx
const GridView = ({ items=[] }) => {
const itemsElements = items.map(item => {
return (<span>{item} </span>)
});
return (<div>{itemsElements}</div>)
}
// list-grid-view.jsx
const ListGridView = ({ items=[] }) => {
const [listView, setListView] = React.useState(true);
// this fn toggles the listView variable
const toggleListView = React.useCallback(() => {
setListView(!listView);
}, [listView, setListView]);
return (
<div>
<button onClick={toggleListView} >Toggle!</button>
{listView ? <ListView items={items} /> : <GridView items={items} />}
</div>
);
}
const items = ['Hello', 'World', '!'];
const element = <ListGridView items={items} />
ReactDOM.render(element, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Hide or show mutliple Divs in Reactjs

I have created a component in my ReactJS app with a Button and a div. My goal is to press the button and show/hide the div, which currently works. But I will re-use the component so I will have multiple Buttons and divs. But I always only want one div to show.
If I press a button, hide all the divs from the same component and show the div from the button I just pressed, otherwise if the button I just pressed div is open hide it. It work the same as Bootstrap's dropdown button, but I prefer not to use Bootstrap's dropdown as I would like to create my own custom button.
I import the below Hide component in my App.js file. It works by hiding or showing the div, but would like to hide all other open div's apart from the button I currently pressed if it is not open yet.
Here is the code I currently have and use my Mycomp twice,
function hide () {
return (
<div>
<Mycomp />
<Mycomp />
</div>
);
}
function Mycomp(){
const[dp, setDp] = useState("none");
const toggle = () => {
if (dp === "none"){
setDp("block")
}else{
setDp("none")
}
}
return(
<div>
<button onClick={toggle}>Test</button>
<div style={{display: dp}}>{dp}</div>
</div>
)
}
export default hide;
You can change your component this way to get what you want . try to run this code to see the result
function Hide() {
const [visibleDivIndex, setVisibleDivIndex] = React.useState(0);
const handleVisibleDiv = id => setVisibleDivIndex(id);
const divArr = [1, 2, 3]; // just to show haw many component we will use
return (
<div>
{divArr.map((item, index) => (
<Mycomp
key={index}
id={index}
visibleDivIndex={visibleDivIndex}
handleVisibleDiv={handleVisibleDiv}
/>
))}
</div>
);
}
function Mycomp({ id, visibleDivIndex, handleVisibleDiv }) {
return (
<div>
<button onClick={e => handleVisibleDiv(id)}>Test</button>
<div style={{ display: id === visibleDivIndex ? "block" : "none" }}>
My Div Content
</div>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(
<React.StrictMode>
<Hide />
</React.StrictMode>,
rootElement
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
The basic idea would be to move the state up to the container.
function Hide() {
const defaultDisplay = () => Array.from({length: 2}).fill(false);
const [displays, setDisplays] = useState(defaultDisplay());
function onClick(id) {
const temp = defaultDisplay();
temp[id] = true;
setDisplays(temp);
}
return (
<div>
{
displays.map((display, i) => {
return <Mycomp display={display} id={i} onClick={onClick} />;
}
}
</div>
);
}
function Mycomp({display, id, onClick}) {
const[dp, setDp] = useState(display);
useEffect(() => {
setDp(display ? 'block' : 'none');
}, [display]);
return(
<div>
<button onClick={() => onClick(id)}>Test</button>
<div style={{display: dp}}>{dp}</div>
</div>
)
}
export default Hide;

Categories