Updating React child component after state change - reactjs

I made a text input box which, when submitted, will update the state of the messages in the parent component. The messages in the parent component are passed down to the message display. I'd like the component responsible for displaying the messages to update and display the messages after each submission, but can't figure out how to do it. I made a code sandbox here:
https://codesandbox.io/s/unruffled-pasteur-nz32o
Here's my code:
Parent component:
import React, { Component } from "react";
import Messages from "./Messages";
import Input from "./Input";
export default class Container extends Component {
constructor(props) {
super(props);
this.state = {
messages: []
};
}
updateMessage(message) {
this.state.messages.push(message);
}
render() {
return (
<div>
<Messages messages={this.state.messages} />
<Input updateMessage={message => this.updateMessage(message)} />
</div>
);
}
}
Message input component:
import React, { Component } from "react";
export default class Input extends Component {
constructor(props) {
super(props);
this.state = {
message: ""
};
}
sendMessage() {
this.props.updateMessage(this.state.message);
this.setState({ message: "" });
}
render() {
return (
<div>
<input
type="text"
value={this.state.message}
onChange={({ target }) => {
this.setState({ message: target.value });
}}
/>
<button onClick={() => this.sendMessage()}>Send</button>
</div>
);
}
}
Message display component:
import React, { Component } from "react";
export default class Messages extends Component {
render() {
return this.props.messages.map(message => {
return <div>{message}</div>;
});
}
}
Thanks!

From your code:
updateMessage(message) {
this.state.messages.push(message);
}
You're modifying the state directly and you're not supposed to do that (except for in the constructor). It won't cause a re-render in this way. Instead, clone the state, modify it, then update the state via setState. Calling setState will invoke a re-render.
updateMessage(message) {
this.setState({
messages: [...this.state.messages, message],
});
}

In your updateMessage(message) method, can you try:
updateMessage(message) {
let { messages } = this.state;
messages.push(message)
this.setState({ messages })
}

Your error is in this part
updateMessage(message) {
this.state.messages.push(message);
}
You can not change state directly. You must use setState() method to change state
updateMessage(message) {
this.setState({
messages : [...this.state.messages, message]
});
}

Related

Update state from other Component to Main Component

I created a component/file in my React-Project, so that I can better organize it.
I have exported a Login-Component <SignInSide></SignInSide> and imported it in my main file:
Main-File:
export default class Login extends Component {
constructor(props) {
super(props);
this.onChangeUsername = this.onChangeUsername.bind(this);
this.state = {
username: ""
};
}
onChangeUsername(e) {
this.setState({
username: e.target.value
});
}
render() {
return (
<SignInSide></SignInSide>
);
}
}
This is my very basic Login-Component. As I already said, I just want to call this.onChangeUsernam when I imported this as <SignInSide></SignInSide> but i dont know what I have to write inside the onClick-Argument inside of the Login-Component to get/update the state from the Main-File.
Login-Component
export default function SignInSide(props) {
return (
<form className={classes.form}>
<TextField/>
<Button>
Login
</Button>
</form>
);
}
I am very thankful for helping. Can you give me a short and easy to understand example, so that I can add this by myself to my project? I only have to understand, what i have to do.
To update the Login component's state, you need to pass props from parent component to child component (passing props from Login to SignInSide component).
Therefore, you need to pass onChangeUsername method as a prop to the <SignInSide/> component. Inside SignInSide component, you need to manage a local state to keep the text input that you entered. It only needs to be used when you're submitting the data which trigger the onClick function onChangeUsername which has been passed from parent component.
Main-File:
import { Component } from "react";
import SignInSide from "./SignInSide";
export default class Login extends Component {
constructor(props) {
super(props);
this.onChangeUsername = this.onChangeUsername.bind(this);
this.state = {
username: "",
};
}
onChangeUsername(data) {
this.setState({
username: data,
});
}
render() {
console.log("username updated: ", this.state.username);
return <SignInSide onChangeUsername={this.onChangeUsername} />;
}
}
SignInSide Component:
import { useState } from "react";
export default function SignInSide(props) {
const [data, setData] = useState("");
const handleChange = (e) => setData(e.target.value);
return (
<form>
<label>
User Name:
<input type="text" value={data} onChange={handleChange} />
</label>
<button type="button" onClick={() => props.onChangeUsername(data)}>
Submit
</button>
</form>
);
}
Application View
Check the console logs as shown in the application view to identify whether username is updated in main component.

React - can't call setState on a component that is not yet mounted (scaledrone app)

it gives me the same error no matter what i try;
its either that error or my push function breaks
full error is: "Warning: Can't call setState on a component that is not yet mounted. This is a no-op, but it might indicate a bug in your application. Instead, assign to this.state directly or define a state = {}; class property with the desired state in the App component."
import React, { Component } from 'react';
import './App.css';
import Messages from "./Messages";
import Input from "./Input";
class App extends Component {
constructor() {
super();
this.state = {
messages:[],
member: {
username: randomName(),
color: randomColor(),
},
}
this.drone = new window.Scaledrone("Qk3ma3HbEXr6Lwh7", {
data: this.state.member
});
this.drone.on('open', error => {
if (error) {
return console.error(error);
}
const member = {...this.state.member};
member.id = this.drone.clientId;
this.state.member = {...member};
});
const room = this.drone.subscribe("observable-room");
room.on('data', (data, member) => {
const mcopy = this.state.messages;
mcopy.push({member, text: data});
this.setState({mcopy});
});
}
render() {
return (
<div className="App">
<div className="App-header">
<h1>Chat Aplikacija</h1>
</div>
<Messages
messages={this.state.messages}
currentMember={this.state.member}
/>
<Input
onSendMessage={this.onSendMessage}
/>
</div>
);
}
onSendMessage = (message) => {
this.drone.publish({
room: "observable-room",
message
});
}
}
export default App;
You should not call setState() in the constructor(), Technically setState is meant to update existing state with a new value. you should move state manipulation to ComponentDidMount life cycle.
Also, don't mutate state, instead make a clone and then make changes.

React componentDidUpdate() does not fire

I have an react app with primereact installed and I am using primereact/captcha.
Maybe I have misunderstood something, but isn't the following code supposed to work (console.log('Child component did update'))?
import React from 'react';
import { Captcha } from 'primereact/captcha';
export default function App() {
return (
<div className="App">
<ParentComponent/>
</div>
);
}
class Child extends React.Component {
componentDidUpdate () {
console.log('Child component did update');
}
render() {
return (<h2>Child component</h2>);
}
}
class ParentComponent extends React.Component {
constructor() {
super();
this.state = {
captchaSovled: false,
key : Math.random()
}
}
render() {
let output;
if (this.state.captchaSolved) {
output = <Child key={this.state.key} />;
} else {
output =<Captcha siteKey="xxxxxxx" onResponse={() => this.setState({ key : Math.random(), captchaSolved: true })} />
}
return (
<div>
<h1>Parent component</h1>
{output}
</div>
);
}
}
From React doc
componentDidUpdate() is invoked immediately after updating occurs. This method is not called for the initial render.
In your code, the Child component is mounted after captchaSolved state is set, therefore only componentDidMount is fired on Child component.
componentDidUpdate is fired, if there is any change in the state or props. As of your component child:
class Child extends React.Component {
componentDidUpdate () {
console.log('Child component did update');
}
render() {
return (<h2>Child component</h2>);
}
}
There is no state or props which are changing, that's why componentDidUpdate never get's invoked.

Unable to call a property of child react component from parent react component

Following is the jsx code of child & parent parent react components. I am trying to pass the data from child react component to parent react component as property (generateReport) but it throws the error as
Uncaught TypeError: this.props.generateReport is not a function
at MetricsReport.generateReport (metrics-report.jsx:40)
child.jsx
import React, { Component } from 'react';
import {
Row,
Col,
Input,
Collapsible,
CollapsibleItem
} from 'react-materialize';
class MetricsReport extends Component {
constructor(props) {
super(props);
this.state = {
metricsParams: { reportType: '' }
};
this.getReportType = this.getReportType.bind(this);
// Add the below line as per the answer but still facing the problem
this.generateReport = this.generateReport.bind(this);
}
getReportType(event) {
console.log(this.state.metricsParams);
let metricsParams = { ...this.state.metricsParams };
metricsParams.reportType = event.target.value;
this.setState({ metricsParams });
}
generateReport() {
this.props.generateReport(this.state.metricsParams);
}
componentDidMount() {}
render() {
return (
<div class="ushubLeftPanel">
<label>{'Report Type'}</label>
<select
id="metricsDropDown"
className="browser-default"
onChange={this.getReportType}
>
<option value="MetricsByContent">Metrics By Content</option>
<option value="MetricsByUser">Metrics By User</option>
</select>
<button onClick={this.generateReport}>Generate Report</button>
</div>
);
}
}
export default MetricsReport;
parent.jsx
import React, { Component } from 'react';
import MetricsReport from '../components/pages/metrics-report';
class MetricsReportContainer extends Component {
constructor(props) {
super(props);
this.generateReport = this.generateReport.bind(this);
}
generateReport(metricsParams) {
console.log(metricsParams);
}
componentDidMount() {}
render() {
return (
<div>
<MetricsReport generateReport={this.generateReport} />
</div>
);
}
}
export default metricsReportContainer;
You forgot to bind the context this inside child component MetricsReport:
// inside the constructor
this.generateReport = this.generateReport.bind(this);
But you may simply use like this:
<button
onClick={this.props.generateReport(this.state.metricsParams)}
>
Generate Report
</button>
You can handle these scenarios by using anonymous functions instead of normal function. Anonymous function handles your this and removes the need to binding.
generateReport(metricsParams) {
console.log(metricsParams);
}
becomes
generateReport = (metricsParams) => {
console.log(metricsParams);
}
Also in child class
generateReport() {
this.props.generateReport(this.state.metricsParams);
}
becomes
generateReport = () => {
var metricsParams = this.state.metricsParams;
this.props.generateReport(metricsParams);
}

React-Checking and Unchecking of CheckBox

I have a component in which there is a checkbox and on on Changed event of checkbox ,i want to display a message saying it is checked or not .
There is no error in VS Code ,and when i tried debugging the code did fire on Changed event and called the tick function and call the setstate,but then when i step into next line ,it goes into some internal React javascript files which i find hard to understand ,to figure out the problem . Presently i get a checkbox ,but on checking or unchecking the message does not change
import React,{ Component } from 'react';
class Checked extends Component{
constructor(props)
{
super(props);
this.state= {check: true};
}
tick()
{
this.setState({check:!this.state.check});
}
render(){
var msg="";
if ( this.state.check=true)
{
msg="checked";
}
else
{
msg="unchecked";
}
return(<div><input type="checkbox" onChange={this.tick.bind(this)} defaultChecked={this.state.check} ></input>
<h1>checkbox is {msg}</h1>
</div> );
}
}
export default Checked;
You can also add the logic in the function, this implementation works for me
import React from 'react';
import { render } from 'react-dom';
class Checked extends React.Component {
constructor(props) {
super(props);
this.tick = this.tick.bind(this);
this.state = {
checkBox: true,
checkedMsg: ''
}
}
tick() {
this.setState({
checkBox: !this.state.checkBox
})
this.state.checkBox ? this.setState({ checkedMsg: 'checked' }) : this.setState({ checkedMsg: 'unchecked' })
}
render() {
return (
<div>
<input
type="checkbox"
onClick={this.tick}
defaultChecked={this.state.check}
/>
<h1>checkbox is {this.state.checkedMsg}</h1>
</div>
);
}
}
export default Checked;
render(<Checked />, document.getElementById('root'));

Resources