Home > Net >  Not getting the expected output when using react hooks
Not getting the expected output when using react hooks

Time:08-09

I was converting react class components to functional components in one of my projects.

This is the class component

export default class App extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      user: null,
    }
  }

  componentDidMount() {
    this.func1().then((res) => {
      this.func2();
    })

  }
  async func1() {
    this.setState({
      user: "ishan"
    })
    const res = await fetch('https://jsonplaceholder.typicode.com/todos/1')
    const data = await res.json();
    return data;
  }

  func2() {
    console.log("user state: ",this.state.user);
  }
  render() {
    return (
    <div className='App'>
      <h1>Hello World</h1>
    </div>
    );
  }
}

This gives the following output in the console

user state: ishan

This is the corresponding functional component

export const App = () => {
  const [user, setUser] = useState(null);

  useEffect(() => {
    func1().then((res) => {
      func2();
    });
  }, []);

  const func1 = async () => {
    setUser("ishan");
    const res = await fetch("https://jsonplaceholder.typicode.com/todos/1");
    const data = await res.json();
    return data;
  };

  const func2 = () => {
    console.log("user state: ",user);
  };

  return (
    <div className="App">
      <h1>Hello World</h1>
    </div>
  );
};

This gives the following output in the console:

user state: null

I'm not sure why the user state is null in case of functional component, the flow of logic is similar in both the cases. Please explain the difference in the output.

CodePudding user response:

The problem seems to be in closures.

Everything inside the useEffects callback will only ever run on mount, hence, user will always be null there because closures "remembered" only its initial state.

One way to workaround this issue would be to split your useEffect into 2:

useEffect(() => {
  func1();
}, []);

useEffect(() => {
  func2();
}, [user]);

In this case, func2 will run each time user changes.

CodePudding user response:

Each render has it's own state, effect, props, and everything else

In order to better understand this, we can put a log statement outside func2 like this:

export const App = () => {
  const [user, setUser] = useState(null);

  useEffect(() => {
    func1().then((res) => {
      func2();
    });
  }, []);

  const func1 = async () => {
    setUser("ishan");
  };

  const func2 = () => {
    console.log("user state: ",user);
  };
  console.log("user state from outside: ",user); // <- put a log statement outside

  return (
    <div className="App">
      <h1>Hello World</h1>
    </div>
  );
};

And reload the app, you will get this output:

user state from outside: null
user state from outside: null

user state: null
user state: null

user state from outside: ishan 
user state from outside: ishan 

Both user state: null and user state from outside: null are expected to have a null value, as the first render has a user state as null, and both log statement are pointing the state at that particular render, but the outside log statement has a updated state logged out.

That's because the state gets updated by func1, causing component to rerender and thus outside log gets executed, and as opposed to initial render's null value, this render(completely different from the initial render) has an updated value. As useEffect has an empty dependency, it only runs once after initial render, and inside it func2 won't run this time.

Check out sandbox

  • Related