Giter VIP home page Giter VIP logo

redux-batched-subscribe's Introduction

redux-batched-subscribe

build status npm version

Store enhancer for redux which allows batching of subscribe notifications that occur as a result of dispatches.

npm install --save redux-batched-subscribe

Usage

The batchedSubscribe store enhancer accepts a function which is called after every dispatch with a notify callback as a single argument. Calling the notify callback will trigger all the subscription handlers, this gives you the ability to use various techniques to delay subscription notifications such as: debouncing, React batched updates or requestAnimationFrame.

Since batchedSubscribe overloads the dispatch and subscribe handlers on the original redux store it is important that it gets applied before any other store enhancers or middleware that depend on these functions; The compose utility in redux can be used to handle this:

import { createStore, applyMiddleware, compose } from 'redux';
import { batchedSubscribe } from 'redux-batched-subscribe';

const enhancer = compose(
  applyMiddleware(...middleware),
  batchedSubscribe((notify) => {
    notify();
  })
)

// Note: passing enhancer as the last argument to createStore requires redux@>=3.1.0
const store = createStore(reducer, initialState, enhancer);

Note: since compose applies functions from right to left, batchedSubscribe should appear at the end of the chain.

The store enhancer also exposes a subscribeImmediate method which allows for unbatched subscribe notifications.

Examples

Debounced subscribe handlers:

import { createStore } from 'redux';
import { batchedSubscribe } from 'redux-batched-subscribe';
import debounce from 'lodash.debounce';

const debounceNotify = debounce(notify => notify());
// Note: passing batchedSubscribe as the last argument to createStore requires redux@>=3.1.0
const store = createStore(reducer, intialState, batchedSubscribe(debounceNotify));

React batched updates

import { createStore } from 'redux';
import { batchedSubscribe } from 'redux-batched-subscribe';

// React >= 0.14
import { unstable_batchedUpdates } from 'react-dom';

// Note: passing batchedSubscribe as the last argument to createStore requires redux@>=3.1.0
const store = createStore(reducer, intialState, batchedSubscribe(unstable_batchedUpdates));

Thanks

Thanks to Andrew Clark for the clean library structure.

redux-batched-subscribe's People

Contributors

gaearon avatar tappleby 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

redux-batched-subscribe's Issues

Project status/passing action to notify callback

I see there are three open PRs for passing the action being dispatched to the notify callback (#6, #13, #17) - any updates about your thoughts regarding a change like that?

This enhancer gives us great perf wins for our application, but there are a handful of actions dispatched by keyboard input that we need to notify subscribers immediately or things get laggy and cause other input issues.

Please don't take this as being critical or nagging, I know everyone is busy and OSS projects can easily become time sinks, I'm just looking to see whether I should maintain a separate fork or your goals are to continue maintaining this project.

Is there a way to use subscribeImmediate with react-redux?

I use react-redux and its normal bindings of creating a mapStateToProps, mapDispatchToProps, and using those to enhance my components by calling react-redux's connect function, which internally calls subscribe.

This library exposes a subscribeImmediate, but I'm not sure how to use it for a component since I'm using react-redux, which takes care of subscribing for me. I'm guessing there's not an easy way to do it, but opening this issue just to ask if there is one.

Confused as to order of execution for notify()

Hi, I'm not sure if my project is set up weird or anything, but I have a store that looks like

import { createStore } from 'redux';
import { Map } from 'immutable';
import rootReducer from './reducers';
import { batchedSubscribe } from 'redux-batched-subscribe';

let notifier = 0;
const notifyAndSetFlag = (notify) => {
  notifier = 0;
  console.log("Notifying");
  notify();
};
const debouncer = (notify) => {
  clearTimeout(notifier);
  notifier = setTimeout(notifyAndSetFlag.bind(null, notify), 200);
};

export default function configureStore(initialState) {
  return createStore(rootReducer, initialState || Map(), batchedSubscribe(notify => debouncer(notify)));
}

And a very simple application with one text field that, when I type into it, it dispatches an action to update some text on the screen.
I've entered logging statements in mapStateToProps, render, the reducer, and in notifyAndSetFlag.
When I type "asdf" quickly, I would normally expect to see

reducer
reducer
reducer
reducer
notify
mapstatetoprops
render
mapstatetoprops
render
mapstatetoprops
render
mapstatetoprops
render

But instead I see

reducer
mapstatetoprops
render
reducer
mapstatetoprops
render
reducer
mapstatetoprops
render
reducer
mapstatetoprops
render
notify

Is my store set up incorrectly or is this an issue with batched subscribe or is there some other problem?
When I put in a breakpoint, it looks like

var listeners = currentListeners = nextListeners;
    for (var i = 0; i < listeners.length; i++) {
      var listener = listeners[i];
      listener();
    }

is still being called in redux dispatch(). Should that not happen?

notify() is being called when thunk is dispatched

Hi,

I was looking through source code of redux-batched-subscribe and redux-thunk and noticed that according to redux-thunk implementation when you dispatch a function, thunk middleware calls that function and return immediately.
On the other hand, dispatch implementation of redux-batched-subscribe (see L53-L56) always calls notifyListenersBatched() method thus always notifying subscribers.

I wonder if redux-batched-subscribe was intentionally designed to always notify subscribers or not?

Sorry for opening an issue, I've noticed no bugs and everything works fine, just wanted to discuss the code πŸ™‚
@tappleby , do you have any thoughts on how it is possible to support arbitrary middlewares in store enhancers?

Add an open source license?

Thanks for sharing this project! We'd love to use it as part of the Mattermost open source project (https://www.mattermost.org/) in our React Native mobile app (which users an Apache 2.0 license).

Would you consider adding either an MIT or an Apache 2.0 license?

To do so, in GitHub you can hit "Create new file" and name a file LICENSE.txt

image

This will prompt GitHub to offer a license template:

image

If you use either an MIT license or an Apache 2.0 license it would make it easy to add your work to other open source projects, and we'd love to include your work in ours.

Thanks kindly for your consideration.

Undesired Actions Notifications

Hi. I've designed a custom redux middleware in a attempt to really push all side-effects away from my view code by means of redux-saga. I am basically appending a FIFO "TaskReducer" to my single redux store, and handling any QUEUE actions with it:

//queue action creator
function queue( task ){
  return {
    type: 'QUEUE',
    payload: task
  };
};
//taskMiddleware
export default store => next => action => {
  const result = next(queue(action));
  return result;
}
//and a TaskReducer that merges repeated tasks in a meaningful way (e.g. a FIFO queue) ...

I find this way of delegating tasks quite convenient for performance reasons. However, since the QUEUE action plays as any other action in redux, any store listeners (e.g. hundreds of connected components) will eventually waste time on it, and even DevTools won't work properly since I am not interested in replaying side-effects...

So I was wondering if this situation is of any interest for this repository, as it seems to me that this is the correct place to implement a solution for "undesired action subscriptions".

Perhaps passing the dispatched action as an argument for the batch function would solve this? That way we could easily check for the action type and decide whether we should notify the listeners or not:

const store = batchedSubscribe((notify, action) => {
  if(action.type !== QUEUE)
    requestAnimationFrame(notify);
})(createStore)(reducers)

Anyways, is this worth pull requesting?

Add Example

Add a example for this easy to understand.

Adding exceptions

As mentioned in reduxjs/redux#125 (comment) there are issues when batching updates and using controlled components. For example when using controlled input fields, when the user edits something (not at the end of the line / text) the cursor always jumps to the end.
For this reason I'd like to add exceptions to which notifications are batched or have a way to enable / disable the batcher.
I'm going to work on a PR, but comments are more than welcome.

I'm also reading through: petehunt/react-raf-batching#8

A couple of questions

Hey,
first of all, thank you very much for creating this library!
It addresses exactly a problem that I am facing in my app.

As I understood from this PR, acdlite/redux-batched-updates#3 , this library is the successor of redux-batched-updates , right?

I first could not get your library to work following the example of the readme. Now it works using the debounce method and removing the logger middleware redux-logger , since the logger is turned off in production and that is where batched updates matter the most it is not a big deal though. Is there a workaround for this issue though?

Now my issue is, which i believe is still a quite common use case, I am fetching data from 100 different endpoints and would like to render the data above the fold when it arrives, and batch update the rest in chunks.
The way it works with your fantastic library is that when using batched updates it waits until all 100 requests have completed. How can I make the first render after, say the first 4-5 requests have completed?
Is there a way to get a bit more fine grained control about the updates I would like to have batched, e.g. batch excessive data fetching, but not ordinary user interaction, etc.

Would love to hear your point of view and if you see a way to solve these issues!

Keep up the great work,
Cheers!

babel 5 error

Hi, I'm getting the following error when compiling a project with redux-batched-subscribe:

ERROR in ./~/redux-batched-subscribe/lib/index.js
Module build failed: ReferenceError: [BABEL] /MYPATH/node_modules/redux-batched-subscribe/lib/index.js: Using removed Babel 5 option: /MYPATH/node_modules/redux-batched-subscribe/.babelrc.stage - Check out the corresponding stage-x presets http://babeljs.io/docs/plugins/#presets

Any idea? Thanks

UPDATE:

removing redux-batched-subscribe/.babelrc let me compile without errors.
Is it safe to remove it? In that case, should it be included in the npm package?

Combine batchedUpdates & requestAnimationFrame?

As far as I understand React's batchedUpdates addon can batch multiple state updates into one render. It uses this internally automatically for DOM events (probably only the ones caused by React). By calling this manually you can include updates caused by events that are not automatically batched, like ajax requests and probably events triggered outside of React in general.
This addon doesn't however work with requestAnimationFrame, which means you might have unnecessary rerenders in between frames.
I understand they are working on always batching until animations frames and hoping they can release this soon.

In the meantime it seems like a good idea to combine requestAnimationFrame with batchedUpdates. Did anyone already attempt this?

More info:

usage with unstable_batchedUpdates

Issue:

I would think this code would batch dispatches together that occur synchronously:

import { unstable_batchedUpdates } from 'react-dom';
import { createStore, compose, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import reducer from './reducer';

// this doesn't work as expected
export default createStore(
  reducer,
  compose(
    applyMiddleware(thunk),
    batchedSubscribe(unstable_batchedUpdates),
  ),
);

However, I'm noticing that each dispatch that fires outside of a React event causes a re-render, instead of one re-render once all synchronous dispatches have finished. If I swap out unstable_batchedUpdates for setTimeout, it works:

// this works but is not ideal
export default createStore(
  reducer,
  compose(
    applyMiddleware(thunk),
    batchedSubscribe(setTimeout),
  ),
);

I think unstable_batchedUpdates used to work, is it possible it's failing with a newer version of react or redux?

Env:

  react: 16.3.0,
  redux: 4.0.0

Example: Selective batching based on meta.batch in Action

Following on from the examples of @staab and @peteruithoven. Here is an example that selectively batches actions using requestAnimationFrame, based on the presence of meta.batch in an action. It uses a middleware to inspect actions and call notify().

This allows the batching, for example async actions, whilst letting everything else work as normal.

You can replace rafUpdateBatcher() with LoDash's debounce() if you prefer.

batching/state.js

export default {
  notify: null
}

batching/enhancer.js

import { batchedSubscribe } from 'redux-batched-subscribe'
import State from './state'

export default batchedSubscribe(
  (freshNotify) => {
    State.notify = freshNotify
  }
)

batching/rafUpdateBatcher.js

import raf from 'raf'
import State from './state'

let rafID

function delayedNotify () {
  rafID = null
  State.notify()
}

export default function rafUpdateBatcher () {
  if (rafID) return // prevent multiple request animation frame callbacks
  rafID = raf(delayedNotify)
}

batching/middleware.js

import rafUpdateBatcher from 'rafUpdateBatcher'
import State from './state'

const shouldBatch = action => action.meta && action.meta.batch

export default () => next => (action) => {
  const resolved = next(action)

  if (State.notify && !shouldBatch(action)) {
    State.notify()
  } else {
    rafUpdateBatcher()
  }

  return resolved
}

store.js

import { applyMiddleware, compose, createStore } from 'redux'
import batchedSubscribeMiddleware from './batching/middleware'
import batchedSubscribeEnhancer from './batching/enhancer'

const store = createStore(
  reducer,
  intialState,
  compose(
    batchedSubscribeEnhancer,
    applyMiddleware(batchedSubscribeMiddleware)
  )
)

Example action

{
   type: ACTION,
   payload: { ... },
   meta: { batch: true }
}

Delay slice()ing the listeneres

Redux introduced a few optimizations in 3.x, and we now delay slice()ing listeners unless absolutely necessary, and don’t slice() them more than once per dispatch(). This project should probably adopt similar optimizations.

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.