-
Notifications
You must be signed in to change notification settings - Fork 4.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Replace hand-picked redux concepts with react hooks...? #26849
Comments
I think there's some missing context here. One of the important aspect of Another thing missing is the "store creator" and "data consumer" are two different persons in general. The store consumer needs to define the public APIs of its stores (actions and selectors) and the data consumer only uses useSelect and useDispatch to communicate with this public API (he doesn't need to know controls, resolvers..., that's implementation detail) How do you imagine a store creator defining his resolvers with that in mind? |
cc/ @jsnajdr b/c I hear you like |
I agree that the generator-based routines and controls built on top of the The concepts in these libraries are almost identical to redux-saga, another generator-based side-effect engine for Redux. I think it's not a coincidence that these libraries were both created around January 2016, at a time when Today, the most natural thing to do would be to write async functions. And in connection with Redux, dispatch and call them as thunks. Because what is the difference between a generator control function and a standard async function? The main difference is that generators use the Command pattern to do things. When a generator calls function *myControl() {
const param = yield select( 'my/store', 'getParam', 1 );
const response = yield apiFetch( '/rest/is/best', { param } );
return response.body;
} then the { type: '@@data/select', selector: 'getParam', params: [ 1 ] }
{ type: 'apiFetch', path: '/rest/is/best', query: { param: 1 } } Then it's up to the There is a layer of indirection between the author of the generator function and the actual execution. Do we need that extra indirection? I'd say that in vast majority of cases, we don't need it at all. The only exception I can think of is maybe In Redux thunk language, slightly modified for import apiFetch from '@wordpress/api-fetch';
const myControl = () => async ( { select, dispatch } ) => {
const param = select( 'my/store' ).getParam( 1 );
const response = await apiFetch( '/rest/is/best', { param } );
return response.body;
} Such a control/thunk can then be dispatched to store either directly: store.dispatch( myControl() ); or using the React bindings: const dispatch = useDispatch();
dispatch( myControl() ); The dispatching process makes sure that the thunk is called with the right In conclusion, I agree that the generators could be entirely phased out without losing much. React bindings Then you can use Fetching data While the primary concern is to fetch data from some resource, More modern and ergonomic libraries have been developed lately, like
One thing I don't need to implement is the cache and its I think we should use these libraries as inspiration. I believe it can be implemented as a thin query layer over existing "Real" application state The "manage complex state in memory" and "fetch data from network and cache them" use cases are very different from each other. They could be very easily implemented with two completely different libraries. And if you use the latest "Autumn 2020 edition" of React best practices and libraries, they are. It's a bit unfortunate that we use the same To conclude, I think removing the |
@jsnajdr Just want to state that this comment pretty much sums up all my feelings about |
I realize that I didn't share my sentiment earlier, so I wanted to add to the discussion here and what I think of the current state of
Things I disagree with in the comments above:
these libraries shine where you're in a "read-heavy" environment and when you're a very limited number of mutations. While that's true for a lot of applications, our main one is very different from that: the editor. We need a layer that fetches data, allow edits to data, support dirty checking, and undo/redo ... from completely unrelated components. This is something these solutions are just not good for. It doesn't mean we can get inspired from some of their implementation details to provide utilities for things like cache management.... Things I think the data module is very good at:
Potential improvements I'm exploring:
It's is still very early but this is exactly what this draft PR is about #26866 (don't look at the code much, it's still very experimental) but the interesting thing is that it implements exactly the ideas I share above while retaining backward compatibility and as you can see by the "performance" numbers on the PRs, it is still more performant than master even if I didn't refactor the store definitions (because it considers a store as an atom, useSelect discovers and subscribes to the stores it needs and not all stores) |
Excellent discussion! I agree hooks may not be the best tool for the job. What matters is that we find out what is. #26866 looks promising, I'm curious to see how it will unfold. Atom-based stores have the potential to remove a ton of crust from the mental model and the codebase. Plus, as Riad mentioned, components would only get notified about the specific updates they're interested in. 🤞 |
@youknowriad What is your sentiment about continuing to use
Let me offer a different perspective: the actual block editor and its interaction with REST API, the differential edits, dirty checking, autosaves, is and will always be an unique exception. Not amenable to any pre-packaged framework-y solution. Just about everything else, even in Gutenberg, like working with post types, categories, block directory etc., is different: it's mostly reading lists of things, optionally filtered, sorted and paginated, and performing basic CRUD operations on them. Expanding further to wp-admin (or Calypso as a good blueprint for a wp-admin-ish app), we can see even more of REST CRUD with sorting, filtering and pagination that is very simple and un-sophisticated. For all that, a fetching library with awesome devex would be very valuable. And it doesn't hurt much if it doesn't scale beyond certain complexity. |
@jsnajdr I was considering starting a PR to explore removing these dependencies, but then I took a sneak peek of the code in #26866 and it seems like it could replace redux entirely, I believe this includes rungen and redux-routine as well. |
@jsnajdr are you hinting at using two different lines of libraries for these two use-cases? As in the specialized solution for the block editor, and a |
It could be more like using two layers, low-level and high-level, of the same stack. Most components would use the high-level, It's like writing UI in React vs manipulating DOM directly. Very often we do both at the same time, in the same component. And when done well, both approaches complement each other rather than being in conflict. |
just like @adamziel shared, I think these are essential building blocks on how the current async stores works, so we might not be able to remove this any time soon (third-party usage) but it doesn't mean we shouldn't explore simpler ways to do that. My PR (atomic stores) replaces all of that: redux + rungen + redux-routine with one low level library (called @wordpress/stan for now). If it proves to be successful, we could deprecate one in favor of the other.
I agree that the block editor is an exception but it's our main project here so this needs solving anyway. I agree that useSWR and the like have great DevX for component authors but I believe right now our devxp for creating the stores is bad right now but for consuming them, it's not that bad. Writing |
Could we do away with most redux concepts that we use today, and instead rely on React hooks?
This is an audacious proposal directly building up on findings from 26692. Not much thought given to cost/benefit analysis yet but the amount of work involved is potentially huge. Even if this ends up not being pursued, having a discussion would be beneficial anyway. Let's talk!
Our redux actions and selectors have complex needs such as invoking side-effects or actions and selectors from other stores. To enable these interactions, we have concepts like:
At the same time, we do all this things in React components using:
I wonder - what are the advantages of using the former over the latter? These ideas seem to be interchangeable, only using both means more code paths to maintain plus twice the learning and a higher barrier of entry. Let’s see how React hooks could replace the existing concepts side by side:
Actions with yield
These two seem to be equivalent and interchangeable:
Only with the latter one doesn’t need to know about generators, controls, or dispatching actions inline.
Controls
Similarly, controls seems to exist just to handle the async behavior and grant access to different stores. Consider the
yield controls.select( 'core', 'getUndoEdit' );
part of the undo action above. This is the underlying select control:With hooks, this control wouldn’t be required at all - its role would be fulfilled by
useSelect()
. The same goes forcontrols.dispatch
.Async controls
How about async controls, for example
apiFetch
? I find this:To be the same as this:
In this case
yield
turned out to really be just a reimplementation ofawait
- we „hack” generators to simulate async behavior. Actions invoke other actions that are really controls, a middleware (?) calls the control handler, and then the handler waits for the actual promise. Why not remove the indirection andawait
for the promise directly?Registry selectors
This registry control:
Seems to be just this hook in disguise:
Resolvers
Resolvers are particularly interesting. I didn’t quite figure out all the code paths that are running in order to make them work just yet, but it seems like this controls plays an important role:
I imagine that without controls, a canonical
useSelect
hook could be used to explicitly trigger the resolution when needed.Public API
One downside of react hooks is that one could no longer call
wp.data.select( 'x' ).myFunc()
as easily. Or is it true? This isn't fully fleshed out yet, but I imagine exploring "bridge layer" could yield a solution:Other considerations
@wordpress/redux-routine
would go away entirely.Anything else?
Am I missing anything? Is there anything that makes this a blunder or a technical impossibility? Really curious to learn what does everyone think. cc @noisysocks @draganescu @youknowriad @mtias @talldan @tellthemachines @gziolo @nerrad @jorgefilipecosta @ellatrix @kevin940726 @azaozz @aduth @nosolosw
The text was updated successfully, but these errors were encountered: