4 ms·
I never understood why a lot of this was never handled with metaprogramming. I feel like it might have something to do with Redux using constants and imports as
by fny 10y ago
I never understood why a lot of this was never handled with metaprogramming. I feel like it might have something to do with Redux using constants and imports as a type safety hack, but all the ceremony drives me nuts.
First we enumerate our actions as constants...
const ADD_TODO = 'ADD_TODO'
Then we need to create an "action creator" somewhere:
import { ADD_TODO } from '../actionTypes'
function addTodo(text) {
return {
type: ADD_TODO,
text
}
}
Then we need to import that action and dispatch it when we need it...
import addTodo from './actions'
dispatch(addTodo(text))
Now even the same nonsense for reducers...
- danielearwicker 10y agoI developed a kind of meta programming pattern and supporting library, based on Redux, called Immuto, to get absolute and complete static typing (via TypeScript) and minimal boilerplate, also referentiality and modularity (cursors that accepted actions). It was fun, but ultimately pointless, as it was never as straightforward as using the automatic observable approach that I'd learned from Knockout.js. Then I heard about MobX (which is the same ideas from KO but better implemented and without any quirky UI templating thrown in) and so now I used that. Also I use my json-mobx library to get serialisation and undo/redo. Example: https://github.com/danielearwicker/baltar https://github.com/danielearwicker/baltar
- stevejohnson 10y agoSpeaking as someone working in a large Redux codebase, I agree with you 100%. The amount of boilerplate required to do almost anything is staggering.
- acemarke 10y agoDan Abramov has said that the Redux docs were written in a deliberately verbose style to get across the ideas, and he didn't really expect people to strictly imitate that style. There's a wide variety of approaches and utilities you can use to abstract things as much as you'd like - that's entirely up to you.
- antoaravinth 10y agoAs a beginner in Redux, can you tell me about the other ways to handle these boilerplate?
- acemarke 9y agoA few possible options: - Put all the action types, action types, and reducers for a given portion of the application into one file. This is known as the "ducks" structure, per https://github.com/erikras/ducks-modular-redux https://github.com/erikras/ducks-modular-redux . - Use a utility library like https://github.com/acdlite/redux-actions https://github.com/acdlite/redux-actions to generate action types and action creators for you - Write middleware to manage common logic for your application such as API calls - Skip writing action creators and declaring action constants, and just have components do `this.props.dispatch({type : "SOME_ACTION"})`. I don't recommend the last approach, but it's _totally_ up to you, especially because what you regard as "too much boilerplate" is going to be a personal opinion. One person's "too much boilerplate" is another person's "explicit and easy to trace through the system".
- jdeal 10y agoPersonally, I find that when boilerplate gets out of control, you can do one of two things, probably in this order: 1. Write helpers. 2. Write middleware. The first is zero magic, and allows you to opt-in to abstractions as needed. The latter is more powerful and can remove pretty much any boilerplate, but you have to be careful about introducing too much magic. Redux isn't much different than any other code you might write. If you prematurely abstract, you could create a leaky abstraction, and you'll have to unroll the whole thing. If you don't abstract at all, and copy-pasta everything, you're also going to have a bad time.
- acemarke 10y agoAnd most of that came from Flux to begin with (as seen in https://facebook.github.io/react/blog/2014/07/30/flux-actions-and-the-dispatcher.html#actions-and-actioncreators https://facebook.github.io/react/blog/2014/07/30/flux-action... ). There's also a lot of interesting parallels with the Win32 WndProc functions, as discussed in https://bitquabit.com/post/the-more-things-change/ https://bitquabit.com/post/the-more-things-change/ and https://bitquabit.com/post/the-more-things-change/ https://bitquabit.com/post/the-more-things-change/ . That said, you are absolutely encouraged to abstract over that process as much as you want! For example, the https://github.com/acdlite/redux-actions https://github.com/acdlite/redux-actions library will generate both the action constants and the action creators for you, and there's many more similar utilities.
- acemarke 10y agoWhoops, just noticed the bad copy-and-paste. Meant to link to the HN discussion for that article: https://news.ycombinator.com/item?id=10381015 https://news.ycombinator.com/item?id=10381015 .
- chucksmash 10y agoI usually handle this via text editor macros when I'm starting off Start with actionNames.txt ADD_TODO REMOVE_TODO Cat that file to actionTypes.js and actions.js. In actionTypes.js, expand: <line> to: export const <line> = "<line>" And in actions.js, expand <line> to something like: export const (camelcase-string <line>) = () => { return { type: types.<line>, }; }; Also instead of importing actions and actionTypes individually, I'll generally do: import * as actions from './actions'; import * as types from './actionTypes'; dispatch(actions.addTodo(...));
- jamesisaac 10y agoI'd highly recommend using a type system like Flow to enumerate the shape of possible actions the store should support. This brings numerous advantages: 1. You can use strings for action types, no more need to import constants. 2. No need for action creators for simple actions, you can just create the action object inline and Flow will validate its shape. 3. You can be sure that reducers are unpacking properties that actually exist from the actions. In other words, your example could simply be: // types.js type Action = | { type: 'ADD_TODO', text: string } // TodoList.js dispatch({ type: 'ADD_TODO', text }) ...and Flow would confirm at 'compile time' that it matches the contract dispatch expects. This approach has scaled up extremely well for me and no longer feels verbose.
- nathan_f77 10y agoI agree with that. When I first started with Redux and Flow, I was just following all the examples and made sure I used action creators for everything, even if there was no payload. Now I'm feeling more confident with Flux, and I recently realized that no, I don't need "action creators" for everything. That's silly. It's OK to just dispatch an object using your action constant directly. And then I found myself writing action creators on purpose, not because I saw it in some tutorial, but because it actually does remove some duplication. This was also missing from my education for a long time: https://github.com/acdlite/flux-standard-action#actions https://github.com/acdlite/flux-standard-action#actions I had no idea why I was using a "payload" key, instead of just putting all the values in the top-level object. But this convention makes a lot of sense now.