My problem might be more related to js syntax, but the thing is, in material-ui we receive the js code for the Modal function, so how, from another file, does one calls the modal to be opened on a button click (in this case it onRowClicked in a table)
The Modal.js file with relevant code is:
export default function TransitionsModal() {
const classes = useStyles();
const [open, setOpen] = React.useState(false);
const handleOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
return (
<div>
<Modal
aria-labelledby="transition-modal-title"
aria-describedby="transition-modal-description"
className={classes.modal}
open={open}
onClose={handleClose}
closeAfterTransition
BackdropComponent={Backdrop}
BackdropProps={{
timeout: 500,
}}
>
The MainTable.js code with the relevant code is:
const openPopup = rowData => {
console.log(rowData.EBELN);
TransitionsModal().setOpen(true); <---- Trying to call the Modal and open it
}
const MainTable = props => {
return (
<div className="col-md-12">
<div className="feed-toggle">
<ul className="nav nav-pills outline-active">
</ul>
</div>
<MaterialTable
columns = {columnsSetup}
options = {{ NOT_RELATED }}
onRowClick={(event, rowData) => openPopup(rowData)} -> calls Open Popup
data={query => NOT_RELATED
});
})
})
}
/>
</div>
);
};
I think that you can try to control the TransitionModal from your MainList component :
const MainTable = props => {
const [showModal , setModal] = useState;
const handleModal = status => {
setModal(status);
}
return (
<div className="col-md-12">
<TransitionsModal
showModal={showModal}
handleModal={ handleModal }
/>
<div className="feed-toggle">
<ul className="nav nav-pills outline-active">
</ul>
</div>
<MaterialTable
columns = {columnsSetup}
options = {{ NOT_RELATED }}
onRowClick={(event, rowData) => openPopup(rowData)} -> calls Open Popup
data={query => NOT_RELATED
});
})
})
}
/>
</div>
);
};
And in your TransitionModal component you just have to pass the prop to control if it will show or not 'showModal' and the method that will allow it to close it handleModal, check that ive made one method that can handle open and close, just by passing the status parameter:
export default function TransitionsModal( { showModal, handleModal } ) {
const classes = useStyles();
return (
<div>
<Modal
aria-labelledby="transition-modal-title"
aria-describedby="transition-modal-description"
className={classes.modal}
open={ showModal }
onClose={ () => { handleModal(false) } }
closeAfterTransition
BackdropComponent={Backdrop}
BackdropProps={{
timeout: 500,
}}
>
Related
I have a few buttons and "view all" button. The individual buttons load the coresponding data of that index or will show all the data by clicking the "view all" button. Problem I am running into is when I click my "view all" button in the parent it's not updating the state in the child component. On mounting it works as normal but on event handler in the "view all" it doesn't update. Any thoughts on where I am going wrong here?
JS:
...
const Context = createContext(false);
const useStyles = makeStyles((theme) => ({
root: {
display: "flex",
"& > *": {
margin: theme.spacing(1)
}
},
orange: {
color: theme.palette.getContrastText(deepOrange[500]),
backgroundColor: deepOrange[500],
border: "4px solid black"
},
info: {
margin: "10px"
},
wrapper: {
display: "flex"
},
contentWrapper: {
display: "flex",
flexDirection: "column"
},
elWrapper: {
opacity: 0,
"&.active": {
opacity: 1
}
}
}));
const ToggleItem = ({ id, styles, discription }) => {
const { activeViewAll, handleChange } = useContext(Context);
const [toggleThisButton, setToggleThisButton] = useState();
const handleClick = () => {
setToggleThisButton((prev) => !prev);
handleChange(discription, !toggleThisButton);
};
return (
<>
<Avatar
className={toggleThisButton && !activeViewAll ? styles.orange : ""}
onClick={handleClick}
>
{id}
</Avatar>
<p>{JSON.stringify(toggleThisButton)}</p>
</>
);
};
const ToggleContainer = ({ className, selected }) => {
return (
<div className={className}>
{selected.map((item, idx) => (
<div key={idx}>Content {item}</div>
))}
</div>
);
};
export default function App() {
const data = ["first", "second", "third"];
const classes = useStyles();
const [selected, setSelected] = useState([]);
const [activeViewAll, setActiveViewAll] = useState(false);
useEffect(() => {
setActiveViewAll(true);
setSelected([...data]);
}, []);
const handleChange = (val, action) => {
let newVal = [];
if (activeViewAll) {
selected.splice(0, 3);
setActiveViewAll(false);
}
if (action) {
newVal = [...selected, val];
} else {
// If toggle off, then remove content from selected state
newVal = selected.filter((v) => v !== val);
}
console.log("action", action);
setSelected(newVal);
};
const handleViewAll = () => {
console.log("all clicked");
setActiveViewAll(true);
setSelected([...data]);
};
return (
<Context.Provider value={{ activeViewAll, handleChange }}>
<div className={classes.wrapper}>
<Avatar
className={activeViewAll ? classes.orange : null}
onClick={handleViewAll}
>
<span style={{ fontSize: "0.75rem", textAlign: "center" }}>
View All
</span>
</Avatar>
{data.map((d, id) => {
return (
<div key={id}>
<ToggleItem id={id} styles={classes} discription={d} />
</div>
);
})}
</div>
<div className={classes.contentWrapper}>
<ToggleContainer styles={classes} selected={selected} />
</div>
</Context.Provider>
);
}
Codesanbox:
https://codesandbox.io/s/72166087-forked-jvn59i?file=/src/App.js:260-3117
Issue
The issue seems to be that you are mixing up the management of the boolean activeViewAll state with the selected state.
Solution
When activeViewAll is true, pass the data array as the selected prop value to the ToggleContainer component, otherwise pass what is actually selected, the selected state.
Simplify the handlers. The handleViewAll callback only toggles the view all state to true, and the handleChange callback toggles the view all state back to false and selects/deselects the data item.
function App() {
const data = ["first", "second", "third"];
const classes = useStyles();
const [selected, setSelected] = useState([]); // none selected b/c view all true
const [activeViewAll, setActiveViewAll] = useState(true); // initially view all
const handleChange = (val, action) => {
setActiveViewAll(false); // deselect view all
setSelected(selected => {
if (action) {
return [...selected, val];
} else {
return selected.filter(v => v !== val)
}
});
};
const handleViewAll = () => {
setActiveViewAll(true); // select view all
};
return (
<Context.Provider value={{ activeViewAll, handleChange }}>
<div className={classes.wrapper}>
<Avatar
className={activeViewAll ? classes.orange : null}
onClick={handleViewAll}
>
<span style={{ fontSize: "0.75rem", textAlign: "center" }}>
View All
</span>
</Avatar>
{data.map((d, id) => {
return (
<div key={id}>
<ToggleItem id={id} styles={classes} discription={d} />
</div>
);
})}
</div>
<div className={classes.contentWrapper}>
<ToggleContainer
styles={classes}
selected={activeViewAll ? data : selected} // pass all data, or selected only
/>
</div>
</Context.Provider>
);
}
In the ToggleContainer don't use the array index as the React key since you are mutating the array. Use the element value since they are unique and changing the order/index doesn't affect the value.
const ToggleContainer = ({ className, selected }) => {
return (
<div className={className}>
{selected.map((item) => (
<div key={item}>Content {item}</div>
))}
</div>
);
};
Update
Since it is now understood that you want to not remember what was previously selected before toggling activeViewAll then when toggling true clear the selected state array. Instead of duplicating the selected state in the children components, pass the selected array in the context and computed a derived isSelected state. This maintains a single source of truth for what is selected and removes the need to "synchronize" state between components.
const ToggleItem = ({ id, styles, description }) => {
const { handleChange, selected } = useContext(Context);
const isSelected = selected.includes(description);
const handleClick = () => {
handleChange(description);
};
return (
<>
<Avatar
className={isSelected ? styles.orange : ""}
onClick={handleClick}
>
{id}
</Avatar>
<p>{JSON.stringify(isSelected)}</p>
</>
);
};
const ToggleContainer = ({ className, selected }) => {
return (
<div className={className}>
{selected.map((item) => (
<div key={item}>Content {item}</div>
))}
</div>
);
};
Update the handleChange component to take only the selected value and determine if it needs to add/remove the value.
export default function App() {
const data = ["first", "second", "third"];
const classes = useStyles();
const [selected, setSelected] = useState([]);
const [activeViewAll, setActiveViewAll] = useState(true);
const handleChange = (val) => {
setActiveViewAll(false);
setSelected((selected) => {
if (selected.includes(val)) {
return selected.filter((v) => v !== val);
} else {
return [...selected, val];
}
});
};
const handleViewAll = () => {
setActiveViewAll(true);
setSelected([]);
};
return (
<Context.Provider value={{ activeViewAll, handleChange, selected }}>
<div className={classes.wrapper}>
<Avatar
className={activeViewAll ? classes.orange : null}
onClick={handleViewAll}
>
<span style={{ fontSize: "0.75rem", textAlign: "center" }}>
View All
</span>
</Avatar>
{data.map((d, id) => {
return (
<div key={d}>
<ToggleItem id={id} styles={classes} description={d} />
</div>
);
})}
</div>
<div className={classes.contentWrapper}>
<ToggleContainer
styles={classes}
selected={activeViewAll ? data : selected}
/>
</div>
</Context.Provider>
);
}
I am trying to toggle a class for a specific element inside a loop.
const ItemList: React.FC<ListItemUserProps> = (props) => {
const { items } = props;
const [showUserOpt, setShowUserOpt] = useState<boolean>(false);
function toggleUserOpt() {
setShowUserOpt(!showUserOpt);
}
const userOptVisible = showUserOpt ? 'show' : 'hide';
return (
<>
{items.map((t) => (
<React.Fragment key={t.userId}>
<div
className={`item ${userOptVisible}`}
role="button"
tabIndex={0}
onClick={() => toggleUserOpt()}
onKeyDown={() => toggleUserOpt()}
>
{t.userNav.firstName}
</div>
</React.Fragment>
))}
</>
);
};
export default ItemList;
When I click on an element, the class toggles for every single one.
You can create another component that can have it's own state that can be toggled without effecting other sibling components' state:
Child:
const ItemListItem: React.FC<SomeInterface> = ({ item }) => {
const [show, setShow] = useState<boolean>(false);
const userOptVisible = show ? "show" : "hide";
const toggleUserOpt = (e) => {
setShow((prevState) => !prevState);
};
return (
<div
className={`item ${userOptVisible}`}
role="button"
tabIndex={0}
onClick={toggleUserOpt}
onKeyDown={toggleUserOpt}
>
{item.userNav.firstName}
</div>
);
};
Parent:
const ItemList: React.FC<ListItemUserProps> = ({ items }) => {
return (
<>
{items.map((t) => (
<ItemListItem key={t.userId} item={t} />
))}
</>
);
};
If you simply adding classes to the element, I would keep it simple and use a handler to toggle the class using pure JS.
const handleClick = (e) => {
// example of simply toggling a class
e.currentTarget.classList.toggle('selected');
};
Demo:
const {
useState,
} = React;
// dummy data
const data = Array(20).fill(null).map((i, index) => `item ${(index + 1).toString()}`);
function App() {
const [items, setItems] = useState(data);
const handleClick = (e) => {
e.currentTarget.classList.toggle('selected');
};
return (
<div>
{items.map((item) => (
<button key={item} onClick={handleClick}>{item}</button>
))}
</div>
);
}
ReactDOM.render( <
App / > ,
document.getElementById("app")
);
.selected {
background: red;
}
<script crossorigin src="https://unpkg.com/react#17/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#17/umd/react-dom.production.min.js"></script>
<div id="app"></div>
I think it'd be best if you kept track of the index so that you could target a single item in your list. As it stands the boolean is going to change the styling for all as you haven't specified which one should get the className.
Add a useState hook to keep track of it like:
const [activeIndex, setActiveIndex] = useState(null);
Then create a new function:
function handleIndexOnClick(index) {
setActive(index);
}
Then in your map() function add index. You'll then need to pass index in to you className attribute and the onClick function. The end result for that bit should look like:
{items.map((t, index) => (
<React.Fragment key={t.userId}>
<div
className={`item ${activeIndex && items[activeIndex] ? 'show' : 'hide }`}
role="button"
tabIndex={0}
onClick={() => handleIndexOnClick(index)}
onKeyDown={() => toggleUserOpt()}
>
{t.userNav.firstName}
</div>
</React.Fragment>
))}
I'm using Strapi and Next.js to create a Dropdown Menu, but when i hover on a menu button, all dropdown container of the menu show up.
Can anybody show me the way to do it right? Thank you very much!
This is my code:
const Navbar = ({ navbar }) => {
const [open, setOpen] = useState(false);
const [mobileMenuIsShown, setMobileMenuIsShown] = useState(false);
const handleClick = () => {setOpen(!open)};
const onMouseEnter = () =>{
if (window.innerWidth < 960) {
setOpen(false);
} else {
setOpen(true);
}
};
const onMouseLeave = () =>{
if (window.innerWidth < 960){
setOpen(false);
} else {
setOpen(false);
}
};
return (
<>
<nav>
<div>
{navbar.topmenu.map((navMenu) => (
<div
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
key={navMenu.id}
>
<button
onClick={handleClick}>
<span class="mr-1">{navMenu.label}</span>
</button>
{open &&
<ul>
{navMenu.submenu.map((link) => (
<li key={link.id}>
<CustomLink link={link}>{link.text}</CustomLink>
</li>
))}
</ul>
}
</div>
))}
</div>
</nav>
</>
In React I created a component that holds a local state of popup. With a little help from onClick handler I change the local state to make the popup show up. The Popup component in turn contains confirm button. I would like to call a function deleteItem ONLY after the confirm button is clicked. But I don't get how to do it. In the code below the item gets deleted right after the popup shows up but it has to be deleted only if I press the button in the Popup component. If I understand correctely, the state of the components changes when the popup shows up and I have to get it khow to the MainComponent and only in this case the function deleteItem will be called.
import {deleteItem} from './item-reducer';
const MainComponent = ({items}) => {
const [visiblePopup, setVisiblePopup] = useState(false);
return(
{items.map(item => <li key={item.id}></li>
<img onClick={() => {
setVisiblePopup(true);
deleteList(item.id) // I have to call this function after the button
in the Popup component is pressed
}}
/>
)}
<Popup setVisiblePopup={setVisiblePopup}
)
}
Popup.jsx
<div onClick={() => setVisiblePopup(false)} />
Confirm
</div>
What I have to accomplish 1) I click img and popup shows up 2) I press
'Confirm' 3) function deleteItem is invoked 4)popup dissapeares.
If I understand you correctly this is what your looking for ?
const MainComponent = ({items}) => {
const [modalState, setModalState] = useState({
display: false,
deleteItemId: undefined
});
const modalCallback = useCallback((deleteItemId)=>{
deleteItem(deleteItemId)
setModalState({ display: false, deleteItemId: undefined })
},[])
return(
<Fragment>
{
items.map(item => (
<Fragment>
<li key={item.id}></li>
<img onClick={() => setModalState({ display: true, deleteItemId: item.id })} />
</Fragment>
))
}
<PopupModal
visible={modalState.display}
deleteItemId={modalState.deleteItemId}
callback={modalCallback}
/>
</Fragment>
)
}
const PopupModal = ({ visible, deleteItemId, callback }) => {
return (visible ? <div onClick={ () => callback(deleteItemId)}>Confirm</div> : null)
}
--- OR ----
const MainComponent = ({items}) => {
const [modalState, setModalState] = useState({
display: false,
deleteItemId: undefined
});
return(
<Fragment>
{
items.map(item => (
<Fragment>
<li key={item.id}></li>
<img onClick={() => setModalState({ display: true, deleteItemId: item.id })} />
</Fragment>
))
}
modalState.display ? <div onClick={() => [deleteList(modalState.deleteItemId), setModalState({display: false, deleteItemId: undefined}) ]}>Confirm</div> : null
</Fragment>
)
}
I have 2 onClick functions
function VisitGallery(name) {
const history = useHistory();
console.log("visitgallery", name)
history.push("/gallery")
}
function App() {
const accesstoken = "******************"
const [viewport, setviewport] = React.useState({
latitude: ******
longitude: *******
width: "100vw",
height: "100vh",
zoom: 11
})
const [details, setdetails] = React.useState([
])
React.useEffect(() => {
const fetchData = async () => {
const db = firebase.firestore()
const data = await db.collection("data").get()
setdetails(data.docs.map(doc => doc.data()))
}
fetchData();
}, [])
const [selectedpark, useselectedpark] = React.useState(null);
React.useEffect(() => {
const listener = e => {
if (e.key === "Escape") {
useselectedpark(null);
}
};
window.addEventListener("keydown", listener)
return () => {
window.removeEventListener("keydown", listener)
}
}, [])
return (
<div className="App">
<ReactMapGl {...viewport}
mapboxApiAccessToken={accesstoken}
mapStyle="mapbox://**************"
onViewportChange={viewport => {
setviewport(viewport)
}}>
{details.map((details) =>
<Marker key={details.name} latitude={details.lat} longitude={details.long}>
<button class="marker-btn" onClick={(e) => {
e.preventDefault();
useselectedpark(details);
}}>
<img src={icon} alt="icon" className="navbar-brand" />
</button>
</Marker>
)}
{selectedpark ?
(<Popup
latitude={selectedpark.lat}
longitude={selectedpark.long}
onClose={() => {
useselectedpark(null);
}}
>
<div>
<Card style={{ width: '18rem' }}>
<Card.Body>
<Card.Title>{selectedpark.name}</Card.Title>
<Card.Text>
{selectedpark.postalcode}
</Card.Text>
<Button variant="primary" onClick = VisitGallery() >Visit Gallery</Button>
</Card.Body>
</Card>
</div>
</Popup>)
: null}
{
console.log("in render", details)
}
</ReactMapGl>
</div>
);
}
export default App;
The outer onClick is assigned when the marker is first created, and when it is clicked the useselectedpark function is called, details is then assigned to selectedpark.
The inner onClick is assigned to the function VisitGallery(). When the inner onClick is triggered, i want to navigate to another page, hence the history.push().
Ideally, what i want for it to happen is, when the outer onClick is triggered, the cardview shows, and i have an option to visit the next page, which can be triggered by an onClick within the card. However, what is happening right now is both the onClicks are triggered when i click on the thumbnail. How do i fix it such that it is how i want it to be ideally?
ps: do let me know if my explanation is confusing and i will edit it accordingly
Try adding your second onClick into a callback function?
<Button variant="primary" onClick='()=>{ VisitGallery() }' >Visit Gallery</Button>
So that it doesn't automatically invoke the function until the click is triggered.