setState is not changing value of boolean parameter - reactjs

I'm working on a modal in React and have it working successfully elsewhere in my application. However, in the following code snippet, this.state.display is never being set to false. I can console log around it, see that the function is firing, but this.state.display is set to true after initialization throughout the entire lifecycle.
class AdvancedToDoModal extends Component {
constructor(props) {
super();
this.state = {
display: false,
modalContent: this.fetchModalContent(props)
}
this.fetchModalContent = this.fetchModalContent.bind(this);
this.numberInput = this.numberInput.bind(this);
this.dateInput = this.dateInput.bind(this);
this.showModal = this.showModal.bind(this);
this.hideModal = this.hideModal.bind(this);
}
numberInput() {
return (
<div>Number Input</div>
)
}
dateInput() {
return (
<div>Date Input</div>
)
}
showModal() {
this.setState({ display: true })
}
hideModal() {
console.log('hide')
this.setState({ display: false }, () => {
console.log('display is always true: ', this.state)
});
}
fetchModalContent(props) {
var modalContent;
if (props.inputType === 'number') {
modalContent = this.numberInput();
} else if (props.inputType === 'date') {
modalContent = this.dateInput();
} else {
modalContent = null;
console.log('Unknown inputType');
}
return modalContent;
}
render() {
return (
<div onClick={this.showModal} className={this.state.display} style={{height: '100%', width: '100%'}}>
<Modal display={this.state.display} title={this.props.title} onClose={this.hideModal} >
{this.state.modalContent}
</Modal>
</div>
)
}
}
Any pointers would be appreciated!

The problem with your code is that you have one onClick handler on a parent element which sets the state when you click close button. Look at your render function
render() {
return (
<div onClick={this.showModal} className={this.state.display} style={{height: '100%', width: '100%'}}>
<Modal display={this.state.display} title={this.props.title} onClose={this.hideModal} >
{this.state.modalContent}
</Modal>
</div>
)
}
Here at the root div you have onClick handler which calls showModal which sets the display state to true. Now when in modal you click on any close button that calls your hideModal, but after that, your parent div's onClick is also called which sets you state again to true. Therefore your display state always remain true. Remove this onClick handler and it will be fixed.

Related

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>
);
}
}

ReactJS Call a function in render after an action

I'm using ReactJS, and shopify's polaris in order to create a website. I'm very new to react so this might be a newbie question but I looked over the internet and couldn't manage to put the pieces together.
I have a dropdown list and basically whenever the user clicks on an item from a list I want to add a button next to the dropdown. Here is my code:
import React from "react";
import { ActionList, Button, List, Popover } from "#shopify/polaris";
export default class ActionListExample extends React.Component {
constructor(props) {
super(props);
this.state = {
active: false,
title: "Set Period",
};
}
renderButton() {
console.log("Button clicked")
return (
<div>
<Button fullWidth={true}>Add product</Button>;
</div>
);
}
togglePopover = () => {
this.setState(({ active }) => {
return { active: !active };
});
};
render() {
const activator = (
<Button onClick={this.togglePopover}>{this.state.title}</Button>
);
return (
<div style={{ height: "250px" }}>
<Popover
active={this.state.active}
activator={activator}
onClose={this.togglePopover}
>
<ActionList
items={[
{
content: "One",
onAction: () => {
this.setState({ title: "One" }, function() {
this.togglePopover();
this.renderButton() //THIS IS WHERE I CALL THE METHOD
});
}
}
]}
/>
</Popover>
</div>
);
}
}
I've placed a comment in the code to show where I call the renderButton() method. Whenever I click the "One" element in the dropdown, it prints out "Button clicked" but nothing gets rendered to the screen. Any help is greatly appreciated. Thanks in advance!
You need to add another variable to check if an item is clicked, and as #azium commented, you need to add the output to your JSX, not inside the onAction function.
As of right now, you close the Popper when an item is clicked, setting this.state.active to false, so you can't rely on that to render your button. You need to add something like this.state.isButton or something and in onAction include:
onAction: () => {
this.setState({ title: "One", isButton: true }, () => {
this.togglePopover();
});
}
and then in your JSX:
{this.state.isButton && this.renderButton()}
This is a perfect use case for conditional rendering.
You basically want to render a component based on a condition (Boolean from your state in this case).
Conditional rendering can be written is several ways as you can see in the docs.
In your case i would go for something like this:
return (
<div style={{ height: "250px" }}>
<Popover
active={this.state.active}
activator={activator}
onClose={this.togglePopover}
>
<ActionList
items={[
{
content: "One",
onAction: () => {
this.setState({ title: "One" }, function() {
this.togglePopover();
});
}
}
]}
/>
{this.state.active && this.renderButton()}
</Popover>
</div>
);
}
}
Note that i just placed it at a random place, feel free to move it wherever you need it in the markup.
Thanks to everyone's help I finally was able to do this. I placed an extra attribute in the state called isButton and I initially set it equal to false. Here is my render function:
render() {
const activator = (
<Button onClick={this.togglePopover}>{this.state.title}</Button>
);
return (
<div style={{ height: "250px" }}>
<Popover
active={this.state.active}
activator={activator}
onClose={this.togglePopover}
>
<ActionList
items={[
{
content: "One",
onAction: () => {
this.setState({ title: "One", isButton: true }, function() { //Set isButton to true
this.togglePopover();
});
}
}
]}
/>
{this.state.isButton && this.renderButton()} //ADDED HERE
</Popover>
</div>
);
}
Please look at the comments to see where code was changed. Thanks!

how to setState to false after run a Modal, and go to other Modal?

I want to go to modal 2 from modal1.
I click button, with onclick={this.openprofile} and setstate of show open profile to true.
Then I want to setState of current modal (modal1) to false, but I can not?
I use from componentdidmount(),... and other lifecycle methods but can not set it to false.
export default class Register_Modal extends Component {
constructor(props) {
super(props);
this.state = {
showBaseModal: true,
codemodal: false
};
this.openCodeModal = this.openCodeModal.bind(this);
this.closeCodeModal = this.closeCodeModal.bind(this);
}
openCodeModal(e) {
e.preventDefault();
this.setState({
codemodal: true,
showBaseModal: false
});
}
closeCodeModal() {
this.setState({ codemodal: false });
}
render() {
return (
<div>
{this.state.showBaseModal && (
<Modal
isOpen={this.props.open}
onRequestClose={this.props.close}
ariaHideApp={false}
contentLabel="selected option"
isClose={this.props.close}
style={customStyles}
onClick={this.props.close}
>
<h2>hhhh</h2>
<button onClick={this.props.close} >انصراف</button>
<button onClick={this.openCodeModal} >بعدی</button>
</Modal>
)}
{this.state.codemodal && (
<div >
<RegisterCode_Modal open={() => true} close={this.closeCodeModal} />
</div>
) }
</div>
);
}
}
in these code, when I click button to setState codemodal, I go to other modal, but I can not change current state of codemodal and showBase modal to initial value.

Callback function, responsible for updating state, passed as props to child component not triggering a state update

The callback function (lies in Images component) is responsible for making a state update. I'm passing that function as props to the Modal component, and within it it's being passed into the ModalPanel component.
That function is used to set the state property, display, to false which will close the modal. Currently, that function is not working as intended.
Image Component:
class Images extends Component {
state = {
display: false,
activeIndex: 0
};
handleModalDisplay = activeIndex => {
this.setState(() => {
return {
activeIndex,
display: true
};
});
};
closeModal = () => {
this.setState(() => {
return { display: false };
});
}
render() {
const { imageData, width } = this.props;
return (
<div>
{imageData.resources.map((image, index) => (
<a
key={index}
onClick={() => this.handleModalDisplay(index)}
>
<Modal
closeModal={this.closeModal}
display={this.state.display}
activeIndex={this.state.activeIndex}
selectedIndex={index}
>
<Image
cloudName={CLOUDINARY.CLOUDNAME}
publicId={image.public_id}
width={width}
crop={CLOUDINARY.CROP_TYPE}
/>
</Modal>
</a>
))}
</div>
);
}
}
export default Images;
Modal Component:
const overlayStyle = {
position: 'fixed',
zIndex: '1',
paddingTop: '100px',
left: '0',
top: '0',
width: '100%',
height: '100%',
overflow: 'auto',
backgroundColor: 'rgba(0,0,0,0.9)'
};
const button = {
borderRadius: '5px',
backgroundColor: '#FFF',
zIndex: '10'
};
class ModalPanel extends Component {
render() {
const { display } = this.props;
console.log(display)
const overlay = (
<div style={overlayStyle}>
<button style={button} onClick={this.props.closeModal}>
X
</button>
</div>
);
return <div>{display ? overlay : null}</div>;
}
}
class Modal extends Component {
render() {
const {
activeIndex,
children,
selectedIndex,
display,
closeModal
} = this.props;
let modalPanel = null;
if (activeIndex === selectedIndex) {
modalPanel = (
<ModalPanel display={this.props.display} closeModal={this.props.closeModal} />
);
}
return (
<div>
{modalPanel}
{children}
</div>
);
}
}
export default Modal;
links to code
https://github.com/philmein23/chez_portfolio/blob/chez_portfolio/components/Images.js
https://github.com/philmein23/chez_portfolio/blob/chez_portfolio/components/Modal.js
You're dealing with this modal through a very non-react and hacky way.
Essentially, in your approach, all the modals are always there, and when you click on image, ALL modals display state becomes true, and you match the index number to decide which content to show.
I suspect it's not working due to the multiple children of same key in Modal or Modal Panel.
I strongly suggest you to ditch current approach. Here's my suggestions:
Only a single <Modal/> in Images component.
Add selectedImage state to your Images component. Every time you click on an image, you set selectedImage to that clicked image object.
Pass selectedImage down to Modal to display the content you want.
This way, there is only ONE modal rendered at all time. The content changes dynamically depending on what image you click.
This is the working code I tweaked from your repo:
(I'm not sure what to display as Modal content so I display public_id of image)
Images Component
class Images extends Component {
state = {
display: false,
selectedImage: null
};
handleModalDisplay = selectedImage => {
this.setState({
selectedImage,
display: true
})
};
closeModal = () => {
//shorter way of writing setState
this.setState({display: false})
}
render() {
const { imageData, width } = this.props;
return (
<div>
<Modal
closeModal={this.closeModal}
display={this.state.display}
selectedImage={this.state.selectedImage}
/>
{imageData.resources.map((image, index) => (
<a
//Only use index as key as last resort
key={ image.public_id }
onClick={() => this.handleModalDisplay(image)}
>
<Image
cloudName={CLOUDINARY.CLOUDNAME}
publicId={image.public_id}
width={width}
crop={CLOUDINARY.CROP_TYPE}
/>
</a>
))}
</div>
);
}
}
Modal Component
class Modal extends Component {
render() {
const { display, closeModal, selectedImage } = this.props;
const overlayContent = () => {
if (!selectedImage) return null; //for when no image is selected
return (
//Here you dynamically display the content of modal using selectedImage
<h1 style={{color: 'white'}}>{selectedImage.public_id}</h1>
)
}
const overlay = (
<div style={overlayStyle}>
<button style={button} onClick={this.props.closeModal}>
X
</button>
{
//Show Modal Content
overlayContent()
}
</div>
);
return <div>{display ? overlay : null}</div>;
}
}

opening a modal with the click of a button

The next code uses a Modal react component:
export class AddWorkLogEditor extends React.Component {
constructor(props) {
super(props);
this.addWorkLog = this.addWorkLog.bind(this);
this.onOpenModal = this.onOpenModal.bind(this);
this.onCloseModal = this.onCloseModal.bind(this);
this.state = {
open:true
};
}
onOpenModal() {
this.setState({open: this.props.openModal});
}
onCloseModal() {
this.setState({open:false});
}
addWorkLog() {
}
render() {
const bstyle = {
backgroundColor: 'green',
textAlign:"left",
paddingLeft: '0px',
color: 'white'
};
const {open} = this.state;
return (
<div>
<Modal open={open} onClose={this.onCloseModal} little>
<h3>hi gi</h3>
<Button bsStyle="success" bsSize="small" onClick ={(ev) => {console.log(ev)} }> Save </Button>
</Modal>
</div>
);
}
}
I am trying to call it using:
addWorkLog()
{
return <AddWorkLogEditor/>;
}
and
createAddWorkLogButton () {
return (
<button style={ { color: '#007a86'} } onClick={this.addWorkLog} >Add Work Log</button>
);
}
I mean, after I click at this button nothing shows up. Is there another way to call that modal? I am importing the modal from:
import Modal from 'react-responsive-modal'
You are trying to render the modal only once the button is clicked, while that's quite natural for non-react environments, in react it works in a different way. In the simplest solution the Modal should be always rendered, and when a user clicks the button you change the modal open property to true.
{ /* all the markup of your page */ }
<button onClick={() => this.setState({showModal: true})}>Add Work Log</button>
{ /* anything else */ }
{ /* modal is here but it is hidden */ }
<Modal open={this.state.showModal}>...</Modal>
Alternatively, you can just skip the modal rendering at all until the showModal becomes true.
this.state.showModal && <Modal open>...</Modal>

Resources