useEffect is a side effect and if you use it you're gonna have a bad time.
useEffect is a side effect and if you use it you're gonna have a bad time.
Right.. but then you can't really use React for building serious UI.
But it’s a powerful escape hatch like rust’s `unsafe` block. Rust can do amazing things to ensure safety of your program, but there’s still times where a human can do it better.
By signposting that, keeping it to limited blocks, and wrapping it up in a safe function you get the capability to do hard things, while exposing a safer API.
useEffect is similar, it lets you write the imperative synchronisation logic. For example if you need to interact with a DOM element that only has an imperative API, someone needs to do the work somewhere to wrap it in a declarative one. You can do that in a component with a useEffect hook, and then expose a declarative API.
Because at some point your function call is manipulating registers on the CPU. If we go even further, the CPU needs to load its next instructions and then the data required.
Point being, we abstract and encapsulate complexity and provide a simpler API around it. It’s the only feasible way to manage the inherent complexity.
React builds up a virtual description of UI. react-dom and react native take that description and synchronise it to the UI layer.
If you need to do something extra, that they don’t handle like managing a map widget (ie mapbox) in React that only has an imperative API, it sure would be nice to do that yourself.
You can build a UI without useEffect. You can hide messy implementation details inside a well tested component, and contain where useEffect is being used.
And, you can understand how useEffect works so that you can use it without it having a negative effect on you.
and
"You can ... contain where useEffect is being used."
are not the same statement. Maybe I'm being pedantic, but when I read "build without X", I think there is some method that can be used to avoid X, not that I can hide away X.
All declarative code eventually runs something imperative.
And anything non-theoretical is going to have side effects somewhere.
Painting to a screen is a side effect.
Reading a file is a side effect.
Software gets messy when it intersects reality.
But, we can do quite a lot by staying in a theoretical realm. You can build a whole UI in the theoretical realm, where no side effects exist.
When you actually need to run it though, you drop out the the theoretical and into reality. Now there’s side effect’s everywhere.
So you contain them. Encapsulate those side effects into composable blocks. Test them extensively. Offload that work to someone who’s just focussed on managing them.
Updating the DOM is a side effect. Making a network request is a side effect.
Contain them, test them, and then you can stop thinking about them.
If someone else has done all the work to contain side effects, you can build a UI without them.
Why would fetching data depend on whatever is being rendered?
You know what data you need or not need before rendering anything, or you will be in a world of pain.
- Click the link to show top scorers
- Initiate fetching the data
- (The view may change to indicate you're loading the top scorers, if you want, that's a different matter).
- Receive the data, the view changes to display the data
This is opposed to:
- Click the link to show top scorers
- View changes to show top scorers component. A component loads, or mounts, or inits, or does something which triggers a data fetch
- Receive the data, the view changes to display the data
Your app "knows" you need the data when the user clicks the link, no need to involve the view components into this.
This gives you the ability of adding a lot of complexity to how you fetch the data that is best handled outside a UI component (like caching, reusing it in other components, error handling, retrying, throtling, whatever), while saving you from a whole class of problems where a misunderstood component lifecycle has consequences for your data. Frameworks like React or Angular are going to try to help you by caching outputs, reusing components, dropping updates for the same frame, prerendering, preloading or anything really. If you fetch data when a component does any of that, suddenly you have to care about it and fully understand it. The abstraction leaked. If you treat the component as close to a pure function as you can, things become much simpler.
This applies to relatively big, relatively complicated apps, controlled by a single entity, where it pays off to prioritize simplicity.
- how is the data fetch completion signalled to the component?
- how is the data piped into the component?
Would be happy to be pointed to some example code if you have.
Both have the same answer. When you fetch the data you put it in the state, and then you render your components based only on that state.
If we're talking React the state makes it into the component through the props (pure UI component) or, if using Redux or similar, some kind of hook that triggers a rerender when the store changes (useSelector? My React-Redux is a bit rusty).
The point is not to worry anymore about change detection and rendering, because the framework takes care of it by being either fast enough that it can run on every change, or smart enough that it can tell when it's not necessary to re-render (caching, change detection).
Of course, this setup with redux etc makes a lot of sense for bigger apps with lots of state coming from different sources. I don't have any specific example I can point to, but any redux implementation examples could help. I use NgRx with Angular, and the docs are OK at explaining the flow of data.