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

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

Related

react-bootstrap modal not reacting to property change

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.

React Send child input data to update parent state

Setup: I have set up a two react components in a parent child relationship. The parent has a state that can be changed by press of a button on parent itself.
Expected behaviour: In the child, I have an input field and I want the state to change to the value I send in the input field on the press of the submit button. I have set up the parent and the child as follows:
What I have tried: I going through this answer and this youtube video but I guess I am not smart enough to make sense of it.
This is what my code looks like
Parent:
class App extends Component {
state = {
value:"someValue"
};
changeValue = (value) => {
this.setState({
value
})
}
render() {
return (
<div>
<p>this is the value from state: {this.state.value}</p>
<button onClick={()=>this.changeValue("valueFromParentComponent")}>Press to change value from parent component</button>
<br/><br/>
<Child getChildInputOnSubmit={()=>this.changeValue()} />
</div>
);
}
}
And this is what the child looks like
Child:
class Child extends Component {
state = {
}
sendChildData = (childInputValue) => {
console.group("This is the data from the input of the child component")
console.log("==============")
console.log(childInputValue)
console.log("==============")
}
render() {
return (
<div>
This is the child component
<br /><br />
<form>
<input type="text" placeholder="Some placeholder"></input>
<button onSubmit={this.sendChildData()} type="submit">Send child's input to parent</button>
</form>
</div>);
}
}
The React behaviour encourages to implement an inverse data flow inside a component hierarchy. Meaning that the child components can receive parent methods through props, this methods will work as callbacks, allowing to receive data, trigger behaviours, update his state and more.
I attach a StackBlitz example, showing how this concept would work in your setup https://stackblitz.com/edit/react-jsv5jo
Edit: Here a few extra tips applied on the example:
To work with inputs on React, a common setup consists on listen the onChange event to receive new data and update the component state. Then, this state is used in the value attribute to update the input content on DOM.
Listen the onSubmit event on the form tag instead on submit button, and remember to add some logic to avoid reloading.
Another good practice on React components is initialize your state object inside the Constructor (In case to be working with a Class Component) and write methods to avoid bloat the render one (Be sure to bind the extra methods on your constructor to avoid invocation problems)
Callbacks are used to pass data from Child component to Parent component in React.
We wright function in Parent component that will receive value and pass this function to child component through Props.
class Parent extends Component {
state = {
value: 'someValue'
};
changeValue = value => {
this.setState({
value
});
};
render() {
return (
<div>
<p>this is the value from state: {this.state.value}</p>
<button onClick={() => this.changeValue('valueFromParentComponent')}>
Press to change value from parent component
</button>
<br></br>
<Child getChildInputOnSubmit={this.changeValue} />
</div>
);
}
}
Now in Child component we call Parents function that we passed in props and send value.
class Child extends Component {
constructor(props) {
super(props);
this.state = {
Childvalue: ''
};
}
handleChange = event => {
event.preventDefault();
this.setState({ Childvalue: event.target.value });
};
sendToParent = () => {
//here calling Parents changeValue
this.props.getChildInputOnSubmit(this.state.Childvalue);
};
render() {
return (
<div>
This is the child Component
<br></br>
<form action='#' onSubmit={this.sendToParent}>
<input
type='text'
placeholder='Some placeholder'
value={this.state.Childvalue}
onChange={this.handleChange}
></input>
<button type='submit'>Send child's input to parent</button>
</form>
</div>
);
}
}

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

In React, Is it good practice to search for certain element in DOM?

Is it good to just specify className for element so i could find it later in the DOM through getElementsByClassName for manipulations?
Adding a class to find the DOM element? Sure you can do that, but refs are probably the better solution.
Manipulating the DOM element? That's an absolute no-go. The part of the DOM that is managed by React should not be manipulated my anything else but React itself.
If you come from jQuery background, or something similar, you will have the tendency to manipulate element directly as such:
<div class="notification">You have an error</div>
.notification {
display: none;
color: red;
}
.show {
display: block;
}
handleButtonClick(e) {
$('.notification').addClass('show');
}
In React, you achieve this by declaring what your elements (components) should do in different states of the app.
const Notification = ({ error }) => {
return error
? <div className="notification">You have an error</div>
: null;
}
class Parent extends React.Component {
state = { error: false };
render() {
return (
<div>
<Notification error={this.state.error} />
<button onClick={() => this.setState({ error: true })}>
Click Me
</button>
}
}
The code above isn't tested, but should give you the general idea.
By default, the state of error in Parent is false. In that state, Notification will not render anything. If the button is clicked, error will be true. In that state, Notification will render the div.
Try to think declaratively instead of imperatively.
Hope that helps.
When using React, you should think about how you can use state to control how components render. this.setState performs a rerender, which means you can control how elements are rendered by changing this.state. Here's a small example. I use this.state.show as a boolean to change the opacity of the HTML element.
constructor(props) {
super(props)
this.state = {
show: true
}
}
handleClick() {
this.setState({show: false})
}
render() {
const visibility = this.state.show ? 1 : 0
return (
<button style={{opacity: visibility} onClick={() => this.handleClick()}>
Click to make this button invisible
</button>
)
}

i am not able retrieve array elements one at a time if i call them in my component all the elements are retrieved at a time

I want to load my array element when an event is occurred by referencing the key i tried different variables for the key but it would not accept all the elements of the array are being displayed if i give index as the key.
I am new to Reactjs and not very familiar with all the syntax and concept can somebody help me with the logic to solve this.
The event I am triggering is onClick or onChange.
`var Qstn =['A','B','C','D','E','F','G','H','I','J'];
<div>
{Qstn.map(function(Q,index){
return <span className="col-md-4 txt" key={index}>{Q}</span>
})}
</div>`
Ok I made a codepen with an example
http://codepen.io/lucaskatayama/pen/QGGwKR
It's using ES6 classes components, but it's easy to translate.
You need to set initial state to an empty array like [].
On click button, it call onClick() method which uses this.setState({}) to change component state.
When React notice state changes, it re-render the component.
class Hello extends React.Component {
constructor(){
super();
//Initial State
this.state = {
Qstn : []
}
}
onClick(){
//Called on click button
// Set state to whatever you want
this.setState({Qstn : ['A','B','C','D','E','F','G','H','I','J']})
}
render(){
let Qstn = this.state.Qstn; // load state and render
return (
<div>
<button onClick={() => this.onClick()}>Click</button>
<div>
{Qstn.map(function(Q,index){
return <span className="col-md-4 txt" key={index}>{Q}</span>
})}
</div>
</div>
)
}
}
ReactDOM.render(<Hello />, document.getElementById('container'))

Resources