0

App Component:

class App extends React.Component {
        constructor() {
          super();
            this.state = {
            user: 'Dan',
          };
        }
        render() {
          return (
            <React.Fragment>
              <label>
                <b>Choose profile to view: </b>
                <select
                  value={this.state.user}
                  onChange={e => this.setState({ user: e.target.value })}
                >
                  <option value="Dan">Dan</option>
                  <option value="Sophie">Sophie</option>
                  <option value="Sunil">Sunil</option>
                </select>
              </label>
              <h1>Welcome to {this.state.user}’s profile!</h1>
              <p>
                <ProfilePageClass user={this.state.user} />
                <b> (class)</b>
              </p>
            </React.Fragment>
          )
        }
      }

ProfilePageClass (the problem is here):

class ProfilePageClass extends React.Component {
  showMessage = () => {
    alert('Followed ' + this.props.user); // This get wrong value (new props)
  };

  handleClick = () => {
    setTimeout(this.showMessage, 6000); // This get wrong value (new props)
  };

  render() {
    return <button onClick={this.handleClick}>Follow</button>;
  }
}

setTimeout does not display the message corresponding to the user that was originally followed

I think it's a problem with the props or this, but I'm not sure.

Can anyone tell me what is going on?

  • 1
    What do you mean by "*the user that was originally followed*"? Shouldn't the button alert the name of the user that was followed? – Bergi Jan 27 '23 at 11:34
  • Yes @Bergi, click on follow, the setTimeout takes a moment (I intentionally did it to see if someone could explain it to me) but it changes the props and alert gives the wrong user because the App changed in the user list. –  Jan 27 '23 at 11:38
  • 1
    For example, we gave follow on "Dan", then it was changed to "Sunil", the message should have gone to "Dan" which was the first to be followed but the message remains on "Sunil" which was where the select component was left. –  Jan 27 '23 at 11:41
  • https://codesandbox.io/s/pjqnl16lm7 this is where I got the example –  Jan 27 '23 at 11:43

3 Answers3

0

Nothing to do with the this keyword. When the app state changes, your component instance receives new props values, and the setTimeout callback that runs after they have changed will access the new values. This is sometimes desirable, sometimes not.

This is one of the differences between function components and class components. To get the user profile that was rendered when you clicked the button, you need to explicitly remember it when the button is clicked (or rendered):

class ProfilePageClass extends React.Component {    
  handleClick = () => {
    const user = this.props.user;
//  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    setTimeout(() => {
      alert('Followed ' + this.props.user); // current value (wrong)
      alert('Followed ' + user); // old value (expected)
    }, 6000);
  };

  render() {
    return <button onClick={this.handleClick}>Follow</button>;
  }
}

With a function component, you can't get it wrong (but accessing the current value is next to impossible without useRef):

function ProfilePageClass({user}) {    
  const handleClick = () => {
    setTimeout(() => {
      alert('Followed ' + user); // old value (expected)
    }, 6000);
  };
  return <button onClick={this.handleClick}>Follow</button>;
}
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • +1 then @Bergi, setTimeout in ProfilePageClass sees the new values of this.props.user because it is a closure (the callback of setTimeout), this.props.user is a "live" reference (because closures captures REFERENCES, NOT COPY-SNAPSHOTS), right? –  Jan 27 '23 at 11:56
  • 2
    Yes, the arrow function forms a closure over `this`. Notice that in JS, closures don't even capture values at all (whether that's reference values or copies of those values or deep copies/snapshots even), they capture variables. Though neither `this` nor `const user` do change, so here it's equivalent to capturing the (reference) value of `this` indeed. – Bergi Jan 27 '23 at 12:00
  • "they capture variables", @Bergi that is, it captures `any` variable, reference variables (objects) and primitive values (a number for example) but "alive", not old, right? –  Jan 27 '23 at 12:08
  • 1
    It captures the live variable, no matter what the current value (object / primitive) is. A variable has no type, you can assign a primitive to a variable currently holding an object and vice versa. – Bergi Jan 27 '23 at 12:09
  • "the arrow function forms a closure over `this`" this confused me, did it close on `this` itself or on this.props.user (the user prop only)? or on `this` because it is arrow function? –  Jan 27 '23 at 12:15
  • 2
    On `this` itself, due to how arrow functions work. – Bergi Jan 27 '23 at 12:43
  • arrow functions captures the value of `this` from the containing scope, right? @Bergi –  Jan 27 '23 at 13:16
  • very useful answer @Bergi +1. This is not a problem of closures but to a "certain extent" of `this`, because the setTimeout accesses something of `this` (ProfilePageClass component), in this case that something is the prop user. `this` is not the problem as such as I said, because `this` itself does not change (it is always ProfilePageClass), what changes are its props due to the parent App component passing new props to it, right @Bergi? –  Jan 27 '23 at 13:40
  • 1
    Yes, see the first paragraph of the answer: the problem is that React is mutating the `.props` of the component instance. – Bergi Jan 27 '23 at 14:06
  • Ok, thanks, I need to confirm it. "With a function component, you can't get it wrong (`but accessing the current value is next to impossible`)" @Bergi - That's because `all` functions (useEffect for example) within a function component capture state (useState) and props (props if any), i.e. they capture `everything` within the function component and so "original-initial" values are "preserved" right? –  Jan 27 '23 at 14:17
  • 1
    They capture all the `const`ant immutable variables of the current render, yes. The only way out is to `useRef`. – Bergi Jan 27 '23 at 14:47
  • Excelent @Bergi I didn't know that the state and props were considered constant immutable variables in function component –  Jan 27 '23 at 15:09
-1

It's likely there's a problem regarding the setTimeout your using, in which you're losing reference to the actual prop you wish to display. You can add something like this to your parent component:

this.myRef = React.createRef();

This will generate a ref that you can later pass in to the child component. You can set the refs current item in this fashion:

this.myRef.current = this.state.user

in order to populate the ref.

saguirrews
  • 280
  • 1
  • 7
  • hi @saguirrews, thanks, but, I need a little more detail to understand –  Jan 27 '23 at 02:37
-1

Try this modification, as there might be handleClick is not auto bound.

import React from "react";
export default class App extends React.Component {

  constructor(props) {
    super(props);
      this.state = {
      user: 'Dan',
    };
  }
  render() {
    return (
      <React.Fragment>
        <label>
          <b>Choose profile to view: </b>
          <select
            value={this.state.user}
            onChange={e => this.setState({ user: e.target.value })}
          >
            <option value="Dan">Dan</option>
            <option value="Sophie">Sophie</option>
            <option value="Sunil">Sunil</option>
          </select>
        </label>
        <h1>Welcome to {this.state.user}’s profile!</h1>
        <p>
          <ProfilePageClass user={this.state.user} />
          <b> (class)</b>
        </p>
      </React.Fragment>
    )
  }
}

Profile alert

class ProfilePageClass extends React.Component {
//Define a constructor here
  constructor(props){
    super(props)
// Bind handleClick , so that `this` will point to parent component when you pass to child
    this.handleClick= this.handleClick.bind();
  }
  showMessage = () => {
    alert('Followed ' + this.props.user); // This get value (new props)
  };

  handleClick = () => {
    setTimeout(this.showMessage, 100); // This get  value (new props)
  };

  render() {
    return <button onClick={this.handleClick}>Follow</button>;
  }
}
Mallikarjun M G
  • 509
  • 4
  • 9