Is using useEffect absolutely necessary? - reactjs

I've been using React Hooks for a few months and have strictly adhered to using the useEffect hook as per the documentation.
A new colleague has joined the team and he is asking why is useEffect necessary. We've gone through the documentation together but he points out that implementing useEffect causes extra renders.
Here is a simple use case: A button is pressed to fetch some data. The click is handled by a Click event handler. He is wondering why he can't make the async call to the endpoint directly within the event handler.
I'm looking for an explanation that goes beyond "the docs say you shouldn't" and actually explains what the danger or downside of doing this is.
Might anyone be able to share why?

In short it's used for any side effects (not only fetching data):
A side effect is any application state change that is observable outside the called function other than its return value. Side effects include:
Modifying any external variable or object property (e.g., a global
variable, or a variable in the parent function scope chain)
Logging to the console
Writing to the screen
Writing to a file
Writing to the network
Triggering any external process
Calling any other functions with side-effects
Side effects are mostly avoided in functional programming, which makes the effects of a program much easier to understand, and much easier to test.
Haskell and other functional languages frequently isolate and encapsulate side effects from pure functions using monads.

Related

Is it always necessary to add all used functions within useEffect to its dependencies array?

I am currently building a Tetris game on React just to practice hooks (used to develop in class components back in the day, kind of left React for a while and yesterday I decided to use it once again).
The game is working perfectly well, and it behaves as expected on each and every situation, however, there is a constant warning related to using a function within useEffect without it being a dependency.
To clarify - I have a useEffect function that all it does is call an updateFunction and is dependent just on the x and y coordinates of the moving Tetris block. The update function updates the state of the board whenever the position of the shape changes.
I know that React re-creates functions on each and every render, but giving a useCallback to the update function would cause it to be re-created endlessly (as then, the complier would ask me to make it dependent on the board state, thus each time it updates the board, it'll be forced to be re-created once again), and this causes an infinite loop of rendering.
Is it really necessary to put every function within useEffect as a dependency, even if said functions only causes a visual rendering to show the current state of the game?
You can always put whole function inside useEffect and you want get this error and problem
Someone just asked the similar question, and my answer is there, React won't let me use `useEffect` in a completely reasonable way
The answer to your question is that, no you don't have to, if the warning doesn't bother you, and your code is still working, then move on.
Otherwise you can try to find a way to disable this linter, or ask yourself why not to meet all the dependency requirement.
It's best to think of the effect dependencies as "correct" or not, and not try to tailor them to achieve some specific behavior. (The vast majority of the time anyway)
This means that if an effect uses a value that could possibly change, then it's declared as a dependency. Sometimes you app works fine if your dependencies are "incorrect" because elsewhere you can guarantee a value is constant. But this is more about maintaining the application than having it work currently.
Later if you change how one of the dependencies works in a refactoring, then the effect now may need to re-run and doesn't leading to strange and hard to diagnose bugs like stale values where you can't tell where they come from.
If this leads to cumbersome hooks with huge dependency arrays that infinite loop themselves, then it's an indication that logic is messy and needs to be refactored, split into multiple effects, rethink how the data flows through your hooks, etc.
It's hard to advise specifically without seeing your code. But in your case it seems like you could make the "update function" take arguments instead, which means it could then be completely static. Or if you did use useCallback that depends on x,y then it would regenerate when those change, but you probably have a lot of logic to run when those change, so that's probably expected.
Summary:
I would advise you to take the warnings of eslint-plugin-react-hooks seriously, and clean your code until it passes. In the long wrong, in a large and complex application, it really helps keep things clean.
There are very rare exceptions that come up when doing non standard things, but it's worth it to try your best on this before resorting to ignoring those warnings.

Why are effects named as "passive effects" in React source code?

As a non native english speaker, i am confused by the term "passive effects", as in schedulePassiveEffects, cancelPassiveEffects in React source code.
What exactly does 'passive' mean here?
The reason why it's not just called effect is that there're other Effects in React. There're mutation effects and layout effects along with passive ones.
The passive one is the common one which listens to a state change (through a dependency array) and then can invoke a callback that changes other states.
What's unique about a passive effect is, it waits for all UI (render and commit) to settle down before invoking them in another time slice. So the callback happens in a Javascript time slice similar to an event handler (ex. onClick).
I believe this is where it gets its name, the "passive". Because instead of actively handling an event, it's passively listen to a state change and invoke a callback. I wrote a blog about this, https://javascript.plainenglish.io/is-a-react-passive-effect-an-artificial-event-2535977b9a91

Should I rely on the dependency list check of react useEffect hook?

Consider the following code
useEffect(effect, [v]) // v can be undefined
I want to run the effect function only after the first render or when v changes.
My questions are
Do I need to check whether v changes in effect? Or I can rely on react to check the value of the dependency list (i.e., the [v]).
If the answer of 1. is the latter, is this behavior supposed to be changed in the near future? For e.g., when the concurrent mode is released.
From react official docs
Conditionally firing an effect
The default behavior for effects is to
fire the effect after every completed render. That way an effect is
always recreated if one of its dependencies changes.
It says if one of its dependencies changes, then, an effect is recreated. However, it does not mention the reverse: an effect is recreated if and only if one of its dependencies changes.
I only remember that in the early stage of hook, I read somewhere that the effect function can be run in some cases even without v's change, in order to optimize the memory, or in concurrent mode?? I really do not remember exactly and could not find out the source.
It would be very helpful if anyone can tell me how exactly React does, or refer to the related internal source code of react. I think they would have a special check of the first render, otherwise, when v is undefined after the first render, effect will not run.
I also posted the question on react repo here. The thread helped me solved my question.
Hope that it also helps others.

Is it safe to call componentWillReceiveProps() with side effects

In React's componentWillReceiveProps function, if I check whether the props have changed first, is it ok to then make an AJAX call? It seems that this function may be called multiple times in React Fiber (post 16 beta) and this blog (https://medium.com/#baphemot/understanding-reactjs-component-life-cycle-823a640b3e8d) suggests that no side effects should be performed. However, I didn't see such a warning in the React docs and I figured that the AJAX call was made only if the props changed that that would be OK.
According to recent (React 16.3+) documentation, this is considered unsafe and in reply to your exact query:
Using this lifecycle method often leads to bugs and inconsistencies,
and for that reason it is going to be deprecated in the future.
If you need to perform a side effect (for example, data fetching or an
animation) in response to a change in props, use componentDidUpdate
lifecycle instead.
It's ok to make a call in OnWillReceiveProps but it's up to you to define a condition that makes sense, if your condition is well made you should not have a lot of call. Anyway you should in most of case avoid the use of OnWillReceiveProps which is kind of greedy in term of perfomance

Why we decouple actions and reducers in Flux/Redux architecture?

I've been using Flux first and Redux later for a very long time, and I do like them, and I see their benefits, but one question keeps popping in my mind is:
Why do we decouple actions and reducers and add extra indirections between the call that will express the intent of changing the state (action) and the actual way of changing the state (reducer), in such a way that is more difficult to provide static or runtime guaranties and error checking? Why not just use methods or functions that modify a state?
Methods or function will provide static guaranties (using Typescript or Flow) and runtime guaranties (method/function not found, etc), while an action not handled will raise no errors at all (either static or runtime), you'll just have to see that the expected behavior is not happening.
Let me exemplify it a little better with our Theoretical State Container (TSC):
It's super simple
Think of it as React Component's state interface (setState, this.state), without the rendering part.
So, the only thing you need is to trigger a re-render of your components when the state in our TSC changes and the possibility to change that state, which in our case will be plain methods that modify that state: fetchData , setError, setLoading, etc.
What I see is that the actions and the reducers are a decoupling of the dynamic or static dispatch of code, so instead of calling myStateContainer.doSomethingAndUpdateState(...) you call actions.doSomethingAndUpdateState(...), and you let the whole flux/redux machinery connect that action to the actual modification of the state. This whole thing also brings the necessity of thunks, sagas and other middleware to handle more complex actions, instead of using just regular javascript control flows.
The main problem is that this decoupling requires you to write a lot of stuff just to achieve that decoupling:
- the interface of the action creator functions (arguments)
- action types
- action payloads
- the shape of your state
- how you update your state
Compare this to our theoretical state container (TSC):
- the interface of your methods
- the shape of your state
- how you update your state
So what am I missing here? What are the benefits of this decoupling?
This is very similar to this other question: Redux actions/reducers vs. directly setting state
And let me explain why the most voted answer to that question does not answer either my or the original question:
- Actions/Reducers let you ask the questions Who and How? this can be done with the our TSC, it's just an implementation detail and has nothing to do with actions/reducers themselves.
- Actions/Reducers let you go back in time with your state: again this is a matter of implementation details of the state container and can be achieve with our TSC.
- Etc: state change orders, middleware, and anything that is currently achieved with actions/reducers can be achieved with our TSC, it's just a matter of the implementation of it.
Thanks a lot!
Fran
One of the main reasons is that constraining state changes to be done via actions allows you to treat all state changes as depending only on the action and previous state, which simplifies thinking about what is going on in each action. The architecture "traps" any kind of interaction with the "real world" into the action creator functions. Therefore, state changes can be treated as transactions.
In your Theoretical State Container, state changes can happen unpredictably at any time and activate all kinds of side effects, which would make them much harder to reason about, and bugs much harder to find. The Flux architecture forces state changes to be treated as a stream of discrete transactions.
Another reason is to constrain the data flow in the code to happen in only one direction. If we allow arbitrary unconstrained state modifications, we might get state changes causing more state changes causing more state changes... This is why it is an anti-pattern to dispatch actions in a reducer. We want to know where each action is coming from instead of creating cascades of actions.
Flux was created to solve a problem at Facebook: When some interface code was triggered, that could lead to a cascade of nearly unpredictable side-effects each causing each other. The Flux architecture makes this impossible by making every state transition a transaction and data flow one-directional.
But if the boilerplate needed in order to do this bothers you, you might be happy to know that your "Theoretical State Container" more or less exists, although it's a bit more complicated than your example. It's called MobX.
By the way, I think you're being a bit too optimistic with the whole "it's an implementation detail" thing. I think if you tried to actually implement time-travel debugging for your Theoretical State Container, what you would end up with would actually be pretty similar to Redux.

Resources