Reputation: 3820
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?
Upvotes: 1
Views: 8670
Reputation: 19772
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 div
s 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;
Upvotes: 1