Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save achacttn/31353ec41bc0cf412091360ef634c268 to your computer and use it in GitHub Desktop.
Save achacttn/31353ec41bc0cf412091360ef634c268 to your computer and use it in GitHub Desktop.
Redux Archeology and Design Notes

Design Goals

reduxjs/redux#8 (comment)

The main goal of this library is to prove that Flux can be implemented in a way compatible with full hot reloading (and explore how this can be done). You can run the example code with npm start, change action creators or stores, and the new logic will kick in before you refresh.

https://github.com/reactjs/redux/blob/319377ca5faaf696670063869c8b5f7b3a974bec/README.md (2015-06-05):

Why another Flux framework?

Read The Evolution of Flux Frameworks for some context.

Design Goals

  • Hot reloading of everything.
  • A hook for the future devtools to "commit" a state, and replay actions on top of it during hot reload.
  • No createAction, createStores, wrapThisStuff. Your stuff is your stuff.
  • Super easy to test things in isolation without mocks.
  • I don't mind action constants. Seriously.
  • Keep Flux lingo. No cursors or observables in core.
  • Have I mentioned hot reloading yet?

https://github.com/reactjs/redux/blob/07cf1424cb5e7bb7284acb282c996690cfc6d8c5/README.md (2015-06-16):

Philosophy & Design Goals

  • You shouldn't need a book on functional programming to use Redux.
  • Everything (Stores, Action Creators, configuration) is hot reloadable.
  • Preserves the benefits of Flux, but adds other nice properties thanks to its functional nature.
  • Prevents some of the anti-patterns common in Flux code.
  • Works great in isomoprhic apps because it doesn't use singletons and the data can be rehydrated.
  • Doesn't care how you store your data: you may use JS objects, arrays, ImmutableJS, etc.
  • Under the hood, it keeps all yout data in a tree, but you don't need to think about it.
  • Lets you efficiently subscribe to finer-grained updates than individual Stores.
  • Provides hooks for powerful devtools (e.g. time travel, record/replay) to be implementable without user buy-in.
  • Provides extension points so it's easy to support promises or generate constants outside the core.
  • No wrapper calls in your stores and actions. Your stuff is your stuff.
  • It's super easy to test things in isolation without mocks.
  • You can use “flat” Stores, or compose and reuse Stores just like you compose Components.
  • The API surface area is minimal.
  • Have I mentioned hot reloading yet?

Milestones

Composing Reducers

reduxjs/redux#155 (comment)

Do you see that as a philosophical/pattern stance or as a technical one? In other words, is Redux somehow preventing write cursors or otherwise causing the developer to fall into the pit of success?

For example, if I created a single action set that took a path as a parameter and passed that around to my components, would I have implemented the same thing as a write cursor? Or would that somehow still be fundamentally different than a cursor?

That's a great question! You can totally do that.

What I'm saying is that cursors are very low-level API. Just like you can have a single React component for your whole application, you can have a single SET action and a single reducer that behaves akin to a cursor. In my experience it's not very practical but you can definitely do this.

One important difference is that your SET action will still flow through Redux's dispatcher which potentially allows us to implement things like time travel (#113) and logging/other middleware (#63) that stands between your action and the actual data. With a vanilla cursor approach, a framework just doesn't have the power to do something like that.

reduxjs/redux#176

This gist shows how reducers can be easily composed. But it's still manual composition. It's expressive, but there is some boilerplate for repetitive tasks. For example, you might want to implement things like optimistic updates, undo/redo or pagination scoped to specific parts of your state, but then you can't do it just once on the top—you need to repeat this logic in every relevant reducer.

Higher order functions to the rescue! In fact, we already have a higher-order reducer: composeStores (to be renamed composeReducers). It takes your app's reducers and returns a reducer that composes their state into a single object.

We can express more patterns with higher order reducers? Absolutely! In fact this may be the beginning of reusing logic in Flux because these higher order reducers don't need to know about specific actions in your app.

reduxjs/redux#140 (comment)

Idea for fans of cursors: showing how to implement a cursor-like functionality with Redux using a single reducer and a single SET action. Use case: you like cursors but want benefits of Redux (time travel etc). Also because it's a fun experiment.

reduxjs/redux#191 (comment)

Right now it's easy to forget a default case and have your reducer return nothing in some circumstances. There have been some WIP attempts to address it: #174. Here's the issue that spawned that PR: #173.

But let's take a moment to think about what composeStores really is. It's a shortcut right? It's a bit opinionated already: it assumes plain objects as the initial state. See #153.

Can we make it a bit more opinionated for convenience?

reduxjs/redux#191 (comment)

Here's something wrong with this change. People will start with composeStores but might wrap their reducers into community-provided helpers (e.g. DevTools, pagination utils, etc) later. If all of these community-provided have to special-case undefined, it's going to be a pain. It's also easy to forget about this when writing a utility, and to interpret undefined as a value. Everyone's going to have to write special branches to handle this case.

Therefore I propose, instead of interpreting undefined as a special value, narrow down the composeStores-compatible reducer contract. That is, throw when undefined is return.

reduxjs/redux#191 (comment)

If it's really a valid use case for you and a matter of principle, you can always use your own composeReducers with your custom behavior. I still think the pros of this change, especially making it friendly to the beginners, outweigh the cons.

reduxjs/redux#197 (comment)

I think we're actually doing the right thing here. First, we want to check the actual app's reducers—not the root one. Second, we don't want this to be a hard limitation—just something the default utilities help you with. You're free to use your own composeReducers that allows undefined for individual reducers, or even not use composeReducers at all.

Redux doesn't really care what your root value is like. I'd say it should only be enforced at composeReducers level, as our built-in utilities follow “make it easier for a beginner” approach.

Statements of Intent

reduxjs/redux#195 (comment)

The best API is often no API. The current proposals for middleware and higher-order stores have the tremendous benefit that they require no special treatment by the Redux core — they're just wrappers around dispatch() and createStore(), respectively. You can even use them today, before 1.0 is released. That's a huge win for extensibility and rapid innovation. We should favor patterns and conventions over rigid, privileged APIs.

reduxjs/redux#137 (comment)

The essential element is to preserve the “it's like Flux but better, don't worry” vibe. I don't want people to think it's similar to Reflux or something, which sounds like Flux but breaks some of its nice properties. I also don't want them to think they need to learn FP. As long as we can keep it so, I'm okay with this change.

https://hashnode.com/ama/with-redux-cisteui6p005gzx53fstg8t6l

Also, as I mentioned in a different response, "[the] reason the middleware API exists in the first place is because we explicitly did not want to prescribe a particular solution for async." My previous Flux library, Flummox, had what was essentially a promise middleware built in. It was convenient for some, but because it was built in, you couldn't change or opt-out of its behavior. With Redux, we knew that the community would come up with a multitude of better async solutions that whatever we could have built in ourselves.

https://hashnode.com/ama/with-redux-cisteui6p005gzx53fstg8t6l

We didn’t want to prescribe something like this in Redux itself because we know a lot of people are not comfortable with learning Rx operators to do basic async stuff. It’s beneficial when your async logic is complex, but we didn’t really want to force every Redux user to learn Rx, so we intentionally kept middleware more flexible.

reduxjs/redux#55

To continue supporting async actions, and to provide an extensibility point for external plugins and tools, we can provide some common action middleware, a helper for composing middleware, and documentation for how extension authors can easily create their own.

reduxjs/redux#57 (comment)

I agree it's not very clear from the README, but Redux makes a hard assumption that you never mutate the state. Mutating the states defeats the purpose of Redux, and it's easy to avoid mutating it, so it's in fact a net good that the mutating code breaks early.

reduxjs/redux#63 (comment)

We currently don't provide a convenient way to read arbitrary state from a React component. You could use Connector with a super broad select but that might become a perf issue. There are many cases when you want to read the state, but not be subscribed to it, and it's usually convenient to do inside action creator, because such “state reads” are usually performed for pagination, figuring out if the entity is already cached, etc—something that isn't really tied to a particular component, but to a particular action instead.

reduxjs/redux#99 (comment)

I'm pretty sure about “don’t put anything unserializable into store or actions”. It's one of the invariants that's been working really well for me (and allowed time travel, for example) and there need to be very compelling reasons to consider changing it.

reduxjs/redux#99 (comment)

This actually doesn't require to put promises in the store in the end, and I like this. The difference is that, at the end of the dispatch chain, raw actions don't contain any promises. Those are “collected” by the said middleware instead.

reduxjs/redux#110 (comment)

So long story short, nested dispatches in Redux aren't a problem like they are in other libraries, because our "dispatcher" is just a reduce operation.

reduxjs/redux#113 (comment)

Action middleware is about transforming a stream of raw actions (which could be functions, promises, observables, etc.) into proper action payloads that are ready to be reduced.

reduxjs/redux#166 (comment)

I don't like composing Stores because I want there to be only a single source of truth in the app. Either a “simple” Store (production), or a “powerful” Store that the time travely thing “dumbs down” so it looks like “simple” store to the app (development).

reduxjs/redux#153 (comment)

You should let stores define their own initial state using default parameter like @emmenko showed.

The initialState argument is only meant to be used for rehydrating state from server or localStorage.

reduxjs/redux#155 (comment)

What Redux does not give you is write cursors. This is a core design decision made for a reason.

Redux lets you manage your state using composition. Data never lives without a reducer ("store" in current docs) that manages that data. This way, if the data is wrong, it is always traceable who changed it. It is also always possible to trace which action changed the data.

With write cursors, you have no such guarantees. Many parts of code may reference the same path via cursor and update it if they want to.

This is similar to how React works. If you see a DOM node, you can trace which component owns it. If you remove the component, there will be no DOM node.

Redux provides similar guarantees for reducers and data. So indeed, it is "less powerful" than cursors in the same sense React's model is "less powerful" than jQuery DOM manipulation. But sometimes less power is actually a good thing.

reduxjs/redux#160 (comment)

Currently Redux lets you pass select prop to Connector to choose the slice of the global state you're interested in. It's totally possible to compose those select functions so, to avoid expensive re-renders, instead of passing the data, you can pass those functions down. Child components may define their own select taking this.props.selectRoot (or whatever parent calls it) into account, and can even pass those down too. Because the functions are not likely to change (in best case they're outside component, in worst case bound to some particular props) there's no cost to passing them down. Now, components closer to the leaf ones (where re-rendering is relatively cheap) are able to finally use Connector with whatever select function they want (which as I said above may be composed through several levels of components). The nice thing is, because Redux assumes data is always immutable (even if you use plain JS objects), it is able to shallowly compare the results of select function calls and determine whether a deep component needs to re-render.

reduxjs/redux#162 (comment)

We need to popularize the idea of higher-order reducers. These are reducers that take your existing reducers, but amend them with some stock functionality (pagination, todo/redo, etc).

Like HOCs, but for reducers :-)

reduxjs/redux#140 (comment)

I started working on a new version of the README. I think that target audience for the README is developers who aren't super familiar with functional programming, and are skeptical of yet another Flux library. So, the focus should be on explaining how Redux significantly improves on the traditional Flux architecture, but is still very familiar.

reduxjs/redux#215 (comment)

I agree it's a natural feature that most Redux apps will probably want to have, but once we put it into the core, everyone will start bikeshedding exactly how it should work, which is what happened for me with Flummox. We're trying to keep the core as minimal and flexible as possible so we can iterate quickly and allow others to build on top of it.

As @gaearon said once, (I can't remember where... probably Slack) we're aiming to be like the Koa of Flux libraries. Eventually, once the community is more mature, the plan is to maintain a collection of "blessed" plugins and extensions, possibly under a reduxjs GitHub organization.

reduxjs/redux#216 (comment)

Here's why I chose to write Redux instead of using NuclearJS:

  • I don't want a hard dependency on ImmutableJS
  • I want as little API as possible
  • I want to make it easy to jump off Redux when something better comes around

With Redux, I can use plain objects, arrays and whatnot for the state.

I tried hard to avoid APIs like createStore because they bind you to a particular implementation. Instead, for each entity (Reducer, Action Creator) I tried to find the minimal way to expose it without having any dependency on Redux whatsoever. The only code importing Redux and actually depending hard on it will be in your root component and the components that subscribe to it.

Oh, and of course I had these two other goals in mind that NuclearJS did not satisfy:

  • Everything must be hot reloadable
  • It should be possible to build time travel tools with reevaluation on reload, like in my talk

https://twitter.com/dan_abramov/status/618757338154037248

You probably saw @sebmarkbage's talk called “Minimal API surface area”: http://youtube.com/watch?v=4anAwXYqLG8 …. That's what we're trying to do in Redux.

We don't buy the extreme modularity argument. But we also don't want to build a framework when conventions are enough to spawn an ecosystem.

It's important to expose the right extension points so modularity empowers instead of crippling. We're not there yet, but we're close.

Like Flux, Redux is more of a pattern than a framework. Unlike Flux, it enables a richer ecosystem of tools.

reduxjs/redux#229

As part of 1.0 we can do some breaking changes we won't be able to do after 1.0. This includes bikeshedding on names.

I wanted this library to be a functional programming trojan horse for Flux people, which I think it succeeded at. Before we reach 1.0, we are able to drop some of the Flux naming baggage and find better names for whatever it does.

Traditionally dispatch was called this way because in Flux an action really is dispatched across multiple Stores. In Redux, however, functional composition means that there is only one root reducer at the top. Do we really dispatch?

reduxjs/redux#229 (comment)

I'm confident I don't want it to be value() or state().

I made mistakes with these kinds of APIs and I'm always for verbosity over confusion.

reduxjs/redux#230 (comment)

It's worth noting it's possible to do it the other way: move core to redux-core and have redux be a Swiss knife project. But I'm not sure it's right to give React preference given that people are starting to use it outside React (and indeed it's something I want to encourage).

reduxjs/redux#1360

This decision was taken because way too many people had problems with reducers not handling the actions. Turns out, usually it happened due to a typo when importing a constant in the reducer file:

import { INRCEMENT } from './actions'

Of course the matching case never hits, but beginners are often puzzled by this because typos can be hard to spot, and most bundlers don’t analyze dependencies to warn you about missing imports yet.

We felt this is a good enough reason to choose a single convention (in our case, the most popular one is the one that comes from Flux: type property). I understand not everyone is happy with the name, but you can’t please everyone.

The developer efficiency that we get from this tradeoff seems worthy to me.

reduxjs/redux#377

We've been bikeshedding on this for quite a while, and from the conversations I was in, I didn't find any better word than “actions”. A new term has to be significantly better to justify inventing yet another terminology.

Redux names comes from “Flux with reducers” and it makes sense for me to keep the Flux terminology, at least to aid the beginners.

https://twitter.com/dan_abramov/status/689641756258885632

I think async action creators are suboptimal. How to introduce declarative effects without alienating most developers is a tricky question.

Such API must avoid hardcore FP lingo, be easy to compose, rely on language features, solve real problems well, support powerful tooling.

https://twitter.com/dan_abramov/status/622568094939090944

So hard to write the new docs. Many different audiences to cater to.

Should make sense to: Flux beginners, FP people, FP people who don't get Flux, Flux people who don't get FP, normal JS people too

Flux people: “is this proper Flux?” FP people: “is this that weird thing called Flux?” Normal people: “why not Backbone”

https://twitter.com/dan_abramov/status/693204191934824450

We can’t choose. We are for everyone who wants to write stateful JS apps that behave more predictably. Not about skill level.

reduxjs/redux#1189

Dan talks about why combineReducers is opinionated and how state is meant to be initialized.

Action Semantics

reduxjs/redux#195 (comment)

Now what action you should dispatch when AC finishes buying pizzas?

You can dispatch BUY_PIZZA but then you get a semantic problem that reducers were told to perform BUY_PIZZA action but they do something else. It is like you would tell me to buy pizza but actually wanted me to count them.

Or you want to dispatch actions describing what they should do. But which one? Each is doing something else. If you choose to dispatch all of them INC_PIZZA, INC_COFFEE, INC_COLA, it requires that AC knows what each reducer is doing. Even more worse is that if you want to add another reducer which would do something else when pizza is bought you are breaking Single Responsibility Principle, because by adding reducer you have to update ActionCreator as well if it requires new action!

Or you can invent some action covering all cases, you will probably end up with something generic non-descriptive like DO_WHATEVER_YOU_WANT.

With event sourced mental model AC writes facts and doesn't care at all what happens next. AC simply log PIZZA_BOUGHT. First reducer sees PIZZA_BOUGHT and thinks, when pizza is bought I'm responsible to increase pizza count. Next one inc coffee and the next inc cola. Everything is correct and it makes sense even under semantic lenses.

And you can add how many new reducers you want doing whatever you wish when pizza is bought. You don't need to touch AC. You do so only to extend its logging capability when your reducers need more facts.

reduxjs/redux#140 (comment) (and preceding)

Past tense works well for facts which happened. But I was thinking about it yesterday and I think I switched my understanding of Flux.

As I understand:

Eventsourcing system is based on a log of facts which happened. Facts are logged after complex processing. Eventsourcing system process them synchronously and when it starts with state A and applies the log to it always ends with state B. Hence this system can be replayed.

Commandsourcing system is when I log a command before any processing happens. To replay such a system then processing of each command has to be deterministic. In today concurrent world with Service Oriented Architectures, microservices and such, it is impossible to build replayable commandsourcing system as a whole.

Flux:

Flux doesn't log commands (actions) before any processing happens but it logs commands after all non-deterministic processing is done. Subsequent commands (actions) processing is strictly synchronous and potentially replayable. Hence I don't have to log facts and I can log commands (actions).

With this understanding actions doesn't log fact what happened but actually what should happen.

The only issue is that different developers try to grasp Flux either as eventsourced app or general commandsourced app while it is a special case of a command sourced app. Flux can be a small part of my whole system which can afford to work as synchronous command sourced app. There still can be many other non-deterministic async services around, like WebApiTools.

reduxjs/redux#384

Tons of discussion on action semantics. Couple quotes:

Action - call to do something or a fact that something happened?

Answer on this question defines how smart should be reducer. This shapes whole app.

reduxjs/redux#891

Other Notes

reduxjs/redux#37 (comment)

The prescribed way of code organization in NuclearJS is to group all stores, actions and getters of the same domain in a module.

  • Modules expose a single public API, the index.js file. It is improper for an outside piece of code to require any file within the module except the index.js file.
  • Stores are registered lazily through the module's index.js. This may seem weird at first, but in NuclearJS stores are more of an implementation detail and not ever directly referenceable.
  • Data access to the module's store values is done entirely through the getters it exposes. This provides a decoupling between the store implementation and how the outside world references the state that a module manages. A getter is a contract between the outside world and the module that a particular piece of information is accessible. The evaluator of a getter does not care about the underlying store representation.

Not saying you should adopt 100% this route but really reinforcing the concept of organizing by domain and not by type.

reduxjs/redux#313

Concept: Redux actions are sent to the web server, over some channel, likely a websocket. The server can also send actions to the browser.

reduxjs/redux#775 (comment)

Redux is a generic framework that provides a balance of just enough structure and just enough flexibility. As such, it provides a platform for developers to build customized state management for their use-cases, while being able to reuse things like the graphical debugger or middleware. (Joe Savona)

Dan and Andrew's notes on "The Evolution of Flux Libraries" for Javascript Jabber, October 2015:

https://docs.google.com/document/d/13qMWQVnPp-hNV9FTqwkPDtsdjhQap3WcBF0lddl60ps/edit#heading=h.dpxnamcp7w8s

https://devchat.tv/js-jabber/181-jsj-the-evolution-of-flux-libraries-with-andrew-clark-and-dan-abramov

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment