Open otakustay opened 5 years ago
So, the approach I'm going to provide relies on my personnal experience, but tries to take every concern you have into account. More than happy to discuss any part of it.
I understand the appeal of using the useEffect
diffing system, and I will go for that. However, I would never EVER have it inside my poor List
. In my opinion, it should remain pure, taking only the results (maybe some other displaying configuration props, but not relevant here) as a prop. So, I would not consider your 2 first examples really separating view and logic.
Forked your 2nd example, you can follow along here https://codesandbox.io/s/826zymymkl
I move the useEffect
to the ListContainer
. I'm annoyed by eslint that I don't provide onLoadResult
to the deps, because I know its constant, but nevermind. And my List
is pure, preventing any renders of ListContainer
regarding the change or parameters to affect the render of List
.
Because you seem concerned with extra renders, in your switchFilter
action, I "batch" the change of the filter with the resetting of the page to 1.
That is pretty much all I would change. You will need to make ListAutoFetcher
more generic to apply it to others lists and stuff, but again, it's another topic.
In my opinion, the most important question is “As a state machine, the js environment changes because of signals from outside(from location, network io, screen interaction, etc...), or because of its own lifecycle(like renders)?”. When the effect is called, even the dom did not change.
When paging, we need extra filter state, because the api is designed so. When submitting filter form, we need extra page state,also because of the api spec. These are logic about data(business logic), not about the view.
So I vote 3rd solution, even if it is without our new super hooks.
By the way, the name ‘hook’ is so evil.
For a long time, names like this are always for user context, not for engine it self.
Why they are not named as beforeRender, afterRender...?
The longer-term idiomatic solution to this problem is Suspense, which would not rely on any of those things. I know it's not very satisfying since it's not something you can use today, but I just want to make it clear why it's somewhat awkward with all of these approaches. Neither of them natively matches the React paradigm very well.
In out team we encountered a explosive discussion on how we should handle the relationship of a fetch and its parameters, after searching in community I still find various solutions to this, I'd like to raise this discussion to find a best practive.
Background
Suppose we have a simple list view like:
Whenever user types keyword in textbox and clicks "Search" button, or they change the page number, we should fetch a new list from remote and render it in table.
We use redux to manage global state of this simple app, the store is structured as:
We developed a total of 3 solutions to demonstrate how the change of
filter
andpageIndex
should cause a fetch ofresults
.Use effect and separation of view and logic
This is the first demo: https://codesandbox.io/s/20x1m39w00
In this implementation we tried to:
useEffect
to trigger a fetch when any parameter changes.components/List
component.In my point of view, I like this solution best because:
components/List
does not receive any redundant props such asfilter
orpageIndex
.useEffect
.Still we have concerns about it:
filter
orpageIndex
does not dispatchFETCH_RESULTS
immediately, this cause a sync dispatch in effect which we previously avoided byno-set-state-did-update
rule.Use effect and params together
The second demo is much like the first one: https://codesandbox.io/s/54o1rjvyv4
The only change is we pass
filter
andpageIndex
tocomponents/List
, in this case we believe effect is a part of component so that every dependencies used to form an effect should be passed as prop.This solution gives a more clear view of what is used to fetch data in
components/List
, this is a highly adopted solution in community, however we're not sure this is recommended officially.Imperative action to fetch data
As opposed to previous, this is our third demo: https://codesandbox.io/s/p5yv48x97x
In this solution we changed our thought and implement the app in a more "redux way":
pageIndex
when "Search" button is clicked.getState()
function, aFETCH_RESULTS
action is dispatched.FETCH_RESULTS
action and updates corresponding parameter in global state.components/List
component, this component now is a pure presentational component, no lifecycle effect is involved.containers/App
container component.By doing these we eliminated the "chaining state update" issue, however it introduces several concerns:
loadResults
thunk could be more and more complex.getState
inredux-thunk
is not highly recommended in community, we found some articles stating that developers should avoid to use it in most cases.containers/App
container only to trigger a fetch on mount, theeuseEffect
take no dependencies andexhaustive-deps
rules complains about it, not paring mount and update is also a big uncomfortable point to us.Since we are not able to get a conclusion for a very long time, we decide to raise this issue for more discussion to find a better solution to these very common use cases.