Unmounted components still being rendered first - javascript

I'm new to React, just a question on componentWillUnmount() and render() lifecycle method. Below is some code:
...
export default class App extends Component {
constructor(props) {
super(props);
this.state = { showMessage: true }
}
handleChange = () => { this.setState({ showMessage: !this.state.showMessage });
}
render(){
<div>
<input type="checkbox" checked={ this.state.showMessage } onChange={ this.handleChange } />
<label>Show</label>
</div>
{ this.state.showMessage && <Message message="Hello" /> }
}
}
export class Message extends Component {
...
componentWillUnmount() {
console.log("Unmount Message Component");
}
render(){
console.log(`Render Message Component `);
return (
<div>{this.props.message}</div/
)
}
}
I trigger the unmounting phase of the Message componentby unchecking the checkbox , so I have those in the console output:
Render Message Component
Unmount Message Component
so my question is:
It is not efficient because the current Message component is going to be destroyed since I don't need it once I uncheck the box. But Message component's render() was still being called, which is unnecessary since we don't really care what content it contains, is it a way to avoid the call of re-render method and just get componentWillUnmount() to be called? I was thinking to use shouldComponentUpdate(), but I can stop render() method to be called, but that will also stop componentWillUnmount() to be called too

When you unmount a component render is not executed - only componentWillUnmount is called. The Render Message Component log is caused by initial render when Message is visible:
class App extends React.Component {
constructor(props) {
super(props);
this.state = { showMessage: true }
console.log("initial render:");
}
handleChange = () => { this.setState({ showMessage: !this.state.showMessage });
}
render(){
this.state.showMessage || console.log("unmounting:");
return <div>
<input type="checkbox" checked={ this.state.showMessage } onChange={ this.handleChange } />
<label>Show</label>
{ this.state.showMessage && <Message message="Hello" /> }
</div>
}
}
class Message extends React.Component {
componentWillUnmount() {
console.log("Unmount Message Component");
}
render(){
console.log(`Render Message Component `);
return (
<div>{this.props.message}</div>
)
}
}
ReactDOM.render(<App/>, document.getElementById("root"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.development.js"></script>
<div id="root"></div>

Related

how to load App class`s componentWillUnmount

Here is the App class. Returns a div or null as the value value in the render function. Shouldn't the app's componentWillUnmount function also execute when returning null? I do not understand that only the componentWillUnmount function of the Header and Body classes is executed.
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {value : true};
}
componentWillUnmount() {
console.log('App componentWillUnmount');
}
btnClick() {
this.setState({
value: ! this.state.value
});
}
render () {
if(this.state.value) {
return (
<div>
<button onClick={this.btnClick.bind(this)}>Btn</button>
<Header id={this.state.value}></Header>
<Body></Body>
</div>
)
} else {
return null;
}
}
}
ReactDOM.render(
<App id="3"/>,
document.getElementById('root')
);

Prevent Child component getting rendered when the state is changed in react

I have a parent and child component of react. Here i pass the id as a prop from parent to child and i am saving the value of the textarea entered using the state. Whenever i am typing in the textarea. The child component gets updated. how to prevent the child component getting updated on every value entered in the textarea? Please help me.
class Child extends React.Component {
constructor() {
super();
}
componentWillMount(){
console.log('child component Will '+this.props.id);
}
componentDidMount(){
console.log('child component Did '+this.props.id);
}
render() {
console.log('child render '+this.props.id);
return <p>Child {this.props.id}</p>;
}
}
class Application extends React.Component {
constructor(){
super();
this.state={
id:1,
textValue:undefined
}
}
componentWillMount(){
console.log('parent component Will');
}
componentDidMount(){
console.log('parent component Did');
}
render() {
console.log('parent render');
return <div>
<textarea onChange={(event)=>{
this.setState(
{textValue:(event.target.value)})
}
}></textarea>
<Child id='1'/>
<Child id='2'/>
</div>;
}
}
ReactDOM.render(<Application />, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react-dom.js"></script>
<div id="app"></div>
Code pen Link
Instead of extending React.Component you can use React.PureComponent. The difference between the two is that the latter also performs a shallow-comparison of both the props and state between each render; if nothing has changed, it doesn't update.
This is also recommended on the official documentation:
If your React component's render() function renders the same result given the same props and state, you can use React.PureComponent for a performance boost in some cases.
Have a look at the code below. I have only changed the first line of code to extend the correct class.
class Child extends React.PureComponent {
constructor() {
super();
}
componentWillMount(){
console.log('child component Will '+this.props.id);
}
componentDidMount(){
console.log('child component Did '+this.props.id);
}
render() {
console.log('child render '+this.props.id);
return <p>Child {this.props.id}</p>;
}
}
class Application extends React.Component {
constructor(){
super();
this.state={
id:1,
textValue:undefined
}
}
componentWillMount(){
console.log('parent component Will');
}
componentDidMount(){
console.log('parent component Did');
}
render() {
console.log('parent render');
return <div>
<textarea onChange={(event)=>{
this.setState(
{textValue:(event.target.value)})
}
}></textarea>
<Child id='1'/>
<Child id='2'/>
</div>;
}
}
ReactDOM.render(<Application />, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react-dom.js"></script>
<div id="app"></div>
Edit: I updated your question and made your code runnable in a code snippet, so that it can be compared with my snippet.
You can control when the component should render using shouldComponentUpdate
Your child component would look like this:
class Child extends React.Component {
componentWillMount(){
console.log('child component Will '+this.props.id);
}
componentDidMount(){
console.log('child component Did '+this.props.id);
}
shouldComponentUpdate(nextProps, nextState){
if (nextProps.id !== this.props.id) {
return true;
}
return false;
}
render() {
console.log('child render '+this.props.id);
return <p>Child {this.props.id}</p>;
}
}
In this example, the Child component will be updated only if its id changes.

Change parent component state from child component

I know that the question with this title has already been asked few times before but the problem is that I couldn't get an appropriate answer. So as I am new to reactJS and trying to create login logout form.
What I want to do is to pass or change a state of parent component from a child component through an event handler(When a user clicks on logout button). Below are the two Components:
First One:
class Home extends React.Component {
constructor(props){
super(props);
this.state = {login : false};
}
login(){
// this method updates the login.state : true
}
render() {
return (
<div>
{this.state.login ? (<ChatBox userNick="fad" />) : (<LoginScreen onSubmit={this.login} />) }
</div>
);
}
}
And Second:
class ChatBox extends React.Component{
logout(){
// Expecting or trying to update parent.state.login : false
// via this method as well
}
render() {
return (
<div className="chat-box">
<button onClick={this.logout} > Logout </button>
<h3>Hi, {this.props.userNick} </h3>
</div>
);
}
}
I have simplified these component to come on point.
What's going here?
Home Component is the main parent component. Initially the state.login is false and in this situation LoginScreen Components shows up. Now, when user login through LoginScreen Component state.login updates to true, it's time to show for ChatBox Component.
Now you can see that ChatBox Component contains a button which calls a method logout to logout user. What I want is to update once again the state.login to false in Home Component When user click on the Logout Button.
I don't know how to do it, It will be appreciate if you help me.
Thanks in advance.
Do it in the same way as you are doing for Login, pass a function as a prop and call it on logout, see updates below.
const LoginScreen = () => (<div>Login Screen</div>);
class Home extends React.Component {
constructor(props){
super(props);
this.state = {login : true};
this.logout = this.logout.bind(this);
}
login(){
// this method updates the login.state : true
}
logout() {
// this method updates the login.state : false
this.setState({ login: false });
}
render() {
return (
<div>
{this.state.login ? (<ChatBox userNick="fad" onLogout={this.logout} />) : (<LoginScreen onSubmit={this.login} />) }
</div>
);
}
}
class ChatBox extends React.Component{
constructor(props) {
super(props)
// This makes sure `this` keeps pointing on this instance when logout is called from the outside.
this.logout = this.logout.bind(this);
}
logout(){
// Call the onLogout property.
this.props.onLogout();
}
render() {
return (
<div className="chat-box">
<button onClick={this.logout} > Logout </button>
<h3>Hi, {this.props.userNick} </h3>
</div>
);
}
}
ReactDOM.render(<Home />, document.querySelector('#main'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="main"></div>
You can pass an event from the Parent component to the Child component that handles the change of the state, like so:
class App extends React.Component {
constructor() {
super();
this.state = { isLoggedIn: false };
}
_handleLogin() {
this.setState({ isLoggedIn: true });
}
_handleLogout() {
this.setState({ isLoggedIn: false });
}
render() {
const { isLoggedIn } = this.state;
return (
<div>
{
isLoggedIn ?
<ChatBox logoutEvent={this._handleLogout.bind(this)} />
:
<Login loginEvent={this._handleLogin.bind(this)} />
}
</div>
);
}
}
const Login = ({ loginEvent }) => (
<button type="button" onClick={loginEvent}>Login</button>
);
const ChatBox = ({ logoutEvent }) => (
<div>
<h1>This is the Chat Box!</h1>
<button type="button" onClick={logoutEvent}>Logout</button>
</div>
);
ReactDOM.render(
<App />,
document.getElementById('container')
);
Here's the fiddle

Why doesn't this child component rerender?

I'm experimenting with ReactJS and I'm trying to understand how child component rendering is triggered. In ReactJS, if I set up an example like this:
var externalCounterVar = 10
class Counter extends React.Component {
constructor(props){
super(props);
this.state = props;
}
render() {
console.log('rendering counter')
return (
<div> {externalCounterVar} </div>
)
}
}
class Main extends React.Component {
constructor(props){
super(props);
}
handleClick() {
externalCounterVar += 1;
}
rerender(){
this.render();
}
render() {
console.log('rendering');
return (
<div>
<button onClick={this.rerender.bind(this)} />
<Counter counter={externalCounterVar} />
</div>
)
}
}
ReactDOM.render(<Main />, document.getElementById('root'));
I'm not sure I understand why when you "rerender" it calls the render method of Main but not Counter? It seems like it should call both render methods since it's rendering Main and Counter is a child of Main.
So when rerender is called, 'rendering' will print but 'rendering counter' will not.
It looks like you're overlooking one of the main benefits of using React, namely how state works.
You never, ever need to call this.render within a React component
You should never set state dynamically, ie: this.state = ...
You should always use this.setState to set your state.
Rewritten, your code should look something like the following:
const externalCounterVar = 10
class Counter extends React.Component {
render() {
console.log('rendering counter')
return (
<div> {this.props.counter} </div>
)
}
}
class Main extends React.Component {
state = {
counter: externalCounterVar
}
handleClick() {
this.setState({counter: this.state.counter + 1});
}
render() {
console.log('rendering');
return (
<div>
<button onClick={this.handleClick.bind(this)} />
<Counter counter={this.state.counter} />
</div>
)
}
}
By calling this.setState, React automatically knows it needs to rerender your component, and as a result, all child components will also be rerendered.
Hope this helps!
In this case you don't have to use rerender method, also with purpose re-render all child components you need update state with method setState. And also accordingly to this you have to "move state up".
Here my example:
class Counter extends React.Component {
render() {
console.log('rendering counter');
return (<div> {this.props.counter} </div>);
}
}
class Main extends React.Component {
constructor(props){
super(props);
this.state = {counter: props.counter};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(prevState => ({counter: ++prevState.counter}));
}
render() {
console.log('rendering');
return (
<div>
<button onClick={this.handleClick} />
<Counter counter={this.state.counter} />
</div>
);
}
}
var externalCounterVar = 10;
ReactDOM.render(
<Main counter={externalCounterVar} />,
document.getElementById('root')
);
In some situations you can use this.forceUpdate() to call re-render.
But, if you can not do this, do not do.
https://facebook.github.io/react/docs/react-component.html#forceupdate

What does "Warning: setState(...): Can only update a mounted or mounting component" mean?

So I have one root component and two child components. I have trying to get one child to call a method that is up in in the root component and update the state up in the root component, and pass the updated down to the other component, but I am getting the following error.
What could be the issue?
warning.js?8a56:36 Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. Please check the code for the firstChild component.
Here is the code:
firstChild.js
export default class firstChild extends React.Component {
constructor(props) {
super(props);
this.state = {
nameText: '',
}
}
nameChange(event) {
this.setState({
nameText: event.target.value,
})
}
submitClick() {
var nameText = this.state.nameText;
this.props.saveName(nameText)
this.setState({nameText: ''});
}
render() {
var st = this.state;
var pr = this.props;
return (
<input
placeholder='Enter Name'
onChange={this.nameChange.bind(this)}
value={this.state.nameText}
/>
<button
onClick={this.submitClick.bind(this)}
/>
And in root component, App.js:
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
submitSuccess: false
}
}
saveName(nameText) {
this.setState({submitSuccess: true});
}
render() {
var props = {};
props.submitSuccess = this.state.submitSuccess;
return (
<div>
<firstChild
saveName={this.saveName.bind(this)}
/>
{React.Children.map(this.props.children, function(child) {
return React.cloneElement(child, props);
})}
</div>
)
}
}
And my secondChild.js:
export default class secondChild extends React.Component {
static propTypes = {
submitSuccess: React.PropTypes.bool.isRequired,
}
constructor(props) {
super(props);
this.state = {
}
}
render() {
return (
<div>
{this.props.submitSuccess}
</div>
)
}
}
Fisrt, rename all your React components as Camel Case like this.
class firstChild ... --> class FristChild
<fristChild> --> <FristChild>
Second, in your FirstChild render method, you should wrap your elements into an enclosing tag like this:
class FirstChild extends Component {
render(){
return (
<div>
<input ... />
<button ... />
</div>
)
}
}
Third, when you use cloneElement upon this.props.children, you should use Proptypes.<type> in your secondChildren instead of Propstypes.<type>.isRequired. Check it here to see why.
class SecondChild extends Component {
static propTypes = {
submitSuccess: React.PropTypes.bool, // remove isRequired
}
}
Regardless all above, I have tested your code and it works fine.
You can try and use componentWillUnmount lifecycle function in order to check when the component is unmounted.
You can also use a flag to signal that the component is unmounted before setting the state:
saveName(nameText) {
if (!this.isUnmounted){
this.setState({submitSuccess: true});
}
}
componentWillUnmount() {
this.isUnmounted = true;
}

Categories