Skip to content
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

Discussion: Roadmap to 1.0 #82

Closed
8 of 12 tasks
markerikson opened this issue Jan 21, 2019 · 34 comments
Closed
8 of 12 tasks

Discussion: Roadmap to 1.0 #82

markerikson opened this issue Jan 21, 2019 · 34 comments

Comments

@markerikson
Copy link
Collaborator

markerikson commented Jan 21, 2019

I'd been thinking about putting up a discussion issue like this already, and someone asked about it today, so figure I might as well.

I deliberately started out at 0.1.0 to give us room to fiddle with the API before reaching stability. The "convert to TS" release put us at 0.4.0. So, what do we feel would be needed to reach a stable 1.0 status?

Here's my rough thoughts on further changes I'm considering:

  • Selectors
    • I'm considering dropping Selectorator, re-exporting createSelector from Reselect, adding a dependency on Re-reselect, and re-exporting its createCachedSelector
    • We might want to have a tiny wrapper around those that only accepts the array form of input selectors, not the arbitrary function arguments form Looking like Reselect v5 may do this as a breaking change
  • createSlice
    • I want to add the ability to handle other action types besides the ones generated for the provided reducers. Working on that now.
    • Would like to consider the "customize payload callback" idea
    • Change names of generated selectors (selectFoo instead of getFoo)?
    • Returning a default selector that's just state => state is pointless. Why are we doing that? What should we do instead?
  • createAction
    • add .type to the action creators in addition to .toString(). This helps with JS switch statements, as well as object lookup tables in TS.
  • Async requests
    • I'm open to some options for simplifying async request handling. Possibilities:
      • Generating "STARTED/SUCCESS/FAILURE" action types
      • Generating thunks that call a provided API function and dispatch those action types based on promise lifecycle
      • Pulling in redux-promise
  • More safety checks
    • I'd like to see if I can make a middleware that checks for side effects that happen in a reducer (at least async requests and uses of randomness) Skipping this idea
  • Other
    • Might as well add reduce-reducers to the toolkit
    • Should fix tree shaking
    • Allow devTools to be more options beyond a boolean
    • Review autodux to see if there's any other functionality we want to swipe
    • Consider adding a simple middleware that lets users define callbacks to run in response to specific actions (which thunks can't do)

Not saying we have to have all of these, but those are the ideas floating around my head.

Thoughts? Suggestions?

@denisw
Copy link
Contributor

denisw commented Jan 21, 2019

I'm considering dropping Selectorator, re-exporting createSelector from Reselect, adding a dependency on Re-reselect, and re-exporting its createCachedSelector

Dropping selectorator for reselect sounds great. 👍 No opinion on re-reselect, which I haven't used yet.

createSlice

I would consider dropping createSlice altogether - it feels not worth it. See my comment on #83.

add .type to the action creators in addition to .toString(). This helps with JS switch statements, as well as object lookup tables in TS.

Good idea! It also improves support for non-string action types (although these are currently not permitted by the TypeScript typings).

Generating thunks that call a provided API function and dispatch those action types based on promise lifecycle

I would generalise "API function" to just "function that returns a promise", but I guess that is what you meant? Like this:

const doAsyncStuff = createAsyncAction('doAsyncStuff', () => API.doAsyncStuff())

doAsyncStuff() // returns thunk
doAsyncStuff.started
doAsyncStuff.succeeded
doAsyncStuff.failed

I like the idea, and think this pattern is sufficiently widespread to be codified.

I'd like to see if I can make a middleware that checks for side effects that happen in a reducer (at least async requests and uses of randomness)

That would require some elaborate monkey-patching, right? I wonder if this is worth the strange unforeseen problems it might cause.

Allow devTools to be more options beyond a boolean

Perhaps we should re-export composeWithDevTools, but not apply it ourselves. I think right now it's impossible to do a build that doesn't include the devtools module in the bundle.

@markerikson
Copy link
Collaborator Author

@denisw: Thanks for taking the time to add your thoughts here and in #83, as well as the recent TS conversion. I really appreciate it.

Given that this is the "Roadmap" thread, I'm going to take this chance to write up my vision for RSK, and how that affects the included functionality. I'll add some specific responses to your comments after that.

@markerikson
Copy link
Collaborator Author

markerikson commented Jan 22, 2019

My Vision for Redux Starter Kit

TL;DR:

  • Make it easier to get started with Redux
  • Simplify common tasks
  • Opinionated defaults guiding towards "best practices"
  • Provide solutions to make people stop using the word "boilerplate"

Background

There are several factors that combine to make it relatively hard to use Redux in comparison to other options:

  • Redux deliberately adds a level of indirection to the idea of updating state, in the form of actions. As Dan said:

[Redux] is not designed to be the most performant, or the most concise way of writing mutations. Its focus is on making the code predictable.

  • Common conventions around Redux add additional levels of indirection and code that "needs" to be written (action creators, action type constants, selectors, thunks, switch statements, immutable updates).
  • There's also a lot of "rules" around Redux that are described in written form, but not obviously enforced by Redux itself, particularly immutability, serializability, and avoiding side effects.
  • JS is a mutable language, and writing correct immutable code is hard, both in terms of avoiding accidental mutations and the amount of code that has to be written to handle updates immutably
  • Redux was always meant to be a small core supporting a larger ecosystem of addons. This means that the core is deliberately tiny, and it's up to the user to choose how to configure the store. This is great for flexibility, but means that even the most common tasks require multiple semi-complex lines (such as adding thunk middleware for async logic, and also setting up the DevTools Extension). It also means the user has to make decisions early on about which addons they want to use, even in a simple app.
  • Functional programming concepts are foreign to most people and hard to grok, especially if you're coming from an OOP background

In addition, a lot of people really hate doing any more physical typing of characters than the absolute minimum they can get away with.

A couple years ago, I filed Redux issue #2295: Request for Discussion: Redux "boilerplate", learning curve, abstraction, and opinionatedness to discuss ways we could improve things. The thread wound up doing a lot of bikeshedding (naturally) and eventually devolved into people linking their own Redux abstraction libraries they'd created. However, the very first comment from @modernserf was pretty much on-point:

A few ideas:

  • official redux-preset package containing redux, react-redux, redux-thunk already wired together
  • dispatch(actionType, payload) could reduce need for action creators
  • built-in jumpstate-style reducer creators, e.g. createReducer({[actionType]: (state, payload) => state}, initState)

That was the initial genesis for Redux Starter Kit.

Objectives

Per the README, other specific inspirations are Create-React-App and Apollo-Boost. Both are packages that offer opinionated defaults and abstractions over a more complex and configurable set of tools, with the goal of providing an easy way to get started and be meaningfully productive without needing to know all the details or make complicated decisions up front. At the same time, they don't lock you in to only using the "default" options. They're useful for new learners who don't know all the options available or what the "right" choices are, but also for experienced devs who just want a simpler tool without going through all the setup every time.

I want Redux Starter Kit to be the same kind of thing, for Redux.

Speed Up Getting Started

Redux has a relatively steep learning curve. I don't think anything can ever completely change that. There's a lot of new terms, a bunch of moving pieces, and a different set of expectations that most people aren't used to. We can improve the docs and the teaching experience, but we can't remove all that. However, we can improve the process of actually trying to use Redux, whether it be for the first time or just setting up a new project. I want to give people a way to get working Redux running as fast as possible.

For new learners, part of this ties into the Redux docs revamp. I plan to have a page that says "You want to try out Redux? Here's a quick chunk of code. Copy-paste this (or open the CodeSandbox), follow it "blindly" for now, and go through the tutorials next to understand what's actually going on".

Similarly, for experienced devs, it should be really easy to add Redux to a React app using Redux Starter Kit. Add one entry to your package.json, and you've got all the basics you need to get going. No need to choose which side effects middleware you're going to use, or manually specify several other dependencies every time.

Simplify Common Use Cases and Remove Boilerplate

I want to remove as many potential pain points from the equation as possible:

  • Reduce the amount of actual physical characters that need to be typed wherever possible
  • Provide functions that do some of the most obvious things you'd normally do by hand
  • Look at how people are using Redux and want to use Redux, and offer "official" solutions for what they're doing already

People use the word "boilerplate" to refer to lots of different things, but mostly it's "writing more repeitive code by hand". The biggest examples are defining action types, writing action creators, and immutable update logic, and to some extent setting up the store. I want the user to be able to do those automatically if they choose to.

Handling async request lifecycles falls into this category as well. Most people follow the "action triplet" pattern laid out in the docs, and have thunks that fetch from an API and dispatch actions accordingly.

Opinionated Defaults for Best Practices

The Redux core is deliberately unopinionated, yet we have all kinds of rules that we try to enforce through documentation and "best practices" that we've collectively come up with.

Redux Starter Kit should set things up in ways that guide the user towards the right approach, and automatically prevent or detect+warn about common mistakes wherever possible. Specifically:

  • Store setup should add the DevTools Extension
  • Writing reducers that do proper immutable updates should be simple, and also make it hard to accidentally mutate
  • Hard-to-notice mistakes like accidental mutations, putting non-serializable values in state, and maybe even side effects in reducers should be caught and the user should be notified

No Lock-In / Add It Incrementally

If someone starts an app using Redux Starter Kit, I don't want to prevent them from choosing to do some parts of the code by hand, now or later down the road. Or, for that matter, pulling in other Redux addons of some kind, whether it be middleware or an action/reducer generation library.

On the flip side, someone should be able to add Redux Starter Kit to an existing Redux application, and begin using it incrementally, either by adding new logic or replacing existing logic incrementally.

They should also be able to pick and choose what pieces they want to use, and not be tied or required to use everything the library offers at once. If someone wants to remove thunks and use sagas instead, or they just want to use createReducer and nothing else, they should be able to just pull that in (and tree-shake the rest).

Become the "Obvious Default" Way to Use Redux

I still don't know how we're going to juggle teaching the basics of "this is how Redux really works", and "here's what this looks like using Redux Starter Kit". Most of RSK's benefit's only become apparent once you've tried writing some Redux code by hand and started wishing there was a shorter way to do something.

But, long-term, I'd like to see RSK become the "obvious default" way for most people to use Redux, in the same way that Create-React-App is the "obvious default" way to start a React project. Sure, there's plenty of other options out there (Gatbsy, NWB), and you can always do it by hand if you want to (Webpack + Babel configs), but it's the choice most people recommend and expect.

Don't Solve Every Use Case, and Stay Visibly "Typical Redux"

Redux is used in lots of different ways. Because of that, and because the core is so minimal, there's thousands of different Redux addon libraries for a wide spectrum of use cases. We can't solve all those ourselves, and we won't solve all those ourselves.

Some specific use cases this library will not address:

  • Full-blown querying of APIs and normalizing entity data. There's too much variety in how people handle data, including where it comes from, how they normalize it, how the loading status is tracked, etc. I know the NgRx folks have @ngrx/entity and ngrx-data that manage fetching, normalization, selectors, and everything else. I understand the desire to abstract away everything regarding handling "business entities", but that requires a ton more time and effort that I can put into this, and I don't want that much complexity involved. The closest I would even remotely consider is maybe including normalizr out of the box, and even that is more than I'd really prefer to do here. (Insert "blah blah GraphQL" comment here.)
  • Pre-built "encapsulated Redux modules / building blocks / plugins". Examples of this include redux-bundler, redux-box, and other libs listed here. Those libs have some neat ideas, and I do wish there was a more predictable way to say "here's a reusable chunk of Redux logic with reducer+actions+sagas+custom middleware, just plug it into the store and it works". Not trying to solve that problem here.
  • Higher-level "frameworks on top of Redux", like rematch or kea. Again, there's some neat ideas, but they're really starting to move away from what "typical" Redux usage looks like, and adding a lot more of their own complexity on top.
  • Code splitting. There's some people doing really powerful stuff with loading code-split reducers, sagas, and other logic at runtime, but that's also not an area I want to get into myself.
  • React-Redux-specific functionality. I want RSK to be useful no matter what UI layer you're using.

Reasonable TypeScript Support

I've recently become aware that apparently more people are using Redux with TypeScript than I had previously thought. Per a recent Twitter poll I ran, looks like it's around 40% or so.

I want to support people using TypeScript, same as I want to support anyone else using Redux in specific ways. So, RSK should be reasonably well typed.

At the same time, I don't want "perfect typing" to become the enemy of "good functionality". A majority of Redux users still use plain JS, and I don't want to get bogged down writing mile-long arcane type signatures or playing "whack-a-squiggly" when I know the code works right as-is. I'm fine with trying to shape things so they work well in TS, but I'm very willing to ship a feature that has less-than-ideal typing if that's what it takes to get it out the door. (Plus, I still barely know TS anyway, and while I hope to get more TS experience in the very near future, I need to be able to work on this lib myself.)

Rationales for Inclusion

With all of those thoughts in mind, I'll go through all the existing and proposed functionality, and justify why I'm making these decisions.

configureStore

  • Shortens the standard store setup logic. Even the recommended approach in the Configuring Your Store docs page is longer and more complex than what most people really want to do and should have to do.
  • Object parameter API makes it easier to read what the arguments are
  • Ensures the "common mistake validation" middleware are added to the store by default
  • Ensures the DevTools Extension is configured (which only happens if we call composeWithDevTools ourselves)

I would love it if configureStore could automatically enable HMR for reducers. Unfortunately, as far as I know, that's not possible because the module paths for module.hot.accept() have to be known statically at build time.

getDefaultMiddleware

  • Defines what the default middleware are in both dev and prod
  • Allows the user to not have to specify middleware unless they want to customize the list
  • If they do customize, allows them to still get the defaults included

createReducer

  • One of the biggest selling points for RSK: no switch statements, no spreads, just "mutate" your state and be done
  • Shorter reducers, no accidental mutations

createAction

  • Saves a couple lines when defining action creators. Not as big of a deal, but sure.

createSlice

Redux is really about reducers. Writing action types as constants and action creators isn't necessary. They're byproducts of several factors:

  • We don't want to write hardcoded action objects in components or the rest of the app
  • We typically split functions into different files based on the type of code
  • We want things defined in one place to reduce duplication.
  • Writing action types as constants means we get import/compiler errors if there's a problem, and can do "Find References"
  • Greppability matters, and we can search for places where an action type is referenced
  • The DevTools action log is meant to be semantically meaningful just based on the action types
  • If we write reducers and action creators in separate files, the constants need to be in their own file so we can import them both places. Even more so if we want different parts of our reducer logic to listen to the same action type.

Those are all perfectly valid reasons to do things the "traditional" way. But it's not necessary. It's totally legal to write this.props.dispatch({type : "ADD_TODO", text : "Buy milk"}) directly inline in a component.

But, writing action types and action creators by hand is one of the biggest pain points people point to. They just don't want to do that. (I'm fine doing that by hand myself, but I clearly am in a minority in that regard, and have something of a high tolerance for "boilerplate" in the first place.)

So, the point of createSlice is to let the user focus on the part of Redux that actually matters - the reducer function - and automatically generate the actions and types so that they don't have to.

The existence of createSlice does not preclude people from doing all this stuff by hand. If someone wants to write their own action creators and types, nothing's stopping them, same as nothing's ever stopped people from writing their own action/reducer generation libs. There's hundreds of those already, so clearly there's a demand for this kind of thing.

I'll certainly say that using createSlice loses a good portion of the greppability aspect. I think that's an acceptable tradeoff for shortening the amount of code needed, and it's always a choice the user has opted into. If they want to grep their action types, they can write things by hand instead of using createSlice, same as always. (In some ways this might even be better for "Find References" usage, because you're tracing the action creator right back to where it was destructured out of the createSlice return value, and there's the reducer right there.)

I agree with @denisw 's comments in #83 that:

  • to some extent createSlice does edge kinda close to the "Redux modules" concept
  • some of the TS behavior is probably a bit shaky
  • the current selector generation logic is somewhere between silly and pointless
  • this doesn't account for anything besides actions and reducers.

I'd like to fix the generated selectors aspect (I think autodux actually walks the initial state value and generates selectors for each field, or something like that). But I'm fine with the "80% use case" behavior for TS typing and slice functionality. If someone wants better types, go look at a TS-specific lib like typesafe-actions. If they want to code-split their sagas, use redux-dynamic-modules or redux-dynostore.

I do want createSlice to support the obvious things you can do with a hand-written reducer, and that includes listening for action types that aren't defined by this slice.

I'm pretty sure autodux's implementation has a few more features that we don't have yet, and I'd up for swiping anything else we think would be beneficial here.

createSelector

The vast majority of Redux apps use either Reselect or one of the wrapper libs. It's obvious that we should bundle some form of Reselect in this package so users don't have to explicitly add it.

I initially chose Selectorator because it's basically a superset of Reselect. createSelector works just fine as if you were using Reselect directly, but you also get the ability to pass in string/array keypaths as "input selectors" if you want to. That cuts down on the need to write a lot of one-line top-level input selectors by hand, like state => state.firstLevel.

Given my own preferences, I'd still like to stick with Selectorator. But, a bunch of TS users have been pointing out that Selectorator itself is hard to type correctly because of how the input selectors can be defined as separate params or an array (plus the ordering of inputs first, output last), and that the string keypaths don't give you any type safety.

The other use case I'd like to potentially solve is selectors that memoize properly when used in multiple components. The classic example is a list that renders <ConnectedListItem itemId={123} />, and the list items are using a selector like selectItemById(state, ownProps.itemId). A standard Reselect selector won't memoize correctly, because it's being called with different IDs in succession. That necessitates increasingly complex code like the "factory function" form of mapState, plus "selector factories". It's just too much hassle.

The Re-reselect lib also wraps Reselect, but adds a createCachedSelector function that can automatically create selector instances internally and cache them based on extracted keys from the data you provide. I still haven't used it myself, but it seems like a potentially simpler alternative for people to use other than the "factory function" approach.

I don't want to ship both Selectorator and Re-reselect. So, I'm open to dropping Selectorator and switching to Re-reselect.

Since Re-reselect allows both multi-param and array input forms, the TS typings are absurdly complex. In the interest of simplifying things, I think we could write a small wrapper that only accepts the array form, and perhaps also uses some of TS's new "spread"-type features to maybe somehow keep that declaration simple.

redux-thunk

Thunks are simply the default choice for side effect with Redux. Period. They're shown in the docs, they're by far the most used, and they're the simplest in terms of setup and usage. Thunks were originally included directly in the store, but later moved out when midldeware were implemented.

I know some people complain that you can do too much with thunks, and I don't care. They're useful, they're baseline necessary functionality, and they're going to be included.

The one weak spot with thunks is that you can't respond to dispatched actions. I'd be open to including one other small middleware that just lets you define callbacks that should run for specific actions. There's dozens of them already, or we could write our own.

Dev Check Middleware

Mutation is by far the most common mistake made by Redux users. It's technically hypothetically theoretically possible that someone might want to mutate their state, but the goal here is to prevent common mistakes. Easy call to add redux-immutable-state-invariant here.

Serializability is less of a meaningful problem, but we do emphasize it as a rule you should follow. Most people I see making this mistake also don't even realize they're doing something "wrong". So, it's worth having a middleware that checks this too.

We can't prevent all side effects in reducers. How would you even detect that, say, a reducer is mutating a variable declared out of its scope? Maybe you could detect uses of localStorage or console.log() by stringifying the function and searching it, but those examples, while not ideal, aren't actually harmful (and let's face it, we all throw log statements into reducers when something isn't working right).

But, kicking off any kind of async logic for a reducer is definitely not how it's supposed to work, and I think it's technically feasible to write a middleware/enhancer that detects uses of async logic in a reducer using zone.js. This isn't an immediate priority and could even be pushed back past 1.0, but I think it's a "wrong" enough mistake that it's worth checking for as long as the implementation doesn't add too much overhead. (And no, I haven't tried to measure the overhead from the other dev middleware yet.)

(FWIW, a couple Twitter polls indicate people care most about selector type safety, and they're fine with dropping Selectorator.)

reduceReducers

It's tiny, it's simple, and we mention it a few times in the docs. Might as well throw it in.

Async Request Handling

The Redux docs bring up the notion of "async request status triplet actions" early on ("REQUEST_STARTED/SUCCESS/FAILURE"). I personally haven't really used this pattern myself (largely because our couple apps have had simplistic fetching behavior), and I feel this pattern contributes to complaints about "boilerplate". But, a Twitter discussion thread and poll indicate that plenty of people are using this pattern. So, it's worth including something to simplify both the generation of the status action types, and potentially the request lifecycle logic itself.

I'm open to some ideas on this one, but my main thought atm is to clone something like redux-thunk-actions, which is used like:

let myFetch = createActionThunk('MY_FETCH', () => api.fetch());

Final Thoughts

Okay, yeah, I know, Wall O' Text. A lot of that is stuff I've written in various places, but hopefully it clarifies what I want to do and why I want to do it.

I'm open to suggestions on exactly what gets implemented and how. I want actual feedback and suggestions from users, because that means this package is helping solve real problems.

But, at the same time, this package is my brainchild, and I know what I want to accomplish. I'll happily take feedback into account, but ultimately I'm going to put in the things I feel will make it easier for people to use Redux.

@markerikson
Copy link
Collaborator Author

I'm re-reading the monster "Redux abstractions" thread, and I came across this post from Dan:

If it were up to me I’d like to see this:

  • A Flow/TypeScript friendly subset that is easier to type than vanilla Redux.
  • Not emphasizing constants that much (just make using string literals safer).
  • Maintaining independence from React or other view libraries but making it easier to use existing bindings (e.g. providing mapStateToProps implementations).
  • Preserving Redux principles (serializable actions, time travel, and hot reloading should work, action log should make sense).
  • Supporting code splitting in a more straightforward way out of the box.
  • Encouraging colocation of reducers with selectors, and making them less awkward to write together (think reducer-selector bundles that are easy to write and compose).
  • Instead of colocating action creators with reducers, getting rid of action creators completely, and make many-to-many mapping of actions-to-reducers natural (rather than shy away from it like most libraries do).
  • Making sensible performance defaults so memoization via Reselect “just works” for common use cases without users writing that code.
  • Containing built-in helpers for indexing, normalization, collections, and optimistic updates.
  • Having built-in testable async flow support.

On top of core, of course, although it’s fine to brand it in as an official Redux thing.
Also, I won’t be writing this. But you can.

Overall, I think what I've laid out meshes fairly well with that list, minus the "built-in memoization" and "indexing and normalization" aspects.

@Dudeonyx
Copy link
Contributor

Dudeonyx commented Jan 22, 2019

@denisw I feel that createSlice() actually reduces the most boilerplate compared to the other utilities available, especially the createReducer() util, there is practically no difference between using it and the createNextState() re-export from immer.

This

const increment = createAction('INCREMENT');

const reducer = createReducer( 0, {
  [increment]: (state, payload) => state + payload
})

is not much different from

const increment = createAction('INCREMENT');

const reducer = createNextState((state, action) => {
  switch (action.type) {
    case increment.toString() :
      return state + action.payload
  }
}, 0 )

On the other hand createSlice automatically creates action creators.

My version of createSlice here automatically creates action creators with typed payloads and if the initialState is an object it creates additional selectors for each property which are all fully typed ( i.e no (state: any) => S). This means that in most cases you'll only need to create your own selectors for derived states.

@modernserf
Copy link

A few thoughts regarding slices:

  • Slices can create namespaced actions, but there's no way to just create namespaced actions. Maybe we want something like createActions('counter', ['increment', 'decrement'])?
  • Selectors depend more on how reducers are combined than how they're defined. Maybe RSK should wrap combineReducers to auto-generate memoized selectors?
  • A result of this is that a slice can't "know" where it lives in the reducer tree; it might be used in many places at once! However, we can assert that it lives in one-and-only-one part of the tree, and combineReducers can validate these assertions, and raise an error if a slice is in the wrong place.

@denisw
Copy link
Contributor

denisw commented Jan 22, 2019

@markerikson Thank you so much for putting down your thinking around redux-starter-kit so elaborately. I understand much better now what it should be, why it is how it is, and where it's headed.

I would like to writing an answer reflecting on the points you made, and add some more arguments against createSlice 😉. But it'll take a while. For now, I just wanted to let you know how much I appreciate the effort you put into your post. 🙂

@BTMPL
Copy link
Contributor

BTMPL commented Jan 22, 2019

I would also like to express love for the idea of createSlice - it's perfect "starting point" for everyone getting their first exposure to redux: "just use createSlice to make a small counter / todo example to get the feel of it" and then expand on it, or just throw it away and use all the other factory methods as your requirements grow.

Also, I'm in favor of including thunks into RSK; Promises - while they could be easier to get the "respond to action" behavior you mentioned - are harder for new developers to understand. We would still need a good API for the actions to support it easilly.

I like the idea of tripple-actions for data fetching and I'm using it in all my apps; it's really simple yet powerful and allows you to implement ongoing request monitoring via middlewares / reducers etc. which we rely on a lot.

"Allow devTools to be more options instead of boolean" - is there need for that? I'm even questioning if there's need to disable devtools for prod builds. If you know what you're doing, $r.props.store.getState() and $r.props.store.dispatch() are always there for you ;)

Re @modernserf "there's no way to create namespaced actions" - why not just createAction('namespace/action', payload), or am I missing something?

@modernserf
Copy link

@BTMPL Its not strictly necessary, its just a convenience function to create a bunch of action creators from a single function, without also needing to define a bunch of reducers. Maybe its totally unnecessary in practice?

@markerikson
Copy link
Collaborator Author

Let's move the discussion of exactly how createSlice should behave over to #91 .

@Wizyma
Copy link

Wizyma commented Feb 25, 2019

hey @markerikson !
What woud you think about an utils to test the reducers (with mutation testing)
something like :

const itTestsCreateReducer = (reducers, initState = {}) => {
  return testSet => {
    testSet.forEach(({ action, mutations }) => {
      mutations.forEach(({ props = {}, name, state }) => {
        it(`should test ${action} ${name}`, () => {
          expect(reducers(state || initState, { type: action, payload: { ...props } })).toMatchSnapshot();
        });
      });

      it('Initial state should match snapshot', () => {
        expect(initState).toMatchSnapshot();
      });
    });
  };
};

and it would be used like :

// some reducer file
const action = createAction('ACTION');
const initialState = { test: '' };

const reducer = createReducer(initialState, {
  [action]: (state, { payload: { value } }) => ({
    ...state,
    test: {
      ...state.test,
      test: value,
    },
  }),
});

describe('Test reducer', () => {
  const testReducer = itTestsCreateReducer(reducer, initialState);
    testReducer([
      {
        action,
        mutations: [
          {
            name: 'Test reducer',
            props: {
              value: '1',
            },
          },
        ],
      },
    ]);
});

Why ?
I think it would be nice to have this kind of thing in a starter kit that everyone share and can use to facilitate the testing phase !
And for people who get into redux they would have a built in functions to test they reducers

I could create a code sandbox for a live example if you think that this looks good for you !

@phryneas
Copy link
Member

One more idea for the Dev Check Middleware: Warn the user if they dispatch an event as a payload. This easily happen when they hava an action with a default payload and then attach it directly to an onClick handler, not realizing that then the event becomes the payload.
Also, this makes the devtools almost unusable when it happens.

@markerikson
Copy link
Collaborator Author

@phryneas : yeah, that's definitely a footgun, and one I've done a few times myself. I think that the serializability check middleware actually ends up warning/throwing about that one.

Also not sure if there's a better way to handle the behavior overall.

@tarekrached
Copy link

I'm curious what your thoughts are on updating the immer dep to 3.x or eventually 4.x with support for Map and Set (which I have a ton of interest in...)

@markerikson
Copy link
Collaborator Author

markerikson commented Aug 9, 2019

Totally fine with that idea. Didn't know newer versions were out.

Having said that, you really shouldn't put maps and sets into Redux, and in fact the serializability check middleware that RSK adds by default will yell at you if you do that.

@tarekrached
Copy link

Yeah, we've run into the serializability check before - I'm not totally clear on why I can put Dates into Redux but not Maps or Sets (but that's probably a discussion for a different forum).

@markerikson
Copy link
Collaborator Author

markerikson commented Aug 23, 2019

Can you put Dates into Redux? I mean, I know you can literally, I would have expected that the serializability check in RSK would have flagged those as not serializable.

edit

I said something here about Date.now() being a number, but that's because it does return a number. typeof(new Date()) // "object", so never mind.

@tarekrached
Copy link

Yep, you're right - the RSK serializability check flags Dates those as not serializable (I was befuddled because we're using date-fns, which until recently dealt with ISO-8601 date strings and Dates the same).

Anyway, thank you so much for all your work with RSK - the ergonomics are so nice, it's made me excited about using redux again.

@markerikson
Copy link
Collaborator Author

Just published https://github.com/reduxjs/redux-starter-kit/releases/tag/v0.7.0 , which drops Reselect and slice selectors and adds the ability to customize default middleware.

@markerikson
Copy link
Collaborator Author

I'm generally set on the createSlice changes described in this comment: #91 (comment)

  • Rename slice option to name, and make it required
  • Return the reducer function itself as the "slice object", with actions object attached

That would be released as v0.8.0.

Not sure how hard the code changes will be, but the docs will need some significant updates to match:

  • createSlice API ref needs to be updated
  • All those tutorial pages I just made need rewritten - not only will the text need updating, but the commit history of the linked example repos has to be redone

I can probably make all those changes myself, but I'd definitely appreciate some help.

PR #109 has the baseline changes for createSlice, but given the churn since it was opened, it would probably be easier to create a new PR. If @denisw is interested in redoing the PR, that'd be great. Otherwise, someone else can hopefully chip in.

Any volunteers to help out?

My goal would be to push out 0.8.0 within the next 2-3 weeks, and then see if there's any remaining changes from this list I feel are necessary before 1.0.

@phryneas
Copy link
Member

I have slacktime tomorrow. I can take a look, but no promises yet, because there's also https://globalclimatestrike.net/ in our city.

@deyan-a
Copy link

deyan-a commented Sep 19, 2019

@markerikson I'll be happy to take a look and see if I can help with this.

@markerikson
Copy link
Collaborator Author

markerikson commented Sep 19, 2019

Cool. I've created a v0.8-integration branch branch where we can integrate both the code and docs changes before merging them.

@phryneas
Copy link
Member

phryneas commented Sep 22, 2019

@markerikson I've opened a PR for the code/type changes and the changes directly in the documentation. This still leaves tutorials, examples and the youtube video open.

I also have a suggestion for another change:

Could we make the reducers available from the slice object?

I'd love to have something like

      const slice = createSlice({
        initialState: 1,
        name: 'test',
        reducers: {
          increment(state) {
            return state + 1;
          },
          duplicate(state) {
            return state * 2;
          },
          customLogicAndIncrementAndDuplicate(state) {
            const custom = state/3; // some additional logic
            return slice.reducers.duplicate(slice.reducers.increment(custom))
          }
        }
      });

And yes, I'm aware that that would be possible by not defining the reducers inline, but then auto inference of the draft type etc. would be lost.

@markerikson
Copy link
Collaborator Author

After further consideration, I've decided to drop the idea of returning the reducer function as the slice result.

@phryneas
Copy link
Member

Could we please have the build & tests run for multiple TS versions? Something like the last 4 or 5 minor releases should catch quite a few bugs.

@markerikson
Copy link
Collaborator Author

Sure! Added #216 .

@markerikson
Copy link
Collaborator Author

With 0.8 out the door, I think I'm satisfied for the API for 1.0. Anything else past this should be additive (reduceReducers, hypothetical async handling, an action listener middleware, etc), and thus could be post-1.0.

I'd like to see if we can get the TSDX build changes in #212 working and switch to that before putting out a couple 1.0 RCs or something.

Ideally, I'd like to have 1.0 out the door in the next week (ie, before ReactConf :) )

Any other concerns that need to be addressed?

@markerikson
Copy link
Collaborator Author

almost there... :)

@markerikson
Copy link
Collaborator Author

And 1.0 is out! Given that, I'm going to go ahead and close this issue.

We've already got #76 open to discuss hypothetical handling of promise lifecycle actions, and #78 covering fixing tree shaking.

I'll open up another issue to discuss a potential action listener middleware.

@yourfavorite
Copy link

Sorry if this is covered somewhere and I've missed it. I see support for re-reselect mentioned in this roadmap but didn't see it covered in the API or documentation. Has a solution for re-reselect type of functionality been covered somewhere or did it get dropped? Thanks!

@markerikson
Copy link
Collaborator Author

We dropped re-reselect prior to 1.0: #82 (comment)

Ultimately it was not a good option for TS usage at the time.

I could imagine that with today's TS capabilities, a tool like that might actually be doable in a typesafe way. I know I've seen some "typesafe keypath lookup" utils out there. Not sure there's enough real benefit at this point, though.

@yourfavorite
Copy link

Thanks Mark. That makes sense. The typescript tooling in RTK is really impressive. When you say "Not sure there's enough real benefit at this point", do you mean that there just isn't enough demand for a re-reselect feature within the toolkit?

@markerikson
Copy link
Collaborator Author

Oh wait, sorry, I misunderstood the question, and goofed up the change description.

What we originally had, and then dropped in 0.7, was https://github.com/planttheidea/selectorator - a Reselect wrapper for writing selectors as string keypaths ('a.b.c.d').

https://github.com/toomuchdesign/re-reselect is a package that also wraps Reselect, but does so to add a keyed item cache to selectors instead of having just a cache size of 1.

My mind mixed up the two, sorry!

So per the re-reselect thing:

Improving selector usage for the ecosystem as a whole is definitely something on my radar, but as you can see from that discussion thread, there's been very little follow-up interest from the rest of the community about improving Reselect. I still want to follow-up on that, but I'm juggling entirely too many things myself atm :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

10 participants