reduxjs / redux-toolkit Goto Github PK
View Code? Open in Web Editor NEWThe official, opinionated, batteries-included toolset for efficient Redux development
Home Page: https://redux-toolkit.js.org
License: MIT License
The official, opinionated, batteries-included toolset for efficient Redux development
Home Page: https://redux-toolkit.js.org
License: MIT License
This package currently imports and re-exports selectorator without using it internally, while also exporting parts of the redux and immer packages that are used internally. In my opinion maintenance for both this package itself and dependent apps/packages would be easier if we removed our re-exports. This would include removing the dependency on selectorator, though in my opinion it's a very useful package and we should still recommend it in the same way in our readme.
npm install @acemarke/redux-starter-kit selectorator
. This is a common practice in documentation for popular npm packages.redux-starter-kit
includes redux-thunk
but doesn't include an async version of createAction
. I want to float the idea of a createAsyncAction
function that uses thunks; I can create a pull request if all looks good.
Here's a rough initial API proposal:
// To be added:
// (Inspired by the current implementation of `createAction`)
function createAsyncAction(type, thunk) {
const action = payload => thunk(payload);
action.toString = () => type;
return action;
}
// Usage example:
const someAsyncAction = createAsyncAction("SOME_ASYNC_ACTION", payload => {
return dispatch => {
return setTimeout(() => {
// One second later...
dispatch(someAsyncActionSuccess());
}, 1000);
};
});
const someAsyncActionSuccess = createAction("SOME_ASYNC_ACTION_SUCCESS");
The only new proposal is at the top of that snippet, the function createAsyncAction
. It takes two parameters: type (same as createAction
) and thunk
.
What are everyone's thoughts?
Addition of some handy quick start instructions are needed the the quick start guide of the documentation.
JSON.stringify(null)
or JSON.stringify({ value: null })
work perfectly fine
Addition of some handy quick start instructions are needed in the quick start guide.
When I use redux-starter-kit with webpack in production mode, keep telling me this:
This means that you are running a slower development build of Redux. You can use loose-envify (https://github.com/zertosh/loose-envify) for browserify or setting mode to production in webpack (https://webpack.js.org/concepts/mode/) to ensure you have the correct code for your production build.
I didn't find any file names like "*.min.js" under /node_modules/redux-starter-kit/dist/
I'm trying to integrate RSK in a minimal Typescript project, but i stumbled upon what looks like a typing conflict :
Apparently configureStore.reducer expects to use AnyAction
interface while createSlice reducer uses PayloadAction
.
I'm a bit new in TS i don't see how to solve this conflict.
deps :
I'd like to set up an actual published docs site using Docusaurus.
First step would be to copy over the Docusaurus config that we're using with React-Redux: https://github.com/reduxjs/react-redux/tree/master/website .
I've set up a Netlify project to host this, and am working on getting redux-starter-kit.js.org
as a domain name (see js-org/js.org#2571 ).
From there, we can extract the docs content from our current README and move those into separate pages.
Please leave a comment if you'd like to work on this!
This does not work - no console output when an action occurs:
// reducer/index.js
import { routerReducer } from 'react-router-redux';
import modals from './modals';
export default {
router: routerReducer,
modals,
};
// store.js
import { configureStore } from 'redux-starter-kit';
import reducer from 'reducers';
import { logger } from 'redux-logger';
export default configureStore({
reducer,
middlewares: [logger],
});
This does work. The actions are output in the console.
// reducer/index.js
import { routerReducer } from 'react-router-redux';
import modals from './modals';
import { combineReducers } from 'redux';
const rootReducer = combineReducers({
router: routerReducer,
modals,
});
export default rootReducer;
// store.js
import { applyMiddleware, createStore } from 'redux';
import reducer from 'reducers';
import { logger } from 'redux-logger';
export default createStore(
reducer,
applyMiddleware(logger)
)
FWIW, I'm on this version of the start kit:
"redux-starter-kit": "^0.4.0"
Are the devtools supported when using this library with react-native?
If no, is it possible to support remote-redux-devtools which would allow devtools on react-native projects
The redux-utilities org has some pretty popular packages, it might make sense to depend on them partially. I haven't really used them that much so please voice your opinions.
This seems like a cool abstraction to handling actions, similar to the one we already have. Though if it requires FSAs it's probably not a great idea unless we can get them to patch it upstream to support plain actions.
This is a handy way to compose reducers together. I don't think we need it yet but it could help us write more complex high order reducers in the future.
This is one of the more popular async packages along with thunk and saga. I'm fine with redux-thunk, but could there be an advantage to switching to promises or supporting both thunks and promises? Can the thunk package alone support promises and completely replace the need to use redux-promise? I think that's an especially important thing to consider since many async HTTP request libraries use promises now.
We now have a published docs site at https://redux-starter-kit.js.org , and I've moved the API reference content from the README over to there.
However, the API doc pages are pretty short with only some simple examples, and they don't go into very much depth.
It would be great if we can expand those pages with more descriptive content.
I'd also like to see some example projects set up showing how to use this in action. One idea would be to base it off of a recent project shown on Reddit that I refactored to make use of RSK.
The index.d.ts attempts to import from src/, leading to broken exports. Referencing this comment on the corresponding pull request. Thanks for your hard work on implementing TypeScript support.
The createReducer
docs page calls out the error of using Immer to mutate the draft while also returning a new value.
One thing we should specifically highlight is that this can happen with arrow functions that use implicit returns, like (state, action) => state.field = action.value)
.
We should reference a couple sections of the Immer README docs on this topic, like https://github.com/mweststrate/immer#inline-shortcuts-using-void , and possibly also link to immerjs/immer#246 (comment) .
I'd also like to add a couple sub-headers for that section, like:
Simplifying Immutable Updates using Immer
Immer API Behavior Notes
Or something along those lines
Thanks for the great tool! It uncovers some great existing utilities, provides some new ones, and sheds light on some useful idioms.
I had a question regarding createSlice
. Having used a "ducks" structure previously, this was a great thing to uncover. And, naturally, I wondered why there wasn't an ability to also pass selectors into createSlice
. However, I noticed that there is the ability to pass selectors into createSlice
but it's undocumented. My only complaint about the functionality as it stands is that it modifies the name of selector. I understand why that happens for actions (and it makes it more readable to boot!), but I don't quite understand why that is needed for selectors.
So, two questions:
createSliceSelector
for import?Thanks!
I would like to suggest a change in the way we support custom middleware because createDefaultMiddleware
, at least for me, sounds confusing. You are not actually creating a default middleware, you are applying your own middleware while keeping the library defaults.
Instead of:
const middleware = createDefaultMiddleware(logger);
const store = configureStore({ middleware });
We could support passing a function to middleware and receiving the default middleware as a param:
const middleware = defaultMiddleware => [defaultMiddleware, logger];
const store = configureStore({ middleware });
This also allows the consumer to control the order of defaultMiddleware in the chain, which might be useful for some people.
defaultMiddleware
would be an object, so in the future we if add more middlewares we could let the user choose the ones he wants:
const middleware = ({ thunk, saga }) => [thunk, saga, logger];
const store = configureStore({ middleware });
The implementation for middleware
argument would be something like:
export function configureStore(options = {}) {
const {
reducer,
devTools = true,
preloadedState,
enhancers = [],
} = options;
let { middleware } = options;
middleware = parseMiddleware(middleware);
// ..
}
const defaultMiddleware = { thunk, saga }
function parseMiddleware(middleware) {
if (middleware === undefined) {
// The order for our own defaultMiddleware doesn't matter, so that's not an issue.
return Object.values(defaultMiddleware)
}
if (Array.isArray(middleware)) {
return middleware
}
if (typeof middleware === "function") {
// Here we need to check for plain objects. The reason is that defaultMiddleware is
// an object to support destructuring when the consumer wants to select specify
// middlewares from the defaultMiddleware.
// But if the consumer uses defaultMiddleware as is, we need to convert it
// to an array.
// The order for our own defaultMiddleware doesn't matter, so that's not an issue.
return middleware(defaultMiddleware)
.reduce(
(result, middleware) =>
isPlainObject(middleware)
? [result, ...Object.values(middleware)]
: [result, middleware],
[]
)
}
throw new Error("`middleware` should be an array or function")
}
There is a potential issue where users could think that they would be allowed to pass middleware as objects:
const apiMiddleware = { todoMiddleware, userMiddleware, ... }
const store = configureStore({
middleware: defaultMiddleware => [defaultMiddleware, apiMiddleware]
})
It's not really an issue as long as you don't expect the order of apiMiddleware
to be maintained. But that's something I didn't like, I don't know how to improve this. Help is welcome.
Hello,
Could the order of enhancers be switched? Now they are added after the middleware and that is why no middleware can see actions that are generated by enhancers.
Combination of redux-first-router and redux-saga is an example of this.
It seems action creator cannot be used inside switch case. case action1
didn't call toString()
Sample code:
import { createAction } from "redux-starter-kit"
const action1 = createAction("action1")
const theAction = action1()
switch (theAction.type) {
case action1:
console.log(theAction.type)
break
default:
console.log('DEFAULT')
}
// Output: DEFAULT
based on my feedback on your reddit post, i tried to split out my comment in Improvements, suggestions and (possible) Issues
The current implementation of createAction
returns an actionCreator which takes 1 argument: the payload.
If you want the payload to support multiple parameters (userId, userName, for instance), you'll have to manually recreate the object every time you use the actionCreator:
const updateUser = createAction('USER_UPDATE')
...
dispatch(updateUser({ userId: 10, data: { firstName: 'martijn', lastName: 'hermans' } }))
Will result in:
{
type: 'USER_UPDATE',
payload: {
userId: 10,
data: {
firstName: 'martijn',
lastName: 'hermans'
}
}
By giving createAction
a second parameter, which reduces the arguments passed to the actionCreator into a payload, you generate actionCreators which allow for multiple arguments.
const updateUser = createAction('USER_UPDATE', (userId, data) => ({ userId, data }) )
...
dispatch(updateUser(10, { firstName: 'martijn', lastName: 'hermans' })))
This will result in the same action, yet it becomes more re-usable, predictable and testable.
The default behaviour, without a "payload reducer" stays the same as it currently is: The first argument becomes the payload.
A basic implementation of this could be:
const identity = payload => payload
export function createAction(type, payloadReducer = identity) {
const action = (...args) => ({
type,
payload: payloadReducer(...args)
})
action.toString = () => `${type}`
return action
}
A more advanced implementation can be found in redux-actions/createAction.js
.toString()
Exposing the slice 'key' has multiple benefits:
state[slice]
will return the part of the state the slice operates on
createActions(slice, ....)
provides the same prefix as the slice has given to the actions
combineReducers({
[userSlice]: userSlice.reducer
})
you can now change the key within createSlice
, and your whole app will function exactly as it did.
In my current implementation, the reducer returned from createReducer
has a type: createReducer(type, actionsMap, initialState)
.
This is basically what the current createSlice(...).reducer
does, but with the added benefit of re-using it as a constant key for its "domain". (usersReducer.toString() === 'books', etc)
createActions(prefix, actionMap)
In it's current state, createSlice
allows you to create prefixed actionCreators.
These actions are however tied to the slice-reducer.
To create custom actionCreators which take the same prefix, you have to manually do that with createAction('users/add')
Prefixing actions has multiple functions:
users/add
doesn't interfere with books/add
type
createActions(prefix, actionMap)
allows you to create prefixed actions the same way createSlice
does, without having them tied 1-1 to a reducer.createActions
can be re-used within createSlice
, but exposes the same functionality without having to create a full slice
The return type of createActions
can either be an
createActions
, the Array version is easier to destructure and exportexport const {
add: addUser,
update,
'do-something-else': doSomethingElse
} = createActions('users', {
add: (data) => ({ data }),
update: (userId, data) => ({ userId, data }),
'do-something-else': (userId, firstName) => ({userId, data: { firstName }})
})
export const {
update
} = createActions('books', {
update: (bookId, data) => ({ bookId, data })
})
add
=> addUser
)update
vs book.update
export const [
addUser,
updateUser,
doSomethingElse
] = createActions('users', {
add: (data) => ({ data }),
update: (userId, data) => ({ userId, data }),
'do-something-else': (userId, firstName) => ({userId, data: { firstName }})
})
Since the payload is whatever the first argument is i put in the actionCreator, i can easily mess up the data i put in there.
Code-completion doesn't hint what needs to be added.
Testing is not possible, since the input is the literal output for the payload
I have to repeat the same object shape every time i dispatch the action. Not very DRY
The 'type' of the slice optional.
If i make two slices, both with no type, and both with an add
action, the type of both actionCreators is add
, which will lead to naming collision.
// books-slice.js
export const { actions } = createSlice({
reducers: {
add: (state, {payload}) => [...state, payload]
}
})
// users-slice.js
export const { actions } = createSlice({
reducers: {
add: (state, {payload}) => [...state, payload]
}
})
// some-component.js
import {actions: UserActions} from './users-slice'
...
dispatch(UserActions.add(user))
// i now have added both a user and a ... book?
When testing either reducer, you will not run into any issues.
When testing the combined Reducer, you will.
You'll probably also not check your sliceReducer again since that just passed a test and works correctly.
The cause of the error will be very hard to find since the implementation is abstracted away.
Since a slice roughly gets treated as a "domain", it would make sense to be able to define actions in createSlice
which don't have a direct connection with a reducer, for instance to catch that action in a middleware.
_Theoretically, you can do that by just adding them with a state => state
reducer
You do want to have actions within the same domain to have the same prefix, since that just makes it easier to debug (and more consistent)
Theoretically, i can, by wrapping the slice reducer in another reducer, etc.
Not every action will be created within a slice reducer, yet i will possibly want those handled by the slice reducer. Currently, this is not possible.
Not being able to add custom actions to a slice Reducer or creating non-handled actions in a sliceReducer means i will have to mix different mindsets.
I will expose actions from a slice, but also have to create them separately somewhere else. This results in having to import them from 2 locations, which means actions are now coupled with slices.
I will create reducers with Slices but if i want to handle actions from userSlice in bookSlice, i can no longer create bookSlice but have to write the reducer manually again. There is no way to let bookSlice have a reducer which handles actions from userSlice.
I hope this provides a bit more structured version of my Reddit comment.
While doing some testing I found that i have to explictly cast my initial state to my desired type if I want to have type safety and auto completion for a more complex state. If I provide the shape as a generic argument I loose all type safety.
Example:
type Todo = {
title: string,
isDone: boolean
}
export const todos = createSlice<Todo[]>({
slice: 'todos',
initialState: [],
reducers: {
addTodo: (state, action: PayloadAction<Todo>) => [...state, action.payload]
}
});
todos.actions. // no more autocomplete | typesafety
todos.actions.addTodo({ oops: 4}) // I can provide whatever arguments I want here
The second approach works just fine and isn't a big issue. Is this intended behaviour?
export const todos = createSlice({
slice: 'todos',
initialState: [] as Todo[],
reducers: {
addTodo: (state, action: PayloadAction<Todo>) => [...state, action.payload]
}
});
todos.actions.addTodo // I get autocomplete
todos.actions.addTodo({isDone: false, title:"asd"}) // TS forces me to provide the right arguments here
``
How can I access the actual state, and not the Immer draft object? An example is when I want to use the initial state values for something in my reducer or when I'd like to compare against values in the current state before performing an update
NOTIFICATIONS_FULFILLED: (state, action) => {
// state is the draft object from Immer
// but I want to access the values set on my actual state
},
Currently if I do state.someProp
as you would in a normal reducer it just logs an empty Proxy
Redux 4 is out. The tests already pass using it, but this issue is just here to remind us to adjust the peer dependency after redux-devtools-extension
needs to update its peer dependency (blocked by zalmoxisus/redux-devtools-extension#490). I'd like wait until this is fixed before adjusting our peer dependencies to reduce confusion and limit peer dependencies seen by users,redux-devtools-extension
updates.
Hi guys!
What is the reason for influence 'slice' params in createSlice to getState
name?
It looks weird, and it's difficult to use with types
https://github.com/reduxjs/redux-starter-kit/blob/master/src/sliceSelector.ts#L16-L21
Time for a lovely bikeshed discussion on what exactly we should name this package!
At the moment, the repo is located under my Github (markerikson/redux-starter-kit
), and published as a scoped NPM package under my username (@acemarke/redux-starter-kit
).
I've always said that I want to turn this into an official Redux-branded package. That first means moving it to our new reduxjs
Github org, which is a no-brainer. (In fact, could probably do that any time now.)
However, there's two questions beyond that:
There's already NPM packages called redux-starter-kit
(an app boilerplate project) and redux-boost
(which is kinda similar to this package, but also does some data fetching stuff), so it looks like both of those are non-options. (Unless we were to convince the owners to give us the package names or something.)
So, I'm open to suggestions for final package naming and publishing.
/cc @nickmccurdy , @sw-yx , @matthew-gerstman, and whoever else feels like commenting.
This is a genuine question. I have been using redux
for long time and one of the pillars is having pure reducers. And I also know what immer
does.
As this is a starter kit, so a bulk of code for people that want start from scratch (newbies and pros), what are the reasons for putting immer
inside the starter package?
A new comer that is trying to learn redux
cold have difficulties or be confused.
Thanks in advance for the feedback!
A lot of Redux users use TypeScript or Flow, and with Redux 4 out, I think it would be a good idea to provide optional types for this module. I would personally prefer to start by porting the codebase to TypeScript, but we could use separate definition files if desired. I may give this a shot but I want to wait for feedback and fix #28 first.
There's only really one config value I care about and configure in my store for the devtools and that's the name
but to support that and others, could the devtools
option accept a boolean or a config object?
I'm opening an issue and not a PR because I'd like to see if the boolean or object is a decent solution or if another would be preferred. I can PR this though. 😄
I'm not sure if this was covered somewhere, but I'm wondering what should be done about actions?
Having in mind that there's a redux
dependency in the project and the combineReducers
is being exposed, I believe we should also expose thecompose
function.
Reason:
npm install
itimport redux
just to consume one function.All the code examples are used in src/index.js
as generated by create-react-app
.
import redux from "@acemarke/redux-starter-kit";
import {createReducer} from "@acemarke/redux-starter-kit";
import {configureStore} from "@acemarke/redux-starter-kit";
import {createSelector} from "@acemarke/redux-starter-kit";
import {createNextState} from "@acemarke/redux-starter-kit";
import {combineReducers} from "@acemarke/redux-starter-kit";
import {createDefaultMiddleware} from "@acemarke/redux-starter-kit";
In the official createSlice documentation, in the example it uses configureStore
, which suppose to be createSlice
:
Hi all,
I'm not sure if this has been asked before but would it be possible to have createSlice
reference other maybe an array/collection of other slices.
I'm quite new to redux and have only ready the API docs but it seems to me that slices are a great way to condense all the redux boilerplate and provide a much better way of organising your code.
I was thinking something like this:
const rootSlice= createSlice({
references: [slice1, slice2] // Maybe this or something similar
})
// slice1.js
const sliceOne = createSlice({
// initialState, reducers etc.
references: [slice1a] // Refers to another slice
})
So in the end you can have all your redux logic isolated in their own file.
Let me know what you think,
Thanks
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:
createSelector
from Reselect, createCachedSelector
createSlice
selectFoo
instead of getFoo
)?state => state
is pointless. Why are we doing that? What should we do instead?createAction
.type
to the action creators in addition to .toString()
. This helps with JS switch statements, as well as object lookup tables in TS."STARTED/SUCCESS/FAILURE"
action typesredux-promise
reduce-reducers
to the toolkitdevTools
to be more options beyond a booleanautodux
to see if there's any other functionality we want to swipeNot saying we have to have all of these, but those are the ideas floating around my head.
Thoughts? Suggestions?
I cloned a repo, then ran yarn
prepare
hook called:
$ npm run lint && npm test && npm run build
> [email protected] lint C:\WORK\sample\REACT\redux-starter-kit
> eslint src
C:\WORK\sample\REACT\redux-starter-kit\src\sliceSelector.js
18:20 warning Expected '===' and instead saw '==' eqeqeq
✖ 1 problem (0 errors, 1 warning)
> [email protected] build C:\WORK\sample\REACT\redux-starter-kit
> rollup -c
src/index → dist/redux-starter-kit.umd.js...
[!] Error: 'curry' is not exported by node_modules\curriable\dist\curriable.js
https://github.com/rollup/rollup/wiki/Troubleshooting#name-is-not-exported-by-module
node_modules\unchanged\es\index.js (2:13)
1: // external dependencies
2: import { __, curry } from 'curriable'; // utils
^
3:
4: import { callIfFunction, callNestedProperty, getDeepClone, getMergedObject, getNestedProperty, getNewEmptyObject, hasNestedProperty, isArray, isCloneable, isEmptyPath, splice } from './utils
';
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! [email protected] build: `rollup -c`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the [email protected] build script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
npm ERR! A complete log of this run can be found in:
npm ERR! C:\Users\Dolgov\AppData\Roaming\npm-cache\_logs\2018-12-11T18_07_21_808Z-debug.log
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/install for documentation about this command.
curriable.js
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(factory((global.curriable = {})));
}(this, (function (exports) { 'use strict';
var __ = typeof Symbol === 'function' ? Symbol('curriable placeholder') : 0xedd1;
/**
* @function getArgs
*
* @description
* get the complete args with previous placeholders being filled in
*
* @param originalArgs the arguments from the previous run
* @param nextArgs the arguments from the next run
* @returns the complete list of args
*/
var getArgs = function (originalArgs, nextArgs) {
var length = originalArgs.length;
var nextLength = nextArgs.length;
var args = new Array(length);
var nextArgsIndex = 0;
for (var index = 0; index < length; index++) {
args[index] =
originalArgs[index] === __ && nextArgsIndex < nextLength
? nextArgs[nextArgsIndex++]
: originalArgs[index];
}
if (nextArgsIndex < nextLength) {
for (; nextArgsIndex < nextLength; nextArgsIndex++) {
args.push(nextArgs[nextArgsIndex]);
}
}
return args;
};
/**
* @function hasPlaceholder
*
* @description
* determine if any of the arguments are placeholders
*
* @param args the args passed to the function
* @param arity the arity of the function
* @returns are any of the args placeholders
*/
var hasPlaceholder = function (args, arity) {
for (var index = 0; index < arity; index++) {
if (args[index] === __) {
return true;
}
}
return false;
};
// utils
/**
* @function curry
*
* @description
* get the method passed as a curriable method based on its parameters
*
* @param fn the method to make curriable
* @param arity the arity of the curried method
* @returns the fn passed as a curried function
*/
var curry = function (fn, arity) {
if (arity === void 0) { arity = fn.length; }
function curried() {
var args = arguments;
return args.length >= arity && !hasPlaceholder(args, arity)
? fn.apply(this, args)
: function () {
return curried.apply(this, getArgs(args, arguments));
};
}
curried.arity = arity;
curried.fn = fn;
return curried;
};
curry.__ = __;
/**
* @function uncurry
*
* @description
* return a function that is the non-curried version of the fn passed
*
* @param curried the curried function to uncurry
* @returns the original fn
*/
var uncurry = function (curried) { return curried.fn; };
curry.uncurry = uncurry;
exports.__ = __;
exports.curry = curry;
exports.uncurry = uncurry;
exports.default = curry;
Object.defineProperty(exports, '__esModule', { value: true });
})));
//# sourceMappingURL=curriable.js.map
Unlike a production build of something like React that has much more overhead for developer tooling, Redux devtools has very little overhead especially because it's mostly disabled until the Redux developer tools extension is open. I think it would be best to keep this on by default, but allow users
to manually set it to NODE_ENV !== production
if it's necessary for some reason.
https://medium.com/@zalmoxis/using-redux-devtools-in-production-4c5b56c5600f
There's been some scattered discussion of how createSlice
should actually behave, so I wanted to move this into its own thread.
I'll keep the initial thoughts short and open things up for discussion:
createSlice
to be included in some form. We're not removing it.createSlice
was inspired by https://github.com/ericelliott/autodux , we should review what functionality autodux
actually has for point of comparison. (Also lots of similar options in https://github.com/markerikson/redux-ecosystem-links, particularly under "Action / Reducer Generators".)Tagging: @modernserf @denisw @Dudeonyx @BTMPL @lkuoch @mattkahl @doxick
I'm trying to figure out how to architect my project and am not sure how all the parts and pieces here fit together.
I don't feel it's clear from the documentation when to use createReducer() and when to use createSlice(). There are a lot of cases where either could be used with createSlice() being less verbose. Is the idea that createSlice() is the easiest choice unless you need more control over the action creators? I'm coming from vanilla redux but also see how this could be confusing to a new user.
The goal: Ideally, if I only import createAction
then I wouldn't expect immer
or selectorator
to be pulled into my bundle. This is the intention behind the ESM build pointed to by the module
field in this library's current package.json
.
Unfortunately, webpack in its default configuration (including how it's configured in frameworks like Next.js) prefers the browser
field to module
, so the entire 103KB UMD build is pulled in no matter what you import. Instead of requiring people to fiddle with a non-default webpack config, it would be better to behave nicely with the defaults.
If you check out the package.json
for redux
and react-redux
, they don't specify the browser
field at all – instead, the UMD build is linked in the unpkg
field. That way, webpack will prefer the module
build but there's still a UMD build included.
This comment also suggests a potential way that these fields could behave together, by providing browser
alternatives to both the ESM and CJS entry points. I'm not sure how that works exactly, but might be worth exploring.
Slice helpers abstract the need to recreate repetitive reducers/actions, etc. I have found myself creating a lot of the same type of reducers and by creating a thin wrapper around createSlice
I'm reducing a ton of boilerplate code.
The primary slices I create often are map
and simple assign
. I've been using these slices in production for awhile now with great success.
Here are some examples:
https://github.com/neurosnap/robodux#slice-helpers
Map slice:
interface SliceState {
[key: string]: string;
}
interface State {
test: SliceState
}
interface Actions {
addTest: State;
setTest: State;
removeTest: string[];
resetTest: never;
}
const slice = 'test';
const { reducer, actions } = mapSlice<SliceState, Actions, State>(slice);
const state = { 3: 'three' };
store.dispatch(
actions.addTest({
1: 'one',
2: 'two',
})
);
/* {
1: 'one',
2: 'two',
3: 'three,
} */
store.dispatch(
actions.setTest({ 4: 'four', 5: 'five', 6: 'six' })
)
/* {
4: 'four',
5: 'five',
6: 'six',
} */
store.dispatch(
actions.removeTest(['5', '6'])
)
/* {
4: 'four'
} */
store.dispatch(
actions.resetTest()
)
// {}
What does everyone think?
I'd be happy to submit a PR to add this feature to RSK.
I just checked why tree shaking is not working correctly as mentioned by @markerikson in his tweet. I tested using the following small example project.
package.json
{
"name": "example",
"private": true,
"scripts": {
"build": "webpack"
},
"dependencies": {
"redux-starter-kit": ".."
},
"devDependencies": {
"webpack": "^4.28.4",
"webpack-cli": "^3.2.1"
},
"sideEffects": false
}
webpack.config.js
module.exports = {
mode: 'production'
}
src/index.js
import { createAction } from 'redux-starter-kit'
const foo = createAction('foo')
console.log(foo(123))
When building, all of redux-starter-kit ends up in the output. Looking more at the Webpack tree-shaking optimization, I eventually found the Debugging Optimization Bailouts section, which suggests running Webpack with --display-optimization-bailout
to find out when optimizations don't get applied. This is what I got:
> example@ build /Users/denis/Projects/redux-starter-kit/example
> webpack --display-optimization-bailout
Hash: 6728bcf74b0489ce1cf3
Version: webpack 4.28.4
Time: 390ms
Built at: 01/11/2019 10:56:01 PM
Asset Size Chunks Chunk Names
main.js 23.9 KiB 0 [emitted] main
Entrypoint main = main.js
[8] (webpack)/buildin/global.js 472 bytes {0} [built]
ModuleConcatenation bailout: Module is not an ECMAScript module
[9] (webpack)/buildin/harmony-module.js 573 bytes {0} [built]
ModuleConcatenation bailout: Module is not an ECMAScript module
[14] ./src/index.js + 2 modules 13.4 KiB {0} [built]
ModuleConcatenation bailout: Cannot concat with ../node_modules/immer/dist/immer.module.js (<- Module uses injected variables (process))
ModuleConcatenation bailout: Cannot concat with ../node_modules/redux-devtools-extension/index.js (<- Module is not an ECMAScript module)
ModuleConcatenation bailout: Cannot concat with ../node_modules/redux-immutable-state-invariant/dist/index.js (<- Module is not an ECMAScript module)
ModuleConcatenation bailout: Cannot concat with ../node_modules/redux/es/redux.js (<- Module is referenced from these modules with unsupported syntax: ../node_modules/redux-devtools-extension/index.js (referenced with cjs require))
| ./src/index.js 104 bytes [built]
| ModuleConcatenation bailout: Module is an entry point
| ../dist/redux-starter-kit.esm.js 12.8 KiB [built]
| + 1 hidden module
+ 25 hidden modules
As ModuleConcatenationPlugin is needed for tree-shaking, this suggests that some of the dependencies using require()
prevent tree-shaking from happening.
In the redux-actions library's version of createAction
, you can provide an optional second argument, a payloadCreator
function. In the past, I've used it for things like generating a unique id for a new item before passing it along. (I don't know if that is best practice though).
Is adding the payloadCreator
argument to your version of createAction
something you would consider?
toJSON
methods on objects, but i'm not sure how consistent it is.SET_*
.Issue to track #11 (adding at least one of Redux's examples here, adapted to use the starter kit)
I already added checks for mutation and serializability. The other category of things that often comes up is side effects in reducers. Had some ideas on that front tonight:
Math.random
the same way? (Was specifically helping someone in Reactiflux tonight who was generating random color values in a reducer.)I figure I can turn this into another validation middleware. The biggest issue is that I can imagine someone putting some other async middleware after ...getDefaultMiddleware()
, and this one throwing errors because those happened while next(action)
was running.
in createSlice example
store.dispatch(counter.actions.increment())
// -> { counter: 1, user: {} }
store.dispatch(counter.actions.increment())
// -> { counter: 1, user: {} } <--------- this should be 2
store.dispatch(counter.actions.multiply(3))
// -> { counter: 6, user: {} }
After trying out your hello world example I noticed, that the parameters of the actions are not infered.
Here's what I got:
export const counter = createSlice({
slice: 'counter',
initialState: 0,
reducers: {
increment: (state) => state + 1,
decrement: (state) => state - 1,
multiply: (state, action: PayloadAction<number>) => state * action.payload
}
});
counter.actions.multiply() // should error out
counter.actions.multiply("2") // should error out
The call to counter.actions.multiply without any or the wrong arguments shouldn't be possible since it will generate a runtime error. Is this intended behaviour or am I doing something wrong / it's not possible?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.