I am trying to open a modal from another component. this is in my parent component:
import { Button, Modal } from 'react-bootstrap';
import React, { useState } from 'react';
import './App.css';
import ModalB from './ModalB';
function App() {
const [showA, setShowA] = useState(false);
const [showB, setShowB] = useState(false);
const handleCloseA = () => setShowA(false);
const handleShowA = () => setShowA(true);
const handleShowB = ({ handleShow }) => {
setShowB(handleShow);
};
return (
<div className="App">
<header className="App-header">
<Button variant="primary" onClick={handleShowA}>Open A</Button>
<Button variant="primary" onClick={handleShowB}>Open B</Button>
<Modal show={showA} onHide={handleCloseA}>
<Modal.Header closeButton>
<Modal.Title>In Modal A</Modal.Title>
</Modal.Header>
</Modal>
<ModalB isModalVisible={showB}></ModalB>
</header>
</div>
);
}
export default App;
And Modal B component:
import { Button, Modal } from 'react-bootstrap';
import React, { useState } from 'react';
import { propTypes } from 'react-bootstrap/esm/Image';
const ModalB = (props) => {
const [showB, setShowB] = useState(false);
const handleCloseB = () => setShowB(false);
const handleShowB = () => setShowB(true);
return (
<div>
<Modal show={props.isModalVisible} onHide={handleCloseB}>
<Modal.Header closeButton>
<Modal.Title>In Modal B</Modal.Title>
</Modal.Header>
</Modal>
</div>
);
}
export default ModalB;
The problem is to display B from the main component. While displaying modalA is simple, I don't understand how to tell B to display from the main component.
Thanks for your help.
Remove the "show" state from ModalB and pass in the handleShowB handler from the parent.
const ModalB = ({ isModalVisible, handleShowB }) => {
return (
<div>
<Modal show={props.isModalVisible} onHide={handleShowB}>
<Modal.Header closeButton>
<Modal.Title>In Modal B</Modal.Title>
</Modal.Header>
</Modal>
</div>
);
}
In parent pass handleShowB handler. Here we just pass an anonymous callback to call the setShowB state updater and update the showB state to be false.
<ModalB
isModalVisible={showB}
handleShowB={() => setShowB(false)}
/>
const handleShowB = ({ handleShow }) => {
setShowB(handleShow); };
Why do you need handleShow if this is undefined?
Just update your state based on current state and the rest of your code would work fine.
const handleShowB = () => setShowB(!showB);
This will trigger the current modal state (weather hide/show) for ModalB component and it will render accordingly.
Here's working demo: https://codesandbox.io/s/show-multiple-modals-edv6y
Related
I am starting with React. I am trying to send a var and function to my component. I know that it is a bad practice to use :any that is why I want to change for a proper way.
I am doing a modal and I am sending the data to my component this way. I am using useState
Datatable.tsx
import { useEffect, useMemo, useState } from "react";
import Modal from "../modal/Modal";
const Datatable = () => {
const [show, setShow] = useState<boolean>(false);
return (
<div>
<Modal show={show} closeModal={() => setShow(false)} />
<button onClick={() =>setShow((s) => !s)}>
Open Modal
</button>
<tableStuff/>
<div/>
);
Modal.tsx
import "./modal.scss";
import React from "react";
import ReactDOM from "react-dom";
const Modal = (props:any) => {
const portal = document.getElementById("portal");
if (!portal) {
return null;
}
if (!props.show) {
return null;
}
return ReactDOM.createPortal(
<>
<div className="modal" onClick={props.closeModal}>
<div className="content">
<h2>Simple modal</h2>
</div>
</div>
</>,
portal
);
};
export default Modal;
I have seen this on tons of videos, but the following piece of code does not work for me.
I am getting this error Binding element 'show' implicitly has an 'any' type and Binding element 'closeModal' implicitly has an 'any' type
//...
const Modal = ({show, closeModal}) => {
if (show) {
return null;
}
//...
return ReactDOM.createPortal(
<>
<div className="modals" onClick={closeModal}>
<button onClick={closeModal}>Close</button>
</div>
</>,
portal
);
}
Is something else I am missing in order to not use (props:any)? Any help or suggestion would be nice.
interface ModalProps {
show: boolean;
closeModal: () => void;
}
const Modal = ({show, closeModal}: ModalProps) => {
I have two components, the parent and child. Currently I have these codes below. But unfortunately it returns an error:
TypeError: Cannot read property 'click' of null
For some reasons I want when button is click the Item component also will be click. But these codes below produces an error above. Anyone does know how to achieve it?
import React, { useRef } from 'react';
const App = (props) => {
const itemRef = useRef(null);
return (
<div>
{dynamicBoolean ? (
<button onClick={() => itemRef.current.click()}>
click item
</button>
) : (
//more codes here
<Item ref={itemRef} />
)}
</div>
);
};
export default App;
Child component would look like below (demonstration purposes, the code is very lengthly)
import React from 'react';
const Item = (props) => {
return (
<div>
//some design here
</div>
);
};
export default Item;
You need useRef and you have to forward this ref to the Item component.
import React, { forwardRef, useRef } from 'react';
const Item = forwardRef((props, ref) => {
return <li {...props}
onClick={() => alert('clicked on Item')}
ref={ref} >MyItem</li>
})
const App = (props) => {
const itemRef = useRef(null);
return (
<div>
<button onClick={() => itemRef.current.click()}>
click item
</button>
<Item ref={itemRef} />
</div>
);
};
export default App;
import React, { createRef } from "react";
const Hello = (props) => {
const itemRef = createRef();
const hello = () => {
itemRef.current.click();
};
return (
<div>
<button onClick={() => hello()}>click item</button>
<Item ref={itemRef} />
</div>
);
};
const Item = React.forwardRef((props, ref) => {
const myClick = () => {
console.log("this is clicked");
};
return (
<button ref={ref} className="FancyButton" onClick={myClick}>
{props.children}
</button>
);
});
export default Hello;
I'm having a TypeError when I try to map through the cart of products and display it in a modal.
I'm using redux to store my states and access them from other components.
Can someone tell me what I'm doing wrong?
This is the Card component from where I send the product to the cart state:
const ProductCard = (props) => {
const product = props.product
return (
<div className="col-lg-4 col-6">
<div className="product-top">
<img src={product.image} alt={product.name} />
<div className="product-bottom">
<h3>{product.name}</h3>
<p>{`${product.price} MT`}</p>
</div>
<button className="btn add-cart-btn" onClick={() => props.addProductsToCart(product)}>Adicionar ao carrinho</button>
</div>
</div>
)
}
const mapDispatchToProps = dispatch => ({
addProductsToCart: product => dispatch(addCartProducts(product))
})
export default connect(null, mapDispatchToProps)(ProductCard);
This is the component where I access the name and price of the cart product:
const CartProduct = (props) => {
const { name, price } = props.product;
return (
<div>
<span>{name}</span>
<span>{price}</span>
</div>
)
}
export default CartProduct;
This is the component where I map the cart products array and set the id and the product itself:
import React from 'react';
import { connect } from 'react-redux';
import { Modal, Button } from 'react-bootstrap';
import CartProduct from './CartProduct';
import '../../styles/_cart-dropdown.scss';
import { setCartModalStatus } from '../../redux/status/status.action';
const CartModal = (props) => {
const status = props.cartModalStatus;
return (
<Modal
size="lg"
aria-labelledby="contained-modal-title-vcenter"
centered
show={status}
onHide={props.setCartModalStatus}
>
<Modal.Header closeButton>
<Modal.Title id="contained-modal-title-vcenter">
Modal heading
</Modal.Title>
<Modal.Title id="contained-modal-title-vcenter">
Modal heading
</Modal.Title>
</Modal.Header>
<Modal.Body>
**{
props.cartProductsArr.map(cartProduct => {
return <CartProduct key={cartProduct._id} product={cartProduct}/>
})
}**
</Modal.Body>
</Modal>
)
}
const mapStateToProps = state => ({
cartModalStatus: state.status.cartmodal,
cartProductsArr: state.cart.cartProducts
})
const mapDispatchToProps = dispatch => ({
setCartModalStatus: () => dispatch(setCartModalStatus())
})
export default connect(mapStateToProps, mapDispatchToProps)(CartModal);
cartProductsArr is an object and not an array. In your mapStateToProps, from the naming I would guess that state.cart could be your array. I would console.log your state in your mapState just to know exactly everything being passed.
I have a table with dynamic data being displayed on it if the user click any of the table rows a modal pops up and shows some more details about the row clicked, on the modal the user has two options, one to see the full details which redirects him to a page with the full details of the row clicked and the other to hide the row.
I tried using the filter method on the data fetched from the API but I wasn't successful how will I go along about implementing this functionality.Any help would be very much appreciated.
Here is the code i used :
import React, { useEffect, useState } from 'react';
import axios from 'axios';
import BootStrapTable from "react-bootstrap-table-next"
import paginationFactory from 'react-bootstrap-table2-paginator';
import {Modal , Button} from "react-bootstrap"
import { useHistory } from "react-router-dom";
import Footer from "./Footer"
export default function DataTable (){
const history = useHistory();
const [companies,setCompanies] = useState([]);
const [modalInfo,setModalInfo] = useState([]);
// eslint-disable-next-line
const [showModal,setShowModal] = useState(false);
const [show ,setShow] = useState(false);
const handleCLose = () => setShow(false)
const handleShow = () => setShow(true)
const getCompaniesData = async () =>{
try{
const data = await axios.get("http://localhost:5000/companies")
setCompanies(data.data)
}
catch(e){
console.log(e)
}
}
useEffect(()=>{
getCompaniesData();
},[])
const columns = [
{dataField:"id",text:"id"},
{dataField:"name",text:"name"},
]
const rowEvents = {
onClick : (e,row)=>{
console.log(row)
setModalInfo(row)
toggleTrueFalse()
}
}
const toggleTrueFalse = () =>{
setShowModal(handleShow);
}
const handleDetails = () =>{
history.replace('/details', {details:modalInfo})
}
function handleHide (mid) {
return companies.filter((item) => {
return item.id === mid;
});
};
const ModalContent = () =>{
return ( <Modal show={show} onHide={handleCLose}>
<Modal.Header closeButton>
<Modal.Title>
{modalInfo.name}
</Modal.Title>
</Modal.Header>
<Modal.Body>
<h1 >Company Details :</h1>
<ul>
<ol>source_id : {modalInfo.source_id}</ol>
<ol>source_name : {modalInfo.source_name}</ol>
<ol>name : {modalInfo.name}</ol>
<ol>city : {modalInfo.city}</ol>
<ol>country : {modalInfo.country}</ol>
</ul>
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={handleDetails}>Full Details</Button>
<Button className="btn btn-danger" variant="secondary" onClick={handleHide(modalInfo.id)}>Hide Element</Button>
</Modal.Footer>
</Modal> )
}
return (
<div>
<h1 className="text-center">Share-work Data Table</h1>
<div className="table-back">
<BootStrapTable
keyField="id"
data={companies}
columns={columns}
pagination={paginationFactory()}
rowEvents = {rowEvents}
/>
<Footer/>
</div>
{show ? <ModalContent/> : null}
</div>
)
}
You need to actually update the state.
Instead of this:
function handleHide (mid) {
return companies.filter((item) => {
return item.id === mid;
});
};
update the state with the setCompanies function you defined above (const [companies,setCompanies] = useState([]);)
This is what you code should look like instead:
function handleHide (mid) {
setCompanies(companies.filter((item) => {
return item.id === mid;
}));
};
Material ui dialog closes the snackbar along with it.
This is a weird problem so I created a demo to demonstrate the issue:
https://codesandbox.io/s/react-hooks-counter-demo-v20w3
I am passing states from parent to child so the parent state can update based on the child
<Child openDialog={openDialog} setOpenDialog={setOpenDialog} />
In the child, I am using these as below
export default function Child({openDialog, setOpenDialog}) {
The button in the child is supposed to close only the dialog but it closes snackbar too.
<button
onClick={() => dialogClick()}
>Click this dialog button
</button>
Line 12 in the child has setOpenSnack("true"); This only works when I comment out line 13 setOpenDialog(false);
const dialogClick = (event) => {
setMsg("This should close dialog only and open the snackbar");
setOpenSnack(true);
setOpenDialog(false);
};
This behavior is only when I split it into child and parent.
In short, why does setOpenSnack(true); in the child not work?
Move your Snackbar to your parent component so that it's not dependent on Dialog's lifecycle.
App.js
import DialogBody from "./child";
function App() {
const [openDialog, setOpenDialog] = useState(false);
const [openSnack, setOpenSnack] = useState(false);
const [msg, setMsg] = useState("nothing");
function doApiCall(formData) {
// do your API calls here
console.log(formData);
// when done hide dialog, show snackbar
setOpenDialog(false);
setOpenSnack(true);
}
const fireOnClick = (event) => {
setMsg("you clicked a button");
setOpenDialog(true);
};
const handleCloseSnack = (event, reason) => {
if (reason === "clickaway") {
return;
}
setOpenSnack(false);
};
function Alert(props) {
return <MuiAlert elevation={6} variant="filled" {...props} />;
}
return (
<div className="App">
<h3>click button below to open dialog</h3>
<button onClick={() => fireOnClick()}>Click me</button>
<Dialog open={openDialog}>
<DialogTitle>This is a dialog</DialogTitle>
<DialogBody doApiCall={doApiCall} />
</Dialog>
<Snackbar
open={openSnack}
autoHideDuration={6000}
onClose={handleCloseSnack}
>
<Alert onClose={handleCloseSnack} severity="error">
{msg}
</Alert>
</Snackbar>
</div>
);
}
Child.js
export default function DialogBody({ doApiCall }) {
const [name, setName] = React.useState("");
function onChange(event) {
setName(event.target.value);
}
return (
<div>
<input type="text" value={name} onChange={onChange} />
<button onClick={() => doApiCall({ name })}>
Click this dialog button
</button>
</div>
);
}
Our DialogBody component only accepts a prop called doApiCall from our parent, which is invoked by clicking the Click this dialog button.
doApiCall might be an asynchronous call to your backends API which when resolves will hide the dialog and sets the snackbar to open.
When the dialog is closed, the Child component is dismantled. This justifies the disappearance of the Snack.
Try this to see:
index.js
import React, { useState } from "react";
import ReactDOM from "react-dom";
import DialogTitle from "#material-ui/core/DialogTitle";
import Dialog from "#material-ui/core/Dialog";
import Child from "./child";
import Snackbar from "#material-ui/core/Snackbar";
import MuiAlert from "#material-ui/lab/Alert";
import "./styles.css";
function App() {
const [msg, setMsg] = useState("nothing");
const [openSnack, setOpenSnack] = useState(false);
const [openDialog, setOpenDialog] = useState(false);
//Close snackbar except when clickaway
const fireOnClick = (event) => {
setMsg("you clicked a button");
setOpenDialog(true);
//setOpenSnack(true);
};
const snackState = (value) => {
setOpenSnack(value)
}
const snackMessage = (value) => {
setMsg(value)
}
const handleCloseSnack = (event, reason) => {
if (reason === "clickaway") {
return;
}
setOpenSnack(false);
};
function Alert(props) {
return <MuiAlert elevation={6} variant="filled" {...props} />;
}
const closeDialogOnly = (event) => {
setMsg("you closed dialog only");
setOpenDialog(false);
};
return (
<div className="App">
<h3>click button below to open dialog</h3>
<button onClick={() => fireOnClick()}>Click me</button>
<Dialog onClose={closeDialogOnly} open={openDialog}>
<DialogTitle>This is a dialog</DialogTitle>
<Child snackbarStat={snackState} snackMessage={snackMessage} setOpenDialog={setOpenDialog} />
</Dialog>
<Snackbar
open={openSnack}
autoHideDuration={6000}
onClose={handleCloseSnack}
>
<Alert onClose={handleCloseSnack} severity="error">
{msg}
</Alert>
</Snackbar>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
child.js
import React from "react";
export default function Child({ snackbarStat, setOpenDialog, snackMessage }) {
const dialogClick = (event) => {
snackMessage("snackMessage")
snackbarStat(true);
setOpenDialog(false);
};
return (
<div className="child">
<button onClick={() => dialogClick()}>Click this dialog button</button>
<p>
{" "}
Clicking the button should close dialog but why is it closing the
snackbar also? <br />
The snack bar opens for one second and disappears.
<br />
setOpenSnack("true") lines is useless when dialog is false.
</p>
</div>
);
}
You have to place the Snackbar in the parent component because when placing it in child component, it will disappear whenever the child is unmounted
Moreover, if you want to set the message, just pass the setMsg to the child component and do the job
export default function Child({
openDialog,
setOpenDialog,
setMsg,
setOpenSnack
}) {
const dialogClick = (event) => {
setMsg("you closed from CHILD");
setOpenSnack(true);
setOpenDialog(false);
};
return (
<div className="child">
<button onClick={() => dialogClick()}>Click this dialog button</button>
<p>
...
</p>
</div>
);
}