react-bootstrap modal not reacting to property change - reactjs

I am using React v18.1, react-bootstrap v2.4. I have a Modal component I am trying to get to display upon a button press. The modal component is quite simple:
class AdjustmentModal extends React.Component {
constructor(props) {
super(props);
this.state = {
'show': this.props.show
};
this.handleClose = this.handleClose.bind(this);
}
handleClose() {
this.setState({ show: false })
}
render() {
return (
<Modal show={this.state.show} onHide={this.handleClose}>
[ ... Modal Content Here ... ]
</Modal>
);
}
}
export default AdjustmentModal;
As you can see, I bind the modal's show property to the value of show in state.
Then, in the component in which I want to display my modal, I have the following:
// Within render() ...
<AdjustmentModal
show={this.state.showAdjustment}
partNo={this.state.partNo}
onHandQty={this.state.onHandQty}
/>
// Futher on in the code, display the modal on click:
<Button className="icon" onClick={this.handleDisplayAdjustment}>
<i className="bi bi-pencil-square"></i>
</Button>
handleDisplayAdjustment :
handleDisplayAdjustment(event) {
event.preventDefault();
this.setState({
showAdjustment : true
});
}
Now, despite the value showAdjustment in the parent component changing to true, the modal doesn't display.
I could set the <Modal show={this.props.show} .../> instead, but props are read-only, so there is no way to close the modal again if reading from props rather than state.

You can use props, which is a better way to handle this if you want to close it then pass a method from the parent which when called update the state in the parent to false and due state update the parent component will re render and though the child component that is the modal component and the Modal will get the updated value which will be false. below is the code on how you can achieve that.
closeModal() {
this.setState({
showAdjustment: false
})
}
// Within render() ...
<AdjustmentModal
show={this.state.showAdjustment}
partNo={this.state.partNo}
onHandQty={this.state.onHandQty}
onClose={this.closeModal.bind(this)}
/>
// Futher on in the code, display the modal on click:
<Button className="icon" onClick={this.handleDisplayAdjustment}>
<i className="bi bi-pencil-square"></i>
</Button>
For the child component
class AdjustmentModal extends React.Component {
handleClose() {
this.props.onClose()
}
render() {
return (
<Modal show={this.props.show} onHide={this.handleClose}>
[ ... Modal Content Here ... ]
</Modal>
);
}
}
export default AdjustmentModal;
EDIT: Explaining the approach
This will make your Modal component a Controlled component that is controlled by Parent, also updating props as a state inside the child component is not the right way, which may create potential bugs.

Related

Clicking a button to open dialog in ReactJS

I'm working with React MDL library, and I used pre-defined components like FABButton
<FABButton>
<Icon name="add"/>
</FABButton>
And it shows the button as in the image bellow:
Now, what I want is showing a dialog with the + icon... not as what happens here:
This happened after this code:
<FABButton>
<AddingProject />
<Icon name="add" />
</FABButton>
The class of dialog is as follows:
class AddingProject extends Component {
constructor(props) {
super(props);
this.state = {};
this.handleOpenDialog = this.handleOpenDialog.bind(this);
this.handleCloseDialog = this.handleCloseDialog.bind(this);
}
handleOpenDialog() {
this.setState({
openDialog: true
});
}
handleCloseDialog() {
this.setState({
openDialog: false
});
}
render() {
return (
<div>
<Button colored onClick={this.handleOpenDialog} raised ripple>
Show Dialog
</Button>
<Dialog open={this.state.openDialog} onCancel={this.handleCloseDialog}>
<DialogTitle>Allow data collection?</DialogTitle>
<DialogContent>
<p>
Allowing us to collect data will let us get you the information
you want faster.
</p>
</DialogContent>
<DialogActions>
<Button type="button">Agree</Button>
<Button type="button" onClick={this.handleCloseDialog}>
Disagree
</Button>
</DialogActions>
</Dialog>
</div>
);
}
}
export default AddingProject;
The above code is with the required import statements
This works with me....
First step: I added the component of the modal as follows:
<FABButton>
<Icon name="add" />
</FABButton>
<ProjectModal>
Second step: I added this prop: visible for the component as here:
<ProjectModal visible={this.state.showDialog} />
And here you need to add showDialog to the states in your class with false.
state = {
showDialog: false
};
Now, to step 3.
Third step: Add this part to your code, to be called when you click.
openModal = () => {
this.setState({ showDialog: true });
};
On the other side, you need to implement onClick in the button as follows:
<FABButton onClick={this.openModal.bind(this)}>
<Icon name="add" />
</FABButton>
Fourth step: In the modal/dialog class, you need to store the visible in a new state variable, which is here showDialogModal
constructor(props, context) {
super(props, context);
this.state = {
showDialogModal: this.props.visible
};
}
Now, you need to pass the changed state from the first class to the modal/dialog class, there are more than one option that React gives you, I used this one in fifth step. Fifth step: use this React event componentWillReceiveProps as below.
componentWillReceiveProps(nextProps) {
if (this.props.showDialogModal != nextProps.visible) {
this.setState({
showDialogModal: nextProps.visible
});
}
}
This will reflect any change in visible property from the first class to our new one here which is showDialogModal
Now in the render part, you need to check the docs of your components, here I started with React-Bootstrap.
Sixth step: use the show property in your component.
<Modal show={this.state.showDialogModal} onHide={this.closeModal}>
onHide is for closing the dialog, which makes you need to implement this too.
closeModal = () => {
this.setState({ showDialogModal: false });
};
Finally, in the closing button, add this:
<Button onClick={this.closeModal.bind(this)}>Close</Button>
Good luck.

Is it okay to call setState on a child component in React?

I have some text. When you click on that element a modal pops up that lets you edit that text. The easiest way to make this work is to call setState on the child to initialise the text.
The other way, although more awkward, is to create an initial text property and make the child set it's text based on this.
Is there anything wrong with directly calling setState on the child or should I use the second method?
Although it is recommended to keep the data of your react application "up" in the react dom (see more here https://reactjs.org/docs/lifting-state-up.html), I don't see anything wrong with the first aproach you mentioned.
If you have to store data that is very specific of a child I don't see anything wrong in keep that information in the child's state.
It seems that your modal doesn't need to have its own state, in which case you should use a stateless React component.
This is one way of passing the data around your app in the React way.
class ParentComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
initialText: "hello",
}
this.saveChildState = this.saveChildState.bind(this);
}
saveChildState(input) {
console.log(input);
// handle the input returned from child
}
render() {
return (
<div>
<ChildComponent
initialText={this.state.initialText}
save={this.saveChildState}
/>
</div>
);
}
}
function ChildComponent(props) {
return (
<div>
<input id="textInput" type="text" defaultValue={props.initialText}>
</input>
<button onClick={() => props.save(document.getElementById('textInput').value)}>
Save
</button>
</div>
)
}
Maybe I am misinterpreting your question, but I think it would make the most sense to keep the modal text always ready in your state. When you decide to show your modal, the text can just be passed into the modal.
class Test extends Component {
constructor() {
this.state = {
modalText: 'default text',
showModal: false
}
}
//Include some method to change the modal text
showModal() {
this.setState({showModal: true})
}
render(
return (
<div>
<button onClick={() => this.showModal()}>
Show Modal
</button>
{ this.state.showModal ? <Modal text={this.state.modalText}/> : null }
</div>
)
)
}

Onclick an element that changes the state of another element

I'm trying to click on a sort icon that will trigger to change the order of a list.
To make it more simpler, let's say you have a button and another button and they are on separate divs from each other.
<div>
//Button 1
<button onclick={"some_click_handler"}>
</div>
<div>
//Button 2
<button>
{this.state.someToggle ? true : false}
</button>
</div>
Create a component which passes a callback to the button, this callback will update the state of the container which will in turn set the props of the list. This is very common in React and is the basis of how the compositional pattern works. If you need to share data between two components just put them in a container and lift the state to the parent component. These components are usually called containers and there is a bunch of documentation on it.
This is a good starting point: https://reactjs.org/docs/lifting-state-up.html
Something like this...
class Container extends React.Component {
constructor(props) {
super(props);
// Don't forget to bind the handler to the correct context
this.handleClick = this.handleClick.bind(this);
}
handleClick(sort) {
this.setState({sort: sort});
}
render() {
return (
<Button handleClick={this.handleClick} />
<List sort={this.state.sort} />
)
}
}

Can't get button component value onClick

I'm sure this is something trivial but I can't seem to figure out how to access the value of my button when the user clicks the button. When the page loads my list of buttons renders correctly with the unique values. When I click one of the buttons the function fires, however, the value returns undefined. Can someone show me what I'm doing wrong here?
Path: TestPage.jsx
import MyList from '../../components/MyList';
export default class TestPage extends React.Component {
constructor(props) {
super(props);
this.state = {};
this.handleButtonClick = this.handleButtonClick.bind(this);
}
handleButtonClick(event) {
event.preventDefault();
console.log("button click", event.target.value);
}
render() {
return (
<div>
{this.props.lists.map((list) => (
<div key={list._id}>
<MyList
listCollection={list}
handleButtonClick={this.handleButtonClick}
/>
</div>
))}
</div>
);
}
}
Path: MyListComponent
const MyList = (props) => (
<div>
<Button onClick={props.handleButtonClick} value={props.listCollection._id}>{props.listCollection.title}</Button>
</div>
);
event.target.value is for getting values of HTML elements (like the content of an input box), not getting a React component's props. If would be easier if you just passed that value straight in:
handleButtonClick(value) {
console.log(value);
}
<Button onClick={() => props.handleButtonClick(props.listCollection._id)}>
{props.listCollection.title}
</Button>
It seems that you are not using the default button but instead some sort of customized component from another libray named Button.. if its a customezied component it wont work the same as the internatls might contain a button to render but when you are referencing the event you are doing it throug the Button component

How to access ref of an element within custom react component

I'm building a Modal component. This component takes modal content as children and the button to trigger the modal as a button prop.
This Modal component should render the button. When clicked it has to position a fixed element exactly on top of that button that then animates to a modal dialog. For this the Modal component needs a ref to the button DOM element to measure it's size and position with getBoundingClientRect.
I want the Modal component to be able to receive through button prop both
button DOM element or a custom React element that renders button.
The api of the component looks like this then
const ModalUser = () => (
<div>
<Modal button={<button>Button</button>}/>
<Modal button={<CustomButton>Button</CustomButton>}/>
</div>
)
The render method of Modal looks like this then
class Modal extends React.PureComponent {
render() {
return (
<div>
{React.cloneElement(this.props.button, {
ref: (el) => { this.button = el && el.button ? el.button : el },
onClick: this.onClick,
})}
<span>top: {this.state.top}</span>
<span>left: {this.state.left}</span>
</div>
);
}
}
And thus requires any CustomElement button to expose this.button as a ref to it's containing button.
class CustomButton extends React.PureComponent {
render() {
return (
<button
ref={(el) => { this.button = el }}
onClick={this.props.onClick}
>
<span>Custom</span>
<span>{this.props.children}</span>
</button>
)
}
}
For me this feels not optimal, but it works. I feel like there should be a more elegant solution to this. Does anyone have a suggestion how to do this better.
Here is a working demo
Codepen

Resources