trustroots icon indicating copy to clipboard operation
trustroots copied to clipboard

Explore options for react data layer

Open nicksellen opened this issue 4 years ago • 10 comments

As we migrate more to react (https://github.com/Trustroots/trustroots/issues/1334) we get more and more need to do more than just render components.

Our current approaches have been:

  • create new API modules for API requests
  • useState / useEffect for component state management
  • reuse angular controller functions by passing function references in route definition (e.g. <my-new-react-component do-something="oldAngularController.someMethod">
  • use angular-compat utility functions to bridge to angular services/etc
  • sometimes reimplement functionality outside of react components in standalone files (e.g. in https://github.com/Trustroots/trustroots/pull/1370#issuecomment-616465433)

But increasingly the question arises, how does data flow look when we are full react based, so far we postponed this question, but now is the time to start exploring the options!

It might be it touches on routing, and other application architecture, but primarily this issue should be concerned with just the data layer.

Describe the solution you'd like

For this issue, it's more a meta issue, to explore the options.

So I'd like to see some nice links to good articles about this topic. Contrasting different approaches and libraries and the various tradeoffs. Maybe also some playground projects to see how they feel when we try and use them, or some exploratory branches to try out some concepts.

Something like the exploration for testing frameworks could be along the right lines.

There is a probably a strong leaning towards redux-based solutions, as that seems a defacto standard, but it's good to explore beyond that too. It's not just about choosing an approach, but exploring together, and making sense together, learning together, and sharing our knowledge, experience, and process...

nicksellen avatar Apr 21 '20 11:04 nicksellen

I would be interested in this and how it could relate to mobile, perhaps with some idea for future code sharing. I have experience with two solutions, I believe these are also the two most common ones to find in React Codebases:

Redux I've used this multiple times over the years, very familiar with it. The team has done a lot of work on trying to make it work with less boilerplate by releasing redux-toolkit https://github.com/reduxjs/redux-toolkit.

Mobx This one I only used once on a project and have less experience with. It was more approachable, more opinionated and more pragmatic (you can change state by just mutating an object and freely perform side effects rather than sending messages like redux). Lends itself to mvvm type setups.

I tend to lean towards redux in my own projects but maybe due to familiarity more than anything else. I find it's restrictions open up some nice tooling and it lends itself to very thin views. Mobx might be quicker to work with and onboard people however, especially coming from other frameworks and platforms.

theolampert avatar Apr 21 '20 11:04 theolampert

I work so much just with WP abstraction layer over Redux that I've lost little bit base on what different options are out there beside Redux. 😅

Some ideas aroudn this might be intersteding read for you: https://github.com/WordPress/gutenberg/tree/master/packages/data

simison avatar Apr 21 '20 11:04 simison

I'll also summarise a bit from this slack thread where we talked about state management.

There is some pushback against global state, keeping state closer to component, especially with react hooks. Another approach is using useContext. This article from Kent C Dodds explains a lot about this. He says:

  • we had the prop-drilling problem
  • redux solved it, so lots of people used redux
  • ... but redux also added a lot of state management complexity
  • useContext addresses prop-drilling issue
  • truly global state can go into redux still
  • don't need all state to be global, state should be kept as close as possible to where it's needed
  • context approach supports multiple providers too, so things can get just the state they need

Dan Abramov (creator of redux, and somebody who just utters a word or two and everybody re-architects their whole app) also said:

I generally recommend putting state closer to where it's being used. If you must hoist it up, I think useReducer + useContext is a decent combo.

https://mobile.twitter.com/dan_abramov/status/1162369357718204416

https://github.com/jamiebuilds/unstated-next also goes in this direction and looks interesting, but is maybe not standard enough for our liking... whereas the context stuff described above is built-in to react already.

Link from @simison https://github.com/Automattic/wp-calypso/pull/38503#pullrequestreview-334555255 talks about redux and expresses preference for splitting things into multiple stores (even though redux apparently discourages this). With the risk of when you need to co-ordinate things across multiple stores. I think this general concept fits well with the other points above too.

@mrkvon and @theolampert point out https://redux-toolkit.js.org is available now, which encapsulates al the redux best-practise stuff in a batteries-included, opinionated way. We tended towards tools that take this approach (e.g. prettier), so maybe it's a good fit.

It bundles in the immer library which is quite a big departure from previous react stuff I did, so if we go this route I think we really need to consciously go down this path.

The createSlice API seems to be the high level thing to use, and it struck me how much it felt like a vuex store (from Vue world, what we use in Karrot).

Also, for comparison, in Karrot, I think we are suffering from some of the potential problems described above (from putting everything in the global store, and having a lot of complex interactions between store modules).

nicksellen avatar May 11 '20 09:05 nicksellen

The other key point is that any solution should be compatible with react native. I kind of assume they are all, but maybe there are some specific considerations there. I wonder if @theolampert has some specific considerations on that point?

nicksellen avatar May 11 '20 09:05 nicksellen

.... and one more comment for now! I think a good next step would be whipping up some experimental PRs to see how some of the approaches feel in the codebase.

I could see a few possible things to evaluate/play with:

  1. find some truly global state (e.g. logged in user), and putting that in a redux toolkit slice setup
  2. find a non global bit of state that is used in multiple places and see how it works with one of these approaches (e.g. unread messages counter, photo credits)
  3. find some essentially local state (e.g. messages within a single message thread) and see if any of these approaches seem useful/applicable

... where it needs to also work with existing angular parts of the app is an interesting challenge too, initial thoughts on two different approaches would be:

  1. single new state location for both angular and react parts - expose it to angular via some shim/service/whatever
  2. have two state locations, existing angular stuff, plus new react location - update both when state changes

(I'm more partial to 1 here)

nicksellen avatar May 11 '20 10:05 nicksellen

opinionated way. We tended towards tools that take this approach (e.g. prettier), so maybe it's a good fit.

Choice to use Angular.js was partly due to this too, since the opinionated nature can take away the guesswork in volunteer projects where people come and go and experiences/learned patterns/opinions can differ quite a bit. It was always important to me to try stay as consistent as possible to make diving into huge codebase easier. Common vs exotic tooling is another aspect that makes things easier, which would bring us to Redux even if there's something "better" from absolute technical point of view.

simison avatar May 11 '20 10:05 simison

Convo from Slack about Recoil:

This went out: https://recoiljs.org/ Facebook's answer to state management

one part sigh (yet another new thing...), and https://github.com/facebookexperimental/Recoil says it's experimental. they have too many researchs to create churn imo, hard to keep up. having said that the premise sounds nice.

Just to check if I have my bearings, is this the same space that MobX is in?

exactly, @theo mentioned mobx on the github issue for this topic ---> https://github.com/Trustroots/trustroots/issues/1416#issuecomment-617131462 I never tried it myself

Ah yeah, redux as well

simison avatar May 20 '20 16:05 simison

A couple more lightweight solutions that might be more fitting for us compared to full blown Redux setup:

https://github.com/tannerlinsley/react-query https://github.com/pmndrs/zustand

simison avatar Jan 16 '21 12:01 simison

I've recently done more work with React Query and pretty convinced it's best solution for us.

It can be much like what we have now with simple Fetch/Axios, but with additional features like continuous polling, caching, pagination, etc. It's very simple, and very intuitive to use, yet offers just enough bells and whistles over basic fetch implementations. No monolithic global state, which is what I probably like the most about it. :-)

simison avatar Oct 19 '21 13:10 simison

My vote would be to use the React Context API directly. It's what Unstated uses under the hood, but you can roll it yourself and get much cleaner usage in your components than you can with unstated. I say this having switched from Unstated.

An example provider from a project of mine is this User provider, which you can put at the top-level of a relevant route.

<WithUser>
  <MySubTree/>
</WithUser>

Then within any child component, you can snag whatever properties you want using useContext(). e.g. this other component where it grabs the logged-in user

I think this being a core API of React is a strong argument for using it to manage shared state. In some sense it's the official answer to the question from the React team.

Because each of these provider components is just a component you can add whatever other features you want, and expose whatever other variables you want, whether that's errors, loading states, more related data, etc.

karlkeefer avatar Nov 04 '21 18:11 karlkeefer