Giter Site home page Giter Site logo

reduxjs / redux-toolkit Goto Github PK

View Code? Open in Web Editor NEW
10.4K 10.4K 1.1K 24.96 MB

The official, opinionated, batteries-included toolset for efficient Redux development

Home Page: https://redux-toolkit.js.org

License: MIT License

JavaScript 3.34% CSS 0.53% TypeScript 96.13%

redux-toolkit's People

Contributors

aryaemami59 avatar barnabasj avatar dannielss avatar denisw avatar dutziworks avatar eskimojo14 avatar fabervitale avatar georchw avatar hnordt avatar itz-me-pj avatar joshuakgoldberg avatar juliengbt avatar kahirokunn avatar kevin940726 avatar labmorales avatar markerikson avatar maruhgar avatar msutkowski avatar nickbabcock avatar nickmccurdy avatar olesj-bilous avatar phryneas avatar princerajroy avatar riqts avatar ryota-murakami avatar sgnilreutr avatar shrugsy avatar sidwebworks avatar stefanbras avatar thoughtworks-tcaceres avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

redux-toolkit's Issues

Consider removing re-exports

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.

Advantages

  • Less maintenance work involved in updating dependencies, and more freedom for dependent packages to update dependencies without waiting for us to update them or installing newer upstream versions. For example: #16
  • Less package bloat for users that don't want the packages we recommend. For example if someone uses their own memoization library that isn't compatible with reselectorator, they won't have to set up tree shaking to remove the extra dependency if we make it optional.
  • Explicit understanding of where dependencies come from. Users would be aware of the packages they're using directly, but not implementation details of this package such as redux-thunk.
  • Closer to the intention of package.json files only including dependencies that are required internally (ignoring optionalDependences and peerDependencies which I don't think would be useful in this case).
  • Dependencies of dependencies don't satisfy the peer dependencies of other dependencies of a package. For example, if we kept our current behavior and someone installed a new package that peer depends on selectorator, they may have to install selectorator manually just to satisfy the peer dependency. This could cause confusion.

Disadvantages

  • Slightly more installation work. This could be remedied by simply adding other packages to the install script in our readme, for example npm install @acemarke/redux-starter-kit selectorator. This is a common practice in documentation for popular npm packages.

Create Async Action

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?

You are currently using minified code outside of NODE_ENV === "production".

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/

typings issue between createSlice and configureStore's reducer prop ?

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 :

  • redux-starter-kit 0.4.2
  • typescript 3.2.4

Set up a published docs site

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!

redux-logger not working via configureStore

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"

Consider using packages from redux-utilities

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.

redux-actions

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.

reduce-reducers

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.

redux-promise

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.

Add more documentation

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.

Clarify Immer behavior in `createReducer` docs

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

Undocumented selectors functionality in `createSlice`

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:

  1. Are you planning on exposing this functionality in the documentation? Or is it hidden for a reason?
  2. Would you be open to either (a) changing this functionality or (b) exposing createSliceSelector for import?

Thanks!

createDefaultMiddleware Semantics

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.

Enhancers are added after middleware

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.

actionCreator#toString didn't work on switch case

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

Suggestion, possible issues and improvements

based on my feedback on your reddit post, i tried to split out my comment in Improvements, suggestions and (possible) Issues

Improvements

Allow actionCreators from createAction to shape their payload

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

Exposing the slice Name/Type through .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)

Suggestions

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:

  • Easier to group actions, which prevents collisions: users/add doesn't interfere with books/add
  • Easier to debug: the "domain"/slice which the actions belong to is visible in the type
  • The addition of 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

  • Object: the actionCreators mapped to the keys
  • Array: the actionCreators returned in order of the actionMap
    From doing +- 15 projects using createActions, the Array version is easier to destructure and export

Return-type: Object

export 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 })
})
  • not every action type lends itself for a variable name
  • unless you export the complete returned Object, you'll probably rename every destructured variable. (add => addUser)
  • destructuring the object version leads to naming collisions if done in the same file (users.update vs book.update

Return-type: Array

export const [
  addUser,
  updateUser,
  doSomethingElse
] = createActions('users', {
  add: (data) => ({ data }),
  update: (userId, data) => ({ userId, data }),
  'do-something-else': (userId, firstName) => ({userId, data: { firstName }})
})
  • the resulting Array can't be exported without destructuring (unless you want to use actions by their index... not very likely)
  • saves boilerplate when destructuring compared to the Object-version

(Possible) issues

actionCreators from createAction have no validation/type safety for the payload

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

Creating actions in slices can lead to collision

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.

Create actions which aren't handled by a reducer for a slice

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)

I can't add my custom actions to be reduced by a slice-reducer

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.

How to describe the shape of the state in createSlice

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
``

Unable to access previous or initial state

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

Support Redux 4

Redux 4 is out. The tests already pass using it, but 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, this issue is just here to remind us to adjust the peer dependency after redux-devtools-extension updates.

Bikeshed discussion: package name

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:

  • What should the actual name of the package itself be?
  • How should we publish it on NPM - scoped or global package name?

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.

Why immer inside a starter kit?

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!

Typing support

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.

allow configuration of devtools via `configureStore`

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. 😄

Expose compose function

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:

  • People would not npm install it
  • Double import redux just to consume one function.
  • To make it more complete, as it's a such a nice helper to have under your sleeve.

Some exports are undefined in create-react-app

All the code examples are used in src/index.js as generated by create-react-app.

Undefined

import redux from "@acemarke/redux-starter-kit";
import {createReducer} from "@acemarke/redux-starter-kit";

Defined

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";

Slices inside slices

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:

  • You only need to create a root slice which can reference every other slice
const rootSlice= createSlice({
    references: [slice1, slice2] // Maybe this or something similar
})
  • Then you effectively have name spaces and only need to create one file with all your actions/reducers etc
// 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

Discussion: Roadmap to 1.0

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?

npm run build: Error: 'curry' is not exported by node_modules\curriable\dist\curriable.js

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)

The main problem below

> [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

Feature discussion: `createSlice` behavior

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:

  • I want createSlice to be included in some form. We're not removing it.
  • At a minimum, I want to add the "listen for other actions" functionality from #83 / #86 .
  • We should make the selector behavior more useful
  • We should consider the desired behavior around combining slices as part of a larger app's reducer.
  • Given that 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".)
  • Refer back to my "vision" statement in #82#82 (comment) for the purpose I want this to serve.

Tagging: @modernserf @denisw @Dudeonyx @BTMPL @lkuoch @mattkahl @doxick

When to use createReducer() vs createSlice()

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.

Improve packaging: webpack uses UMD build, inlines immer, selectorator, etc.

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.

Feature: Slice helpers

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.

Tree shaking is hindered by dependencies

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.

Consider additional runtime checks

  • Warn if data structures (like Sets and Promises) are used in reducers that aren't serializable. We could potentially look for toJSON methods on objects, but i'm not sure how consistent it is.
  • Warn about multiple stores, or make it more difficult/impossible.
  • Warn about using Redux or the starter kit directly when react-redux is being used.
  • Warn about action names that are overly generic or imperative like SET_*.

Add example

Issue to track #11 (adding at least one of Redux's examples here, adapted to use the starter kit)

Add even more runtime sanity checks

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:

  • https://github.com/angular/zone.js/ monkey-patches every source of async behavior in the environment, and lets you create "zones" that monitor async behavior. Per Section 5, Use Case 1.a in the "Zone Primer" doc, warning about async behavior is a perfectly valid use case for Zone.js
  • The other similar concern is randomness. Obviously can't check for everything, but maybe look into monkey-patching 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.

typo in readme file

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: {} }

Parameters of actions are not infered

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?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.