how to call child component callback event from parent component in react - reactjs

Hi I am a beginner in React, I am using Fluent UI in my project .
I am planning to use Panel control from Fluent UI and make that as common component so that I can reuse it.I use bellow code
import * as React from 'react';
import { DefaultButton } from '#fluentui/react/lib/Button';
import { Panel } from '#fluentui/react/lib/Panel';
import { useBoolean } from '#fluentui/react-hooks';
export const PanelBasicExample: React.FunctionComponent = () => {
const [isOpen, { setTrue: openPanel, setFalse: dismissPanel }] = useBoolean(false);
return (
<div>
<Panel
headerText="Sample panel"
isOpen={isOpen}
onDismiss={dismissPanel}
// You MUST provide this prop! Otherwise screen readers will just say "button" with no label.
closeButtonAriaLabel="Close"
>
<p>Content goes here.</p>
</Panel>
</div>
);
};
https://developer.microsoft.com/en-us/fluentui#/controls/web/panel#best-practices
I remove <DefaultButton text="Open panel" onClick={openPanel} /> from the example .
So my question is how can I open or close this panel from any other component ?

I would use React useState hook for this.
Make a state in the component that you want render the Panel like
const [openPanel, setOpenPanel] = useState({
isOpen: false,
headerText: ''
})
Lets say for example you will open it from button
<Button onClick={() => setOpenPanel({
isOpen: true,
headerText: 'Panel-1'
})
}> Open me ! </Button>
Then pass the state as props to the Panel component
<PanelBasicExample openPanel={openPanel} setOpenPanel={setOpenPanel} />
in PanelBasicExample component you can extract the props and use it.
export const PanelBasicExample(props) => {
const {openPanel, setOpenPanel} = props
const handleClose = () => {setOpenPanel({isOpen: false})}
return (
<div>
<Panel
headerText={openPanel.headerText}
isOpen={openPanel.isOpen}
onDismiss={() => handleClose}
// You MUST provide this prop! Otherwise screen readers will just say "button" with no label.
closeButtonAriaLabel="Close"
>
<p>Content goes here.</p>
</Panel>
</div>
);
}

Related

Re-Rendering a component

I'm doing a simple todo list using React. What I fail to do is to remove an item once I click on the button.
However, if I click delete and then add a new item, it's working, but only if I add a new todo.
Edit:I've edited the post and added the parent componenet of AddMission.
import React,{useState}from 'react';
import { Button } from '../UI/Button/Button';
import Card from '../UI/Card/Card';
import classes from '../toDo/AddMission.module.css'
const AddMission = (props) => {
const [done,setDone]=useState(true);
const doneHandler=(m)=>{
m.isDeleted=true;
}
return (
<Card className={classes.users}>
<ul>
{props.missions.map((mission) => (
<li className={mission.isDeleted?classes.done:''} key={mission.id}>
{mission.mission1}
<div className={classes.btn2}>
<Button onClick={()=>{
doneHandler(mission)
}} className={classes.btn}>Done</Button>
</div>
</li>
)) }
</ul>
</Card>
);
};
export default AddMission;
import './App.css';
import React,{useState} from 'react';
import { Mission } from './components/toDo/Mission';
import AddMission from './components/toDo/AddMission';
function App() {
const [mission,setMission]=useState([]);
const [isEmpty,setIsEmpty]=useState(true);
const addMissionHandler = (miss) =>{
setIsEmpty(false);
setMission((prevMission)=>{
return[
...prevMission,
{mission1:miss,isDeleted:false,id:Math.random().toString()},
];
});
};
return (
<div className="">
<div className="App">
<Mission onAddMission={addMissionHandler}/>
{isEmpty?<h1 className="header-title">Start Your Day!</h1>:(<AddMission isVisible={mission.isDeleted} missions={mission}/>)}
</div>
</div>
);
}
const doneHandler=(m)=>{
m.isDeleted=true;
}
This is what is causing your issue, you are mutating an object directly instead of moving this edit up into the parent. In react we don't directly mutate objects because it causes side-effects such as the issue you are having, a component should only re-render when its props change and in your case you aren't changing missions, you are only changing a single object you passed in to your handler.
Because you haven't included the code which is passing in the missions props, I can't give you a very specific solution, but you need to pass something like an onChange prop into <AddMission /> so that you can pass your edited mission back.
You will also need to change your function to something like this...
const doneHandler = (m) =>{
props.onChange({
...m,
isDeleted: true,
});
}
And in your parent component you'll then need to edit the missions variable so when it is passed back in a proper re-render is called with the changed data.
Like others have mentioned it is because you are not changing any state, react will only re-render once state has been modified.
Perhaps you could do something like the below and create an array that logs all of the ids of the done missions?
I'm suggesting that way as it looks like you are styling the list items to look done, rather than filtering them out before mapping.
import React, { useState } from "react";
import { Button } from "../UI/Button/Button";
import Card from "../UI/Card/Card";
import classes from "../toDo/AddMission.module.css";
const AddMission = (props) => {
const [doneMissions, setDoneMissions] = useState([]);
return (
<Card className={classes.users}>
<ul>
{props.missions.map((mission) => (
<li
className={
doneMissions.includes(mission.id)
? classes.done
: ""
}
key={mission.id}
>
{mission.mission1}
<div className={classes.btn2}>
<Button
onClick={() => {
setDoneMissions((prevState) => {
return [...prevState, mission.id];
});
}}
className={classes.btn}
>
Done
</Button>
</div>
</li>
))}
</ul>
</Card>
);
};
export default AddMission;
Hope that helps a bit!
m.isDeleted = true;
m is mutated, so React has no way of knowing that the state has changed.
Pass a function as a prop from the parent component that allows you to update the missions state.
<Button
onClick={() => {
props.deleteMission(mission.id);
}}
className={classes.btn}
>
Done
</Button>;
In the parent component:
const deleteMission = (missionId) => {
setMissions(prevMissions => prevMissions.map(mission => mission.id === missionId ? {...mission, isDeleted: true} : mission))
}
<AddMission missions={mission} deleteMission={deleteMission} />

React functional component able to change its state, is possible?

I want to create a React component for a Dialog.
I want to use Bootstrap Modal component.
I use TypeScript.
This is the usage I plan to use:
<Dialog icon="edit" title="Edit the item" buttons={...} onSave={...} >
... put here the content of the dialog
</Dialog>
Now, I started to write the code to make the Modal visible (pseudo-code):
interface Props {
// open by:
buttonText: OpenIcons,
title: string,
isOpen?: boolean
}
export const Dialog:FC<Props> = (props) => {
let {buttonText, title, isOpen, children} = props
return <>
{button && <button className="btn btn-sm">{buttonText}}</button> }
{ isOpen && <div>
{title}
<div>{children}</div>
<buttons>...</buttons>
</div>
}
</>
}
The problem is: how can I manage to change the isOpen property to true and made the Dialog component reflect this state?
Yes, I can use another component in the page (button text, icon, link...) that manage to change that state and pass it to the Dialog component, but I don't want to.
I want the Dialog component to self-manage its icon/button/link trigger.
The only solution I can think about is create another component inside the Dialog component, a sort of wrapper.
It has to manage the DialogButton (or whatever) and a new DialogInternal component that are not exported, so that the developer just see the exorted Dialog component.
I hope this idea is clear.
There is any better way (simpler, cleaner) to achieve my goal?
Here is your code using a useState hook
export const Dialog:FC<Props> = (props) => {
const [open, setOpen] = React.useState(false);
let {buttonText, title, children} = props
const handleClick = () => setOpen(!open);
return <>
{button && <button className="btn btn-sm" onClick={handleClick}>{buttonText}}</button> }
{ open && <div>
{title}
<div>{children}</div>
<button onClick={handleClick}>Close</button>
</div>
}
</>
}
You can use react hooks like useState and useEffect for maintaining a state in functional components
Ex:
for Usestate
const [isOpen, setIsOpen] = React.useState(initial value);
for useEffect
React.useEffect(()=>{},[dependencies]);

Using a Custom React based Modal, how can I pass a dynamic triggering function so I can re-use the component?

I have the following component which makes up my modal:
import React from 'react';
import { ModalBody, Button, Alert } from 'bootstrap';
import { AppModalHeader } from '../../common/AppModalHeader';
import ModalWrapper from './ModalWrapper';
const QuestionModal= ({
title,
noText = 'No',
yesText = 'Yes',
questionText,
onYesAction
children
}) => {
const { toggle, isOpen, openModal } = useModalForm();
return (
<React.Fragment>
<ModalWrapper className={className} isOpen={isOpen} toggle={toggle}>
<AppModalHeader toggle={toggle}>{modalTitle}</AppModalHeader>
{isOpen ? (
<ModalBody>
<p>{questionText}</p>
<Button
className="float-right"
color="primary"
onClick={() => {
if (onYesAction !== undefined) {
onYesAction(toggle);
}
}}
>
{yesText != null ? yesText : 'Yes'}
</Button>
</ModalBody>
) : null}
</ModalWrapper>
{children({
triggerModal: () => openModal({ id: undefined }),
toggle
})}
</React.Fragment>
);
};
export default QuestionModal;
I want to use it as such, where I can dynamically choose the name of the trigger that opens the modal:
In use e.g. (note: the inner question modal would be repeated, used 4 or 5 times in my application):
....
<QuestionModal
//....params that match up with above
>
{({ triggerModal }) => (
<QuestionModal
//....params that match up with the component
>
{({ triggerModal2 }) => (
<>
<Button onClick={()=>triggerModal();}>Trigger Modal 1</Button>
<div>
<Button onClick={()=>triggerModal2();}>Trigger Modal 2</Button>
</div>
</>
</>
)}
</QuestionModal>
....
How could I achieve this, by extending the question modal to pass a dynamic function? Just because I keep getting stuck in having to think about duplicating the original component, I want to make this component as reusable as I can. Any help would be greatly appreciated.
Thanks in advance
I think you're overcomplicating things. The problem is you're trying to control whether or not the modal is rendered from inside the modal itself. If you really want to have reusable components, it's good to decouple presentation from logic. In your case, you want to have a modal component with all the presentation/layout/styling stuff and pass in via props the actual content.
For example:
import React from 'react';
import { ModalBody, Button, Alert } from 'bootstrap';
import { AppModalHeader } from '../../common/AppModalHeader';
import ModalWrapper from './ModalWrapper';
const QuestionModal= ({
title,
noText = 'No',
yesText = 'Yes',
questionText,
onYesAction
children
}) => {
return (
<React.Fragment>
<ModalWrapper>
<AppModalHeader toggle={toggle}>{title}</AppModalHeader>
<ModalBody>
<p>{questionText}</p>
<Button
className="float-right"
color="primary"
onClick={onYesAction}
>
{yesText}
</Button>
</ModalBody>
</ModalWrapper>
</React.Fragment>
);
};
export default QuestionModal;
Now this is a purely presentational component, it creates a skeleton in which you put the actual content. And for using it, you'll control whether or not the modal is rendered from where it is actually used, like so:
import React, {useState} from 'react';
import QuestionModal from './QuestionModal'
const SomeComponent = (props) => {
const [showModal, setShowModal] = useState(false);
const toggleModal = () => {
setShowModal(!showModal);
}
const yesActionLogic = () => {
// Your yes-action logic...
}
return (
<div>
{showModal ? (
<QuestionModal
title="Sample title",
questionText="Question?"
onYesAction={yesActionLogic}
/>
) : null}
<Button onClick={toggleModal}>Toggle Modal</Button>
{/* The rest of your stuff... */}
</div>
);
}
If you want to create reusable components, it's good practice to not put any business logic on it. Use props to pass in functions that will be triggered from inside the components, and lift all the work to the components that actually hold your business logic.
One of the SOLID principles of software engineering is called Single-responsibility principle, and you can apply it to your React components:
Your Modal component is responsible for displaying data in its correct layout and triggering some set of functions from outside, regardless of what data/logic you pass.
This Modal component will be used by some other component whose responsibility is to show the user a modal with some specific data, at the right time.
So it makes sense that you should toggle your modal from outside.
On a personal note, I like to structure a React app in components that hold only presentational logic, and are used by containers, which are more logic-dense (generally having async requests).

React - Proper way to render dynamic content?

I want to make a modal view has dynamic content by injecting a component to it.
class RootView extends Component {
state = {
modalBody: null
}
setModalBody = (body) => {
this.setState({modalBody: body})
}
render() {
return(<ContextProvider value={this.setModalBody}><Modal>{this.state.modalBody}</Modal></ContextProvider>)
}
}
Then inside any children view i use setState to change parent modalBody
The modalBody can be setted on each route, which means the modalBody can be input list, selection list or text only. So the modalBody must have its state for controlling these inputs.
By this way, it renders ok, but the dynamic content couldn't be updated after state changed. The parent's dynamic content couldn't receive the ChildView new state, i have to setModalBody again and again after it rerendered.
For example, if input in modalBody has changed, the parent couldn't be updated.
class ChildView extends Component {
state = {
inputValue: null
}
handleChange = (e) => {
this.setState({inputValue: e.target.value})
}
setModalBody(body) {
this.props.context.setModalBody(<input value={this.state.inputValue} onChange={this.handleChange} />)
}
render() {
return(<Modal>{this.state.modalBody}</Modal>)
}
}
Full code: https://codesandbox.io/s/lp5p20mx1m
Any proper way to render dynamic content to parent?
I'm not sure why you'd need to create a parent Modal component, when you can make the Modal a simple reusable child component.
See here for a detailed explanation on how to achieve a stateful parent that controls a child modal.
However, if you MUST have a parent Modal component, then you can create a render prop to pass down props to be used by its children.
Working example:
components/Modal.js (parent component -- this has a lot of smaller components that were separated for reusability and ease of understanding -- they're basically simple divs with some styles attached -- see notes below)
import React, { Fragment, Component } from "react";
import PropTypes from "prop-types";
import BackgroundOverlay from "../BackgroundOverlay"; // grey background
import ClickHandler from "../ClickHandler"; // handles clicks outside of the modal
import Container from "../Container"; // contains the modal and background
import Content from "../Content"; // renders the "children" placed inside of <Modal>...</Modal>
import ModalContainer from "../ModalContainer"; // places the modal in the center of the page
class Modal extends Component {
state = { isOpen: false };
handleOpenModal = () => {
this.setState({ isOpen: true });
};
handleCloseModal = () => {
this.setState({ isOpen: false });
};
// this is a ternary operator (shorthand for "if/else" -- if cond ? then : else)
// below can be read like: if isOpen is true, then render the modal,
// else render whatever the child component is returning (in this case,
// initially returning an "Open Modal" button)
render = () =>
this.state.isOpen ? (
<Container>
<BackgroundOverlay />
<ModalContainer>
<ClickHandler
isOpen={this.state.isOpen}
closeModal={this.handleCloseModal}
>
<Content>
{this.props.children({
isOpen: this.state.isOpen,
onCloseModal: this.handleCloseModal,
onOpenModal: this.handleOpenModal
})}
</Content>
</ClickHandler>
</ModalContainer>
</Container>
) : (
<Fragment>
{this.props.children({
isOpen: this.state.isOpen,
onCloseModal: this.handleCloseModal,
onOpenModal: this.handleOpenModal
})}
</Fragment>
);
}
// these proptype declarations are to ensure that passed down props are
// consistent and are defined as expected
Modal.propTypes = {
children: PropTypes.func.isRequired // children must be a function
};
export default Modal;
components/Example.js (child component accepting isOpen, onCloseModal and onOpenModal from the parent -- with this approach, as you'll notice, there's duplicate isOpen logic. While this approach gives you full control over the parent, it's repetitive. However, you can simplify your components by moving the "Open Modal" button logic to the parent, and passing in a prop like <Modal btnTitle="Open Modal"> to make it somewhat flexible, BUT you'll still lose some control of what's being initially rendered when isOpen is false.)
import React, { Fragment } from "react";
import Modal from "../Modal";
import "./styles.css";
const Example = () => (
<div className="example">
<h2>Parent Modal Example</h2>
<Modal>
{({ isOpen, onCloseModal, onOpenModal }) =>
isOpen ? (
<Fragment>
<h1 className="title">Hello!</h1>
<p className="subtitle">There are two ways to close this modal</p>
<ul>
<li>Click outside of this modal in the grey overlay area.</li>
<li>Click the close button below.</li>
</ul>
<button
className="uk-button uk-button-danger uk-button-small"
onClick={onCloseModal}
>
Close
</button>
</Fragment>
) : (
<button
className="uk-button uk-button-primary uk-button-small"
onClick={onOpenModal}
>
Open Modal
</button>
)
}
</Modal>
</div>
);
export default Example;

How to use multiple dialog boxes and render modals in ReactJS

I'm having trouble figuring out how to use Material UI's dialog box to render modals, close them upon button clicking and also make it so that clicking on different things brings up different modals.
This is the dialog component that I took from Material UI
import React from 'react';
import Button from '#material-ui/core/Button';
import Dialog from '#material-ui/core/Dialog';
import DialogActions from '#material-ui/core/DialogActions';
import DialogContent from '#material-ui/core/DialogContent';
import DialogContentText from '#material-ui/core/DialogContentText';
import DialogTitle from '#material-ui/core/DialogTitle';
class DialogBox extends React.Component {
render() {
return (
<Dialog
open={this.props.open}
onClose={this.props.handleClose}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">{"Use Google's location service?"}</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
Let Google help apps determine location. This means sending anonymous location data to
Google, even when no apps are running.
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={this.props.handleClose} color="primary">
Okay
</Button>
</DialogActions>
</Dialog>
);
}
}
export default DialogBox;
This is the page that I am rendering the Dialog Box in, I called it Modal in here. How do I make it so that I can close it when the dialog is open and also so I can click on a different picture and have it open up a dialog with different text?
import React,{Component} from "react";
import home from "./home.png";
import car from "./car.png";
import bed from "./bed.png";
import pet from "./pet.png";
import Dialog from "./Modal.js";
class Scenarios extends Component {
constructor(){
super();
this.state = { open: false };
}
openModal = () =>{
this.setState({ open: true });
}
handleClose = () => {
this.setState({ open: false });
};
render() {
return (
<section className="App-scenarios">
<h2> Quick Tips </h2>
<p>Know What to Do in Different Scenarios during an Earthquake</p>
<div className="scenario-group">
<div className="scenario" onClick={this.openModal}>
<img src={car}/>
</div>
<div className="scenario" onClick={this.openModal}>
<img src={home}/>
<Dialog open={this.state.open} onClose={this.handleClose} title="Home" description="text" />
</div>
<div className="scenario" >
<img src={bed}/>
</div>
<div className="scenario">
<img src={pet}/>
</div>
</div>
</section>
);
}
};
export default Scenarios;
You have a great start, but you're missing a few items. You just need to make your components more flexible and reusable (see below for comments).
Some notes: The example below uses ES6 destructuring, ES6 Fat Arrow Functions, the map function, the spread operator, and callback functions. In addition, you can't wrap a clickable element (Modal) inside of another clickable element (div). The outer most element (div) will only be handled by the onClick event handler.
Working example (for simplicity, I'm not using images, instead I'm using placeholder example.png titles that are clickable):
components/Modal
import React from "react";
import PropTypes from "prop-types";
import {
Button,
Dialog,
DialogActions,
DialogContent,
DialogContentText,
DialogTitle
} from "#material-ui/core";
// "Modal is a pure function that requires 4 parameters in order to display
// the "<Modal />" component. This function would be the same as a traditional function:
//
// function Modal({ deconstructed parameters }) {
// return (
// <Dialog>
// ...
// </Dialog>
// )
// }
const Modal = ({ description, onCloseModal, openModal, title }) => (
<Dialog
open={openModal}
onClose={onCloseModal}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">{title}</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
{description}
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={onCloseModal} color="primary">
Okay
</Button>
</DialogActions>
</Dialog>
);
// PropTypes throws a warning if any of the 4 required params are
// missing! In addition, it ensures that props are consistent in name
// and declaration from parent component to child component.
Modal.propTypes = {
description: PropTypes.string.isRequired,
onCloseModal: PropTypes.func.isRequired,
openModal: PropTypes.bool.isRequired,
title: PropTypes.string.isRequired
};
export default Modal;
components/Scenario
import React, { PureComponent } from "react";
import PropTypes from "prop-types";
// Scenario will be a PureComponent (stateless, but still a class component)
// that will open the Modal component with a supplied "description" and
// "title" via a parent callback function named "handleOpenModal"
class Scenario extends PureComponent {
openModal = () => {
const { description, handleOpenModal, title } = this.props;
handleOpenModal({ description, title });
};
render = () => (
<div className="scenario" onClick={this.openModal}>
<h1>{this.props.imgSrc}</h1>
</div>
);
}
Scenario.propTypes = {
description: PropTypes.string.isRequired,
handleOpenModal: PropTypes.func.isRequired,
imgSrc: PropTypes.string.isRequired,
title: PropTypes.string.isRequired
};
export default Scenario;
components/Scenarios
import React, { Component } from "react";
import Modal from "../Modal";
import Scenario from "../Scenario/";
// A scenarios variable of an array of objects -- makes it so we don't have to
// repeat <Scenario title=".." description="..."imgSrc={...} handleOpenModal={..} />
// over and over, instead we map over these objects as "props" and spread
// them out inside of "Scenario" like so: <Scenario {...props} />, which
// would be the same as:
// <Scenario title={title} description={description} imgSrc={imgSrc} />
const scenarios = [
{
title: "Car",
description: "This is a description for a car.",
imgSrc: "car.png"
},
{
title: "Home",
description: "This is a description for a home.",
imgSrc: "home.png"
},
{
title: "Bed",
description: "This is a description for a bed.",
imgSrc: "bed.png"
},
{
title: "Pet",
description: "This is a description for a pet.",
imgSrc: "pet.png"
}
];
// Scenarios is a stateful class component that'll act as the parent
// for its "Scenario" children. The children will update the parent via
// "this.handleOpenModal". Meanwhile, "Modal" will sit inside the parent
// waiting for the parent state updates to affect how its rendered. The
// Modal will close itself via the parent's "this.handleCloseModal"
// class method when the "Okay" button is clicked.
class Scenarios extends Component {
state = { description: "", openModal: false, title: "" };
handleOpenModal = ({ description, title }) => {
this.setState({ description, openModal: true, title });
};
handleCloseModal = () => {
this.setState({ openModal: false });
};
render = () => (
<section className="App-scenarios">
<h2> Quick Tips </h2>
<p>Know What to Do in Different Scenarios during an Earthquake</p>
<div className="scenario-group">
{scenarios.map(props => (
<Scenario
{...props}
key={props.title} // this is required for React to know that each "Scenario" that is returned by the "map" function is unique and must be handled as individual components (otherwise React will complain and throw a warning)
handleOpenModal={this.handleOpenModal}
/>
))}
</div>
<Modal {...this.state} onCloseModal={this.handleCloseModal} />
</section>
);
}
export default Scenarios;

Resources