0

I am new to React hooks and been playing with the useState function lately.. In vanilla javascript this code works:

const state = {
  firstname: "John",
  lastname: "Doe",
  greeting: function(){
     return sayHello(this.firstname,this.lastname)
  }
}

const sayHello = (fname,lname)=>{
  return `Hello i'm ${fname} ${lname}, nice to meet you!`;
}

console.log(state.greeting()); //outputs "Hello i'm John Doe, nice to meet you!"

But with React hooks:

const [state,setState] = useState({
  firstName: "John",
  lastName: "Doe",
  greeting: function(){
    return sayHello(this.firstName,this.lastName)
  }
})

const sayHello = (fname,lname) => console.log(`Hello i'm ${fname} ${lname}, nice to meet you!`);

const { firstName, lastName, greeting } = state;
return (
    <div>
        <button className="btn" onClick={greeting()}>Greet</button>
    </div>
)

I get an error saying: "Cannot read property 'firstName' of undefined", and i also get [object object] if i just use the "firstName" and "lastName" within the method without the "this" keyword. How can i access the variables?

Twirlman
  • 1,109
  • 2
  • 12
  • 30
  • You don't use `greeting` in the code above. – Estus Flask May 11 '19 at 06:46
  • @Twirlman, can you show how you are calling `greeting` in 2nd snippet, it should work. – Mayank Shukla May 11 '19 at 06:54
  • @estus let's say for example it's attached to an onClick button. – Twirlman May 11 '19 at 07:08
  • 1
    @Twirlman you will get all the answers here: [**How to access the correct `this` inside a callback?**](https://stackoverflow.com/questions/20279484/how-to-access-the-correct-this-inside-a-callback), solution to your problem is: `onClick={greeting.bind(state)}` now when `greeting` method will get called, `this` will point to state value and it will work perfectly. Without proper binding of context `this` will be undefined inside event handler function. – Mayank Shukla May 11 '19 at 07:25

2 Answers2

2

First of all, you're not calling the greeting method, you should call it in order to get your code working.

Second, you should keep your state as lean as possible, so rethink twice before assigning a method to your state, there's a better place to place it in.

const [firstName, setFirstName] = useState('John');
const [lastName, setLastName] = useState('Doe');

const greeting = () => {
  console.log(`Hello i'm ${firstName} ${lastName}, nice to meet you!`);
}

Then somewhere in your code call greeting().

As you can see, we're assigning the firstName and lastName variables with their initial values and their respective setters thanks to react hooks, so you can call them directly in your code.

console.log(`Hi, my name is ${firstName}`) // Hi, my name is John

And if you run setFirstName('David') and after that, you run again:

console.log(`Hi, my name is ${firstName}` // Hi, my name is David

You will get the updated version of firstName, that's better isn't?

bntzio
  • 1,274
  • 14
  • 27
  • Ok sorry i didn't include the whole code. i'm calling it on a button. But let's say i'm going to pass it as a prop using context api. I'm using useContext so i should include the greeting function inside the state right? provided i have enclosed my return statement inside a provider... I can destructure it into const { firstName, lastName , greeting } = useContext(mycontext); – Twirlman May 11 '19 at 07:19
1

The only possibility for the error to occur is that greeting is used with wrong context, e.g. as a callback. This can be fixed by hard-coding it to use correct object:

  ...
  greeting: () => sayHello(state.firstname, state.lastname)
  ...

React and React hooks encourage the use of FP instead of OOP. If there are no reasons to keep greeting method as a part of the state, it can be:

const [state,setState] = useState({
  firstName: "John",
  lastName: "Doe"
});

const { firstName, lastName } = state;
const greeting = () => sayHello(firstName, lastName);

useCallback is not necessary but it may help in some cases:

const greeting = useCallback(() => sayHello(firstName, lastName), [firstName, lastName]);
Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • But what if you want to pass greeting method as context using the "useContext" to other nested components? so it should be in the state right? – Twirlman May 11 '19 at 07:07
  • 2
    @Twirlman Then you should consider it a callback and keep in mind that callbacks lose `this`, unless they are bound. As for useState, the simplest way is to refer to `state` object directly instead of `this`. – Estus Flask May 11 '19 at 07:09