Skip to content

Instantly share code, notes, and snippets.

@gaearon
Last active March 26, 2026 21:25
Show Gist options
  • Select an option

  • Save gaearon/ffd88b0e4f00b22c3159 to your computer and use it in GitHub Desktop.

Select an option

Save gaearon/ffd88b0e4f00b22c3159 to your computer and use it in GitHub Desktop.
Redux without the sanity checks in a single file. Don't use this, use normal Redux. :-)
function mapValues(obj, fn) {
return Object.keys(obj).reduce((result, key) => {
result[key] = fn(obj[key], key);
return result;
}, {});
}
function pick(obj, fn) {
return Object.keys(obj).reduce((result, key) => {
if (fn(obj[key])) {
result[key] = obj[key];
}
return result;
}, {});
}
function bindActionCreator(actionCreator, dispatch) {
return (...args) => dispatch(actionCreator(...args));
}
export function bindActionCreators(actionCreators, dispatch) {
return typeof actionCreators === 'function' ?
bindActionCreator(actionCreators, dispatch) :
mapValues(actionCreators, actionCreator =>
bindActionCreator(actionCreator, dispatch)
);
}
export function compose(...funcs) {
return arg => funcs.reduceRight((composed, f) => f(composed), arg);
}
export function applyMiddleware(...middlewares) {
return (next) => (reducer, initialState) => {
var store = next(reducer, initialState);
var dispatch = store.dispatch;
var chain = [];
chain = middlewares.map(middleware => middleware({
getState: store.getState,
dispatch: (action) => dispatch(action)
}));
dispatch = compose(...chain)(store.dispatch);
return { ...store, dispatch };
};
}
export function combineReducers(reducers) {
var finalReducers = pick(reducers, (val) => typeof val === 'function');
return (state = {}, action) => mapValues(finalReducers,
(reducer, key) => reducer(state[key], action)
);
}
export function createStore(reducer, initialState) {
var currentReducer = reducer;
var currentState = initialState;
var listeners = [];
var isDispatching = false;
function getState() {
return currentState;
}
function subscribe(listener) {
listeners.push(listener);
return function unsubscribe() {
var index = listeners.indexOf(listener);
listeners.splice(index, 1);
};
}
function dispatch(action) {
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.');
}
try {
isDispatching = true;
currentState = currentReducer(currentState, action);
} finally {
isDispatching = false;
}
listeners.slice().forEach(listener => listener());
return action;
}
function replaceReducer(nextReducer) {
currentReducer = nextReducer;
dispatch({ type: '@@redux/INIT' });
}
dispatch({ type: '@@redux/INIT' });
return { dispatch, subscribe, getState, replaceReducer };
}
@snehesht

snehesht commented Oct 5, 2015

Copy link
Copy Markdown

Awesome.

@dbrans

dbrans commented Oct 13, 2015

Copy link
Copy Markdown

@gaearon, In the current revision, in applyMiddleware, the initial value of var dispatch = store.dispatch is never used. Did you meant to use it on line 43?
https://gist.github.com/gaearon/ffd88b0e4f00b22c3159/4c9a62c6a2b353842dd2b6d15b9ca363ad4161e3#file-slim-redux-js-L36

@davidjnelson

Copy link
Copy Markdown

I love this :-) I think redux has the highest GitHub stars per line of code of all time. 80 stars per line ;-)

@dlwalsh

dlwalsh commented Oct 27, 2015

Copy link
Copy Markdown

Is there a reason why mapValues (and also pick) looks like this:

function mapValues(obj, fn) {
  return Object.keys(obj).reduce((result, key) => {
    result[key] = fn(obj[key], key);
    return result;
  }, {});
}

and not the more idiomatic:

function mapValues(obj, fn) {
  return Object.keys(obj).reduce((result, key) => (
    Object.assign({}, result, {
      [key]: fn(obj[key], key);
    });
  ), {});
}

Is it a case of performance over purity?

@ksmithbaylor

Copy link
Copy Markdown

@dlwalsh or for that matter:

function mapValues(obj, fn) {
  return Object.keys(obj).reduce((result, key) => ({
    ...result,
    [key]: fn(obj[key], key)
  }), {});
}

@acusti

acusti commented Dec 1, 2015

Copy link
Copy Markdown

@dbrans The initial dispatch function in applyMiddleware is used on line 41 within the body of the wrapped dispatch (i.e. next) function passed to each middleware.

@CrocoDillon

Copy link
Copy Markdown

@dlwalsh, result is created inside the mapValues function and passed to reduce as initial value (also called an accumulator). Because it’s created inside mapValues, it can be mutated and mapValues would still be a pure function.

@dtinth

dtinth commented May 4, 2016

Copy link
Copy Markdown

@dlwalsh It’s algorithmic complexity. The “purer” version is O(n2) where the original version is O(n), so it’s not the same as using for loops vs Array#reduce. Here’s a benchmark:

function mapValues1(obj, fn) {
  return Object.keys(obj).reduce((result, key) => {
    result[key] = fn(obj[key], key);
    return result;
  }, {});
}

function mapValues2(obj, fn) {
  return Object.keys(obj).reduce((result, key) => (
    Object.assign({}, result, {
      [key]: fn(obj[key], key)
    })
  ), {});
}

var obj = { }
for (var i = 0; i < 10000; i ++) obj[i] = i

var mapper = x => x * x

console.time('mapValues1')
var result1 = mapValues1(obj, mapper)
console.timeEnd('mapValues1')

console.time('mapValues2')
var result2 = mapValues2(obj, mapper)
console.timeEnd('mapValues2')

// Ensure result is correct :)
require('assert').equal(result1[100], 10000)
require('assert').deepEqual(result1, result2)

Result:

mapValues1: 7.764ms
mapValues2: 10896.908ms

You can see that it takes 10 seconds to map a 10000-key object if you create a new object every time. Both functions are equally pure as @CrocoDillon said, since all the mutations happen inside the function.

@davesnx

davesnx commented May 4, 2016

Copy link
Copy Markdown

Well tested @dtinth, but it's basically the Object.assign creates a new object(with result and matches the key) each iteration, and the mapValues1 just add as a key in the resultant object.

@vasanthk

Copy link
Copy Markdown

Pure Genius!

@LucaColonnello

Copy link
Copy Markdown

I did the same for my Redux Course to explain how really Redux works and what is the concept. I notice that in a lot of courses about Redux, teachers miss the importance of selectors...

@tonytangau

Copy link
Copy Markdown

Artistic

@ansonkao

Copy link
Copy Markdown

It's possible to slim it down even further by removing the isDispatching checks! Then it would be < 90 lines!

@sabha

sabha commented Oct 24, 2016

Copy link
Copy Markdown

Thanks Dan. Do we have similar one for React-Redux. Trying to understand the connect method for adding features like "Resolve data before mounting the container".

@jakl

jakl commented Dec 13, 2016

Copy link
Copy Markdown

I would love to watch a youtube video going over in detail every line of this code.

@thangchung

Copy link
Copy Markdown

Awesome. Simple but not simpler 👍

@ehzawad

ehzawad commented Feb 24, 2017

Copy link
Copy Markdown

Cool

@wayofthefuture

Copy link
Copy Markdown

Haters don't hate you know it's genius... #imnotworthy#

@ptim

ptim commented Jun 11, 2017

Copy link
Copy Markdown

@sabha you probably found it already, but for those who com after, here is connect explained: https://gist.github.com/gaearon/1d19088790e70ac32ea636c025ba424e

@gandhirahul

Copy link
Copy Markdown

Amazingly beautiful code 👍

@LorisBachert

Copy link
Copy Markdown

I'm heavily breathing right now. This is just pure awesomeness!

@Shawnxkwang

Copy link
Copy Markdown

It's 2018. Is it too late for me to say 'cool'?

@ryuhangyeong

Copy link
Copy Markdown

Wow!

@sag1v

sag1v commented Mar 28, 2019

Copy link
Copy Markdown

4 years later...
I'm curious why you use .slice on this line
listeners.slice().forEach(listener => listener());

Why do we need a new array here?

@raynerpupo

raynerpupo commented Apr 10, 2019

Copy link
Copy Markdown

@sag1v while that forEach is a blocking operation it doesn't guarantee that a listener() call wouldn't remove a original listener from the listeners array so by copying the list you won't get weird results. 2nd caveat from here: https://redux.js.org/api/store#subscribe.

@raynerpupo

Copy link
Copy Markdown

@sag1v found the possible reason, please read the 2nd caveat here https://redux.js.org/api/store#subscribe

@sag1v

sag1v commented Jun 19, 2020

Copy link
Copy Markdown

@raynerpupo For some reason i missed your comments, thanks :)

For other readers, this code snippet can demonstrate what happens if you don't use .slice to create a "snapshot" of the array.
Basically if a listener removes itself from the array, other listeners might not run (due to mutation).

let arr = [func1, func2];
function func1(){
  arr.splice(0, 1) // func1 removes itself from the array
  console.log(1)
}
function func2(){
  console.log(2)
}
arr
//.slice() // if we don't slice here, func2 wont run
.forEach(fn => fn())

// logs: 1

@hacker0limbo

Copy link
Copy Markdown

2020, starting to learn redux, and got one confusion for this code snippet:

In the createStore function, there should be a third option argument enhancer right? I checked the redux source code and the createStore function might look this:

function createStore(reducer, preloadedState, enhancer) {
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
  }

  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }

    return enhancer(createStore)(reducer, preloadedState)
  }

  // ... dispatch, subscribe, getState, replaceReducer, 
}

In this case we can then pass the applyMiddleware to the createStore function:

const store = createStore(
  rootReducer,
  applyMiddleware(
    thunk,
    logger,
  )

Not sure if my thoughts are reasonable, could someone correct me please?

@taaemoh

taaemoh commented Oct 7, 2020

Copy link
Copy Markdown

I did the same for my Redux Course to explain how really Redux works and what is the concept. I notice that in a lot of courses about Redux, teachers miss the importance of selectors...

Could u please share some link to your course? is it available somewhere?

@ackvf

ackvf commented Jan 23, 2024

Copy link
Copy Markdown

Poetry

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