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.
The problem seems to be in closures.
Everything inside the useEffect
s 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.