How to make a React component ‘communicate’ with another React component? - reactjs

I have a parent jsx component that has 2 different jsx components within it.
The one component is a button and the other is a div that opens and closes itself when you click on it (it has a click handler and a state of open or closed).
I now want to add the ability for the button to open and close the div as well.
Is the only way to accomplish this is to pass a handler function down to the button from the parent, moving the div’s open and closed state to the parent component, and pass the state down to the div as props? The reason I ask is that this particular div component is used in a number of different components and removing the open and closed state would affect a lot of different parent components.

Here's a code example of allowing external state manipulation where you can mix the usage of the button or the div to toggle the state. You extend your Collapsible component to use passed props to update the state.
class Collapsible extends React.Component {
constructor(props){
super(props);
this.state = { isOpen: this.props.isOpen !== false };
this.toggleOpen = this.toggleOpen.bind(this);
}
componentWillReceiveProps({ isOpen }) {
this.setState({ isOpen });
}
toggleOpen(){
this.setState((prevState) => ({ isOpen: !prevState.isOpen }))
}
render() {
let display = this.state.isOpen ? null : "none";
return (
<div
className="collapsible"
onClick={this.toggleOpen}
>
<header> header </header>
<div style={{ display }}>{this.props.children}</div>
</div>
);
}
}
class Parent extends React.Component {
constructor(props){
super(props);
this.state = { isOpen: true };
this.toggleOpen = this.toggleOpen.bind(this);
}
toggleOpen(){
this.setState((prevState) => ({ isOpen: !prevState.isOpen }))
}
render() {
return (
<div className="parent">
<Collapsible isOpen={this.state.isOpen}>content</Collapsible>
<button onClick={this.toggleOpen}>
toggle
</button>
</div>
);
}
}

Here is another code example, hope it helps:
I use the local state of the Container and pass this down to the child components. In a bigger app I'd advice you to use something like Redux to manage your state.
The central idea is that the parent component passes a function which can "change it's state" to the button child. It also passed the current isOpen state to the panel. Clicking the button will change the state of the parent, thus triggering a reflow, thus updating the collapsable.
For future reference:
import React from "react";
import { render } from "react-dom";
const Collapsable = ({ isOpen }) =>
isOpen ? (
<div
style={{
border: "1px solid orange",
padding: "1rem",
background: "maroon",
color: "white"
}}
>
{" "}
Hey, I'm open{" "}
</div>
) : (
<div>Oh no...closed :(</div>
);
const Button = ({ openPanel }) => (
<button onClick={() => openPanel()}>Open Panel</button>
);
class Container extends React.PureComponent {
state = { open: false };
openPanel = () => {
this.setState({
...this.state,
open: this.state.open ? false : true
});
};
render() {
return (
<div>
<Button openPanel={this.openPanel} />
<Collapsable isOpen={this.state.open} />
</div>
);
}
}
const App = () => (
<div>
<Container />
</div>
);
render(<App />, document.getElementById("root"));

Related

How to update back prop to child componet using react hook

I have a parent componet like this, just to show the dialog
The Child Component ( Main to show dialog)
export const MedicalRecord = memo(function MedicalRecord() {
// const onPressViewAll = useCallback(() => {}, [])
const [show, setShow] = useState(false) ///to show dialog
function hanndleDialog() {
setShow(!show) set to show dialog
}
// useEffect(() => {
// if (show == true) {
// setShow(!show)
// }
// },[show])
return (
<SummaryViewContainer
count={5}
title={"dashboardScreen.medicalRecords.title"}
onPress={() => {
hanndleDialog()
}}
>
<View>
{show && (
<ProgressDialog
show={show} //pass t
callback={() => {
hanndleDialog()
}}
/>
)}
<RecordItem />
<RecordItem />
<RecordItem />
</View>
</SummaryViewContainer>
)
})
And parent componet to show this dialog
export default function DialogTesting(show: boolean, { callback }) {
const [showDialog, doShow] = useState(show) //show to present show in child
return (
<View>
{/* <Button
title="click"
onPress={() => {
setShow(true)
}}
>
<Text>Show dialog</Text>
</Button> */}
<Dialog
visible={showDialog}
title="Record New Progress"
style={DIALOG}
onClose={() => {
doShow(false)
callback()
}}
>
But i cant figure out how to open dialog again when close the dialog, it only open for once, i try React Hook : Send data from child to parent component but not work !
How can i show dialog and when i click close button, the children return orignal state so i can click it again, thank you guy so much
Here is a short video of this problem
https://recordit.co/0yOaiwCJvL
I am assuming that you want to find a way to show hide a component based on click. So this is the sandbox for the same.
In this solution, instead of using a derived state, the state is held in the parent's state and the child is mounted/unmounted based on that state.
The state can be updated by a method present in the parent and this method is passed to the child to be triggered on the "hide child" button. The same method is used to show the child component as well.
Below is the core code for the same,
import React from "react";
const Dialog = ({ hideMe }) => {
return (
<div>
<div>I am dialog</div>
<button onClick={hideMe}>Hide me</button>
</div>
);
};
class App extends React.Component {
constructor(props) {
super(props);
this.state = { showDialog: false };
}
toggleDialog = () => {
this.setState((prevState) => {
return { showDialog: !prevState.showDialog };
});
};
render() {
return (
<div>
<div>I am parent.</div>
<button onClick={this.toggleDialog}>Toggle Dialog</button>
{this.state.showDialog ? <Dialog hideMe={this.toggleDialog} /> : null}
</div>
);
}
}
export default App;

Functional Component unable to render return value based on props values

Goal: To implement a Toast Message modal (using Functional Component) which will show or hide based on the props value (props.showToastModal) within the return of ToastModal component
Expected: Using props.showToastModal directly would determine if Toast appears
Actual: Modal does not appear based on props.showToastModal
Here's the code:
Parent Component
class Datasets extends Component {
constructor(props) {
super(props)
this.state = {
showToastModal: false,
toastModalText: ''
}
}
toggleOff = () => {
this.setState({ showToastModal: false, toastModalText: '' })
}
render() {
{this.state.showToastModal && (
<ToastModal
showToastModal={this.state.showToastModal}
toastModalText={this.state.toastModalText}
toggleOff={this.toggleOff}
/>
)}
}
}
Child Component
This works:
const ToastModal = (props) => {
const isOpen = props.showToastModal
return (
<div className={`${css.feedbackModal} ${isOpen ? css.show : css.hide}`}>
{props.toastModalText}
<i
className={`bx bx-x`}
onClick={() => props.toggleOff()}
/>
</div>
)
}
export default ToastModal
But this doesn't (using the props value directly):
const ToastModal = (props) => {
return (
<div className={`${css.feedbackModal} ${props.showToastModal ? css.show : css.hide}`}>
{props.toastModalText}
<i
className={`bx bx-x`}
onClick={() => props.toggleOff()}
/>
</div>
)
}
export default ToastModal
Using a const isOpen = props.showToastModal works as expected instead. I am confused why this happens. Is this is a React Lifecycle issue, or a case where it is bad practice to use props values which may be updated during the render?
Please try destructuring objects
const ToastModal = ({ showToastModal, toggleOff }) => {
return (
<div className={`${css.feedbackModal} ${showToastModal ? css.show : css.hide}`}>
{props.toastModalText}
<i
className={`bx bx-x`}
onClick={toggleOff}
/>
</div>
)
}
export default ToastModal

ref.current is null in react

I am trying to use react reference to render data into iframe but I am getting
reference.current value null.
I also check the value in componentDidMount() still it give me null value. As I set refrence value in constructor. Reference is set to "PreviewEmailModal" component insted of "iframe" component.
Thats why I am getting current value as null. So some can please help me to set refrence to iframe component.
React version- 16.4.2
React DOM version - 16.4.2
import React from "react";
import PropTypes from 'prop-types'
import { Button, Modal } from "react-bootstrap";
class PreviewEmailModal extends React.Component {
constructor() {
super();
this.textInput = React.createRef();
this.state = {
desktopPreview: true
};
}
render() {
this.props;
debugger
return (
<Modal
show={true} onHide={() => {
this.props.closeModal();
}}
className="preview-email-modal"
bsSize="lg">
<Modal.Header closeButton>
<Modal.Title>Preview</Modal.Title>
</Modal.Header>
<Modal.Body >
<div className="preview-icons">
<span className={this.state.desktopPreview ? "active-preview-icon margin-5" : ""}>
<i className="glyphicon glyphicon-blackboard" onClick={() => {
this.togglePreview();
}} />
</span>
<span className={this.state.desktopPreview ? "" : "active-preview-icon margin-5"}>
<i className="glyphicon glyphicon-phone" onClick={() => {
this.togglePreview();
}} />
</span>
</div>
<div>
<div className="text-center">
<iframe
title={"previewFrame"}
style={this.state.desktopPreview ?
{ width: "600px", height: "450px" } :
{ width: "320px", height: "450px" }}
id="previewIframe"
ref={(input) => {
this.textInput = input;
}}
/>
</div>
</div>
</Modal.Body>
</Modal>
);
}
componentDidUpdate() {
debugger
}
componentDidMount() {
const { current } = this.textInput;
//Here I get current value as null every time
const { data } = this.props;
if (current !== null) {
const doc = current.contentDocument;
doc.open();
doc.write(data);
doc.close();
}
}
focusTextInput() {
// Explicitly focus the text input using the raw DOM API
this.textInput.focus();
}
togglePreview() {
this.setState({ desktopPreview: !this.state.desktopPreview });
}
}
PreviewEmailModal.propTypes = {
closeModal: PropTypes.func,
data: PropTypes.string
};
export default PreviewEmailModal;
You are not using Ref correctly. Instead of passing callback to ref field pass instance.
OR pass
ref={this.textInput}
instead of
ref={(input) => {this.textInput = input;}}
You're likely trying to reference this.textInput when it's not actually on the DOM.
I solved this by adding componentDidUpdate like this:
componentDidUpdate() {
const { visible, emailHtml } = this.props;
if (visible) {
// this.iframe is my iframe ref
this.iframe.contentWindow.document.write(emailHtml);
}
}
On another note I don't think you need this line for the ref to work:
this.textInput = React.createRef();
I had the exact same issue (literally my component is called PreviewEmailModal too). I even have a very similar Modal component I'm using within PreviewEmailModal as well. Small world!
As I don't know all of your code I would recommend to move your code from componentDidMount to the ref callback. Please check the answer of Dan Abramov here (componentDidMount called BEFORE ref callback)

Why does clicking expand button closes the side panel in react?

i have a side panel with items listed. when the list item content overflows expand button appears and clicking that expand btn would show the entire content of list item
For this i have created a expandable component. this will show arrow_down when list item content overflows and clicking arrow_down shows up arrow_up.
However with the below code, clicking button 1 just makes the sidpanel disappear instead of arrow_up appearing. could some one help me solve this. thanks.
export default class Expandable extends React.PureComponent{
constructor(props) {
super(props);
this.expandable_ref = React.createRef();
this.state = {
expanded: false,
overflow: false,
};
}
componentDidMount () {
if (this.expandable_ref.current.offsetHeight <
this.expandable_ref.current.scrollHeight) {
this.setState({overflow: true});
}
}
on_expand = () => {
this.setState({expanded: true});
console.log("in expnad");
};
on_collapse = () => {
this.setState({expanded: false});
};
render () {
return (
<div className={(this.state.overflow ?
this.props.container_classname : '')}>
<div className={(this.state.overflow ?
this.props.classname : '')} style={{overflow: 'hidden',
display: 'flex', height: (this.state.expanded ? null :
this.props.base_height)}}
ref={this.expandable_ref}>
{this.props.children}
</div>
{this.state.overflow && this.state.expanded &&
<div className={this.props.expand}>
<button onClick={this.on_collapse}>
{this.props.arrow_up}</button>
</div>}
{this.state.overflow && !this.state.expanded &&
<div className={this.props.expand}>
<button onClick={this.on_expand}>
{this.props.arrow_down}</button>
</div>}
</div>
);
}
}
In the above code i pass the base_height to be 42px.
Edit:
i have realised for the side panel component i add eventlistener click to close the side panel if user clicks anywhere outside sidepanel. When i remove that eventlistener it works fine....
class sidepanel extends React.PureComponent {
constructor(props) {
super(props);
this.sidepanel_ref = React.createRef();
}
handle_click = (event) => {
if (this.sidepanel_ref.current.contains(event.target)) {
return;
} else {
this.props.on_close();
}
};
componentDidMount() {
document.addEventListener('click', this.handle_click, false);
}
componentWillUnmount() {
document.removeEventListener('click', this.handle_click, false);
}
render() {
return (
<div>
<div className="sidepanel" ref=
{this.sidepanel_ref}>
{this.props.children}
</div>
</div>
);
}
}
when i log the event.target and sidepanel_ref.current i see the button element in both of them but svg seems different in both of them.
How can i fix this?
Probably it is because click events bubble up the component tree as they do in the DOM too. If you have an element with an onClick handler inside an element with another onClick handler it will trigger both. Use event.stopPropagation() in the handler of the inner element to stop the event from bubbling up:
export default class Expandable extends React.PureComponent{
constructor(props) {
super(props);
this.expandable_ref = React.createRef();
this.state = {
expanded: false,
overflow: false,
};
}
componentDidMount () {
if (this.expandable_ref.current.offsetHeight <
this.expandable_ref.current.scrollHeight) {
this.setState({overflow: true});
}
}
toggleCollapse = event => {
// use preventDefault here to stop the event from bubbling up
event.stopPropagation();
this.setState(({expanded}) => ({expanded: !expanded}));
};
render () {
const {className, container_classname, base_height, expand, arrow_up, arrow_down} = this.props;
const {overflow, expanded} = this.state;
return (
<div className={overflow ? container_classname : ''}>
<div
className={overflow ? classname : ''}
style={{
overflow: 'hidden',
display: 'flex',
height: expanded ? null : base_height
}}
ref={this.expandable_ref}
>
{this.props.children}
</div>
{overflow && (
<div className={expand}>
<button onClick={this.toggleCollapse}>
{expanded ? arrow_up : arrow_down}
</button>
</div>
)}
</div>
);
}
}

Is there a way I can change parent state from a wrapped component

I'm trying to implement a drop down wrapper. The element wrapped by the DropDownWrapper is expected to toggle the display of drop-down through onClick.
class DropdownMenuWrapper extends React.Component {
constructor(props) {
super(props);
this.state = {
active: true,
};
this.handleDropDownDisplay = this.handleDropDownDisplay.bind(this);
this.displayDropDownItems = this.displayDropDownItems.bind(this);
}
displayDropDownItems(menuItems, onSelectMenuItem, children) {
return (
menuItems.map(item => (
<a
className={cx('navItem')}
key={item.value}
onClick={() => onSelectMenuItem(children)}
>
{item.logo}
{item.display}
</a>
))
);
}
handleDropDownDisplay() {
this.setState({
active: !this.state.active,
});
}
render() {
const {
className, menuItems, onSelectMenuItem, children, label,
} = this.props;
return (
<div>
{children}
<nav className={className} aria-label={label}>
{this.state.active && this.displayDropDownItems(menuItems, onSelectMenuItem, children)}
</nav>
</div>
);
}
}
export default DropdownMenuWrapper;
Here I want to achieve dropdown toggle on the wrapped button below
<DropdownMenuWrapper
menuItems={[
{ value: 'dashboard', display: 'Dashboard' },
{ value: 'myProfile', display: 'My Profile' },
]}
>
<Button />
</DropdownMenuWrapper>
Is there a way I can use {children} onClick to change the state of the Parent component (DropdownMenuWrapper) in this case?
Thankfully this problem has been solved on various platforms, including this one. I'd like to get you started:
You can pass a function from the parent component down to the child component via its props and from there use it to alter the parent component's state.
The function, defined in the parent component, takes care of updating the parent component's state. The child component must then bind the function to an event that takes place in the child component and once the event is triggered the function takes place in the parent component and does the change it's implemented to do.
You can see a detailed code implementation in an already existing answer here: How to update parent's state in React?. Pardon me if this is not what you're looking for, in that case you should make your question more clear.
In the future you should try to search for an existing answer since there's a good chance the problem has been solved. A simple search engine search did the job for me and I like to tag Stack overflow as a part of the search query for a higher chance of a good answer.
This helped me out
reference: https://reactjs.org/docs/react-api.html#reactchildren
class DropdownMenuWrapper extends React.Component {
constructor(props) {
super(props);
this.state = {
active: false,
};
}
displayDropDownItems = (menuItems, onSelectMenuItem) => (
menuItems.map(item => (
<a
className={cx('navItem')}
key={item.value}
onClick={() => onSelectMenuItem(item.value)}
>
{ item.icon && <span className={cx('icon')}>{item.icon}</span>} {item.display}
</a>
))
);
toggleDropDown = () => {
this.setState({
active: !this.state.active,
});
};
render() {
const {
className, menuItems, onSelectMenuItem, children, label,
} = this.props;
const childrenWithProps = React.Children.map(children, child =>
React.cloneElement(child, { toggleDropDown: this.toggleDropDown }));
return (
<div>
{childrenWithProps}
<nav className={className} aria-label={label}>
{this.state.active && this.displayDropDownItems(menuItems, onSelectMenuItem, children)}
</nav>
</div>);
}
}
export default DropdownMenuWrapper;

Resources