Why useEffect doesn't fire on every render? - reactjs

My component has two Rect.useEffect hook
const Example = ({ user }) => {
React.useEffect(() => {
autorun(() => {
console.log("inside autorun", user.count);
});
});
// Only runs once
React.useEffect(() => {
console.log("Why not me?");
});
return <Observer>{() => <h1>{user.count}</h1>}</Observer>;
};
I update this component using mobx. It is re-rendered correctly. But "Why not me?" is printed only once.
As per official docs
By default, effects run after every completed render
This means console.log("Why not me?"); too should run every time prop user is updated. But it doesn't. The console output is this
What's the reason behind this apparent inconsistency?
My complete code can be viewed here

In Mobx, just like Observer component which provides a render function callback, autorun function also executes independently of the react lifecycle.
This behaviour happens because you have user count as a observable variable.
According to the mobx-react docs
Observer is a React component, which applies observer to an anonymous
region in your component. It takes as children a single, argumentless
function which should return exactly one React component. The
rendering in the function will be tracked and automatically
re-rendered when needed.
and mobx docs
When autorun is used, the provided function will always be triggered
once immediately and then again each time one of its dependencies
changes.
You can confirm this behvaior by logging directly inside the functional component and you will observer that the component is only rendered once
EDIT:
To answer your question
If I change useEffect to this
React.useEffect(autorun(() => {console.log("inside autorun", user.count)}));
basically remove anonymous function from useEffect and just pass
autorun directly, then it is run only once. Why is it so? What's the
difference?
The difference is that autorun returns a disposer function which when run will dispose of the autorun and would not longer execute it.
From the docs:
The return value from autorun is a disposer function, which can be
used to dispose of the autorun when you no longer need it.
Now what happens is that since useEffect executes the callback provided to it when it runs, the callback executed is the disposer function returned by autorun which essentially cancels your autorun.

It looks like your component doesn't rerender. autorun receives a callback and might call it independently from render.
Example component rerenders only when its parent rerenders or when its props change.
Use this code to observe what's really happening:
const Example = ({ user }) => {
console.log('render');
React.useEffect(() => {
console.log('useEffect autorun');
autorun(() => {
console.log("inside autorun", user.count);
});
});
// Only runs once
React.useEffect(() => {
console.log("Why not me?");
});
return <Observer>{() => <h1>{user.count}</h1>}</Observer>;
};

Related

How to run a single useEffect both on re-render and on dependency change?

I have the following code,
const [over, setOver] = useState(false);
useEffect(() => {
//some logic
}, [over]);
and I want to be able to run the use effect when the (over) has been changed and also if component props changes as for now the code runs only when over has been changed, similar to
useEffect(() => {
//some logic
}, []);
Your first code and second are not similar at all. Second one runs once only when the component is mounted (added to the DOM). And first code will run onComponentMount and onOverChange.
You need to understand react component lifecycle to understand how useEffect works.
There are 3 lifecycle events. onMount, onStateChange and onUnMount. The callback function runs when onMount or onStateChange is triggered. When you use [] it means the code will only run when mounted and not on any stateChange event. (Cause there isn't any state available to watch)
useEffect(() => {
// this will run when component is added to the DOM or a dependency state have changed
console.log("mounted or dependency state changed");
// this will run when component is destroyed
return () => console.log("component unmounted");
}, [dependency1, dependency2]);
One straightforward way to do this is add the props in dependency array
useEffect(() => {
//some logic
}, [over,props]);
But that is not really a good idea
Go through this once You might not need an effect

UseEffect triggering without respect to dependency array

I have a function below which i used as an array dependency to a useEffect handler
const handleInputUpdate = (event) => {
const eventValue = event.target.value;
setState({ ...state, answer_text: eventValue, trigger: true })
// console.log("I am changing for no reason")
}
Below is the useEffect handler
useEffect(() => console.log(" I am changing for no reason in useeffect"), [handleInputUpdate])
What i want is the useEffect handler to run only when the handleInputUpdate function is called but it runs also on component mount.
Here's what i've observed
The handleInputUpdate function doesn't run on component mount but only i need it to
Without respect to the above observation, the useEffect handler runs anyway.
Here's what i've tried
I tried consoling a text inside the handleInputUpdate function to see whether it runs on component render but it doesn't.
Even though the function doesn't run, the useEffect handler triggers anyway which is not what i want.
How can i solve this ?
Thanks in advance
useEffect dependency array is not used to trigger the effect when a function is called; the elements of the array are observed for any change and then trigger the effect.
In this case, handleInputUpdate will change on every render because it is not memoised, so the effect will also run on every render.
Since handleInputUpdate changes the state when it is called, you are better off adding that state to your useEffect dependency array:
useEffect(() => {
if (answer_text && trigger) {
console.log("I am changing for a reason in useeffect")
}
}, [answer_text, trigger])
The handleInputUpdate function, while it doesn't run on render, looks like it's created when the component runs, just before rendering. Since it won't be === to the value last in the dependency array - the handleInputUpdate from the prior render - the effect callback will run.
You need to observe changes to the answer_text value in state instead.
useEffect(() => {
// ...
}, [state.answer_text]);
I would also recommend separating out your state into different variables - these aren't class components, don't feel like you have to mash everything together into a single object structure.
const [text, setText] = useState('');

useCallback vs useEffect in React

What's the different between useEffect when you pass it dependencies as the second parameter and useCallback?
Don't both essentially run the function/code passed as the first parameter whenever the dependencies passed as the second parameter change?
From what I've read the two hooks are intended to serve different purposes, but my question is whether they in actuality could be used interchangeably because they functionally do the same thing
They're too different.
useEffect will run the function inside when the dependency array changes.
useCallback will create a new function when the dependency array changes.
You can't switch useEffect with useCallback alone because you also need the logic to run the newly created function. (I suppose you could implement this if you used a ref as well, but that'd be quite strange.)
You can't switch useCallback with useEffect because you very often don't want to run the newly created function immediately - rather, you usually want to pass it as a prop to some other component.
useCallback primarily exists for optimization purposes, to reduce re-renders of a child component.
No, They are not same.
useEffect - is used to run side effects in the component when something changes. useEffect does
not return you anything. It just runs a piece of code in the component.
useCallback - Whereas useCallback returns a function, it does not execute the code actually. It is important to understand that
functions are objects in Javascript. If you don't use useCallback, the function you define inside the component is
re-created whenever the component rebuilds.
Example
Consider this example, this component will go in a infinite loop. Think Why?
const TestComponent = props => {
const testFunction = () => {
// does something.
};
useEffect(() => {
testFunction();
// The effect calls testFunction, hence it should declare it as a dependency
// Otherwise, if something about testFunction changes (e.g. the data it uses), the effect would run the outdated version of testFunction
}, [testFunction]);
};
Because on each render the testFunction
would be re-created and we already know that ueEffect will run the code when ever the testFunction changes. And since testFunction changes on each render, the useEffect will keep on running, and hence an infinite loop.
To fix this, we have to tell react, hey please don't re-create the testFunction on each render, create it only on first render (or when something changes on which it depends).
const TestComponent = props => {
const testFunction = useCallback(() => {
// does something.
}, []);
useEffect(() => {
testFunction();
// The effect calls testFunction, hence it should declare it as a dependency
// Otherwise, if something about testFunction changes (e.g. the data it uses), the effect would run the outdated version of testFunction
}, [testFunction]);
};
This won't be a infinite loop, since instance of testFunction will change only on first render and hence useEffect will run only once.
useEffect will run the function inside when the dependency array changes.
useCallback will create a new function when the dependency array changes.
Let's take an example, If I run the below code and click the first button it'll always rerender MemoComponent as well. Why because every time
we are passing new onClick function to this. To avoid re-rendering of MemoComponent what we can do is wrap onClick to useCallback. Whenever you want to create a new function pass state to the dependence array.
If you want to perform some action on state change you can write inside useEffect.
const Button = ({ onClick }) => {
console.log("Render");
return <button onClick={onClick}>Click</button>;
};
const MemoComponent = React.memo(Button);
export default function Home() {
const [state, setState] = useState(1);
useEffect(() => {
console.log(state); // this will execute when state changes
}, [state]);
const onClick = () => {};
// const onClick = useCallback(() => {},[])
return (
<main>
<button onClick={() => setState(1 + state)}>{state}</button>
<MemoComponent onClick={onClick} />
</main>
);
}
useEffect
It's the alternative for the class component lifecycle methods componentDidMount, componentWillUnmount, componentDidUpdate, etc. You can also use it to create a side effect when dependencies change, i.e. "If some variable changes, do this".
Whenever you have some logic that is executed as reaction to a state change or before a change is about to happen.
useEffect(() => {
// execute when state changed
() => {
// execute before state is changed
}
}, [state]);
OR
useEffect(() => {
// execute when state changed
() => {
// execute before state is changed
}
}, []);
useCallback
On every render, everything that's inside a functional component will run again. If a child component has a dependency on a function from the parent component, the child will re-render every time the parent re-renders even if that function "doesn't change" (the reference changes, but what the function does won't).
It's used for optimization by avoiding unnecessary renders from the child, making the function change the reference only when dependencies change. You should use it when a function is a dependency of a side effect e.g. useEffect.
Whenever you have a function that is depending on certain states. This hook is for performance optimization and prevents a function inside your component to be reassigned unless the depending state is changed.
const myFunction = useCallback(() => {
// execute your logic for myFunction
}, [state]);
Without useCallback, myFunction will be reassigned on every render. Therefore it uses more compute time as it would with useCallback.

React: Passing props to parent returns <empy string>

First let me put my code here and I'll explain what's happening.
Parent.js
callback = (id) => {
this.setState({des: id});
console.log(this.state.des);
}
//on my render i have this when i call my child component
<Child callback={this.callback}/>
Child.js
handleChange = (event) => {
let des = event.target.value;
console.log(des);
this.props.callback(des);
};
When i console.logon my Child component, it returns the data that i want to pass, but when I do it in calbackon my Parent component, it returns <empty string> and i don't know why that's happening.
The reason this is happening is because setState is an async function. When you are trying to log this.state.des, the state would not have been set yet. If you want to console log your state to see if it has been set as expected, what you want to do is log it in the callback of this.setState (so it logs once we know state is set). Try something like the following in your parent.js :
callback = (id) => {
this.setState({des: id}, () => {
console.log(this.state.des);
});
}
see the React Docs for setState for more details
The call to setState is asynchronous and therefore you might not read the updated state if you are accessing it directly after calling setState. Because of this setState(updater[, callback]) actually exposes a callback which can be used for operations which depend on the state update being done. (This is explained in the react docs for setState.)
In your case, adjusting the callback function like this
callback = (id) => {
this.setState({des: id}, () => {
console.log(this.state.des);
});
}
should do the trick.
If you want to know more about the reasoning behind setState being asynchronous (even if it might be a bit confusing in the beginning, like in your case) you should check out this github issue and especially this comment.

React useEffect runs every time I scroll after using hooks

I have a hook to save scroll value on window scroll.
(BTW, my state object is more complicated than this example code. That's why I'm using useReducer. I simplified the state for this question.)
Then when I attached that hook to App, everything inside useEffect runs every time I scroll.
I get that console.log(scroll) prints every time on scroll, but why is console.log("somethingelse") also prints every time on scroll?
I have other codes to put inside useEffect, so it's a huge problem if everything inside useEffect runs on scroll.
How do I make only useScroll() related code to run on scroll, and everything else runs only on re-render like useEffect supposed to do?
I made CodeSandbox example below.
function App() {
const scroll = useScroll();
useEffect(() => {
console.log(scroll);
console.log(
"this also runs everytime I scroll. How do I make this run only on re-render?"
);
});
return ( ... );
}
The way your code is currently written the useEffect code will be executed on each update. When scrolling the state is updated, so the effect will be executed.
You can pass props as the second variable to the useEffect to determine whether it should run or not.
So, if you create an effect like this it will run every time the scroll is updated
useEffect(() => {
console.log('This wil run on mount when scroll changes')
}, [scroll]) // Depend on scroll value, might have to be memoized depending on the exact content of the variable.
useEffect(() => {
console.log('This will only run once when the component is mounted')
}, []) // No dependencies.
useEffect(() => {
console.log('This will run on mount and when `someStateVar` is updated')
}, [someStateVar]) // Dependent on someStateVar, you can pass in multiple variables if you want.
Without passing the second argument, the effect will always run on every render. You can add multiple effects in a single component. So, you'll probably want to write one containing the stuff that needs to be executed when scroll updates and create one or more others that only run on mount of after some variable changes.
There is a concept of cleanup functions which can be attached to an useEffect hook. This cleanup function will be run once the rerender is complete. It's usually used in scenarios where you need to unsubscribe from a subscription once the render is complete. Below is the sample code from React website
useEffect(() => {
const subscription = props.source.subscribe();
return () => {
// Clean up the subscription
subscription.unsubscribe();
};
});
More details can be found in the blogpost
useEffect Hook

Resources