-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feat: Initial implementation of redux-pauseable-store (#7)
* Scaffolding for redux-pauseable-store * Minimal functionality for redux-pauseable-store * Initial test coverage
- Loading branch information
Showing
16 changed files
with
374 additions
and
28 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,4 @@ | ||
export { default as reduxDecorator } from './reduxDecorator'; | ||
export * from './reduxDecorator'; | ||
|
||
export { default as store } from './store'; | ||
export * from './store'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,12 @@ | ||
import React, { ReactNode } from 'react'; | ||
import React, { ReactNode, useMemo } from 'react'; | ||
import { Provider } from 'react-redux'; | ||
|
||
import store from './store'; | ||
import { createDevHelperStore } from './store'; | ||
|
||
const reduxDecorator = (storyFn: () => ReactNode): ReactNode => { | ||
return <Provider store={store}>{storyFn()}</Provider>; | ||
const devHelperStore = useMemo(createDevHelperStore, []); | ||
|
||
return <Provider store={devHelperStore}>{storyFn()}</Provider>; | ||
}; | ||
|
||
export default reduxDecorator; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,37 +1,77 @@ | ||
import { createStore } from 'redux'; | ||
import { createStore, Store } from 'redux'; | ||
import { composeWithDevTools } from 'redux-devtools-extension'; | ||
|
||
// We only need one action: this is only to demonstrate that updates can be frozen | ||
const COUNT_ACTION = 'COUNT_ACTION'; | ||
const INCREMENT_ACTION = 'INCREMENT_!' as const; | ||
const DECREMENT_ACTION = 'DECREMENT_!' as const; | ||
const SET_ACTION = 'SET_!' as const; | ||
|
||
export type HelperState = { | ||
export type DevHelperState = { | ||
count: number; | ||
lastTimestamp: number; | ||
}; | ||
export type HelperAction = { | ||
type: typeof COUNT_ACTION; | ||
|
||
type DevHelperAction_Increment = { | ||
type: typeof INCREMENT_ACTION; | ||
}; | ||
type DevHelperAction_Decrement = { | ||
type: typeof DECREMENT_ACTION; | ||
}; | ||
type DevHelperAction_Set = { | ||
type: typeof SET_ACTION; | ||
payload: { | ||
newValue: number; | ||
}; | ||
}; | ||
|
||
export type DevHelperAction = | ||
| DevHelperAction_Increment | ||
| DevHelperAction_Decrement | ||
| DevHelperAction_Set; | ||
|
||
const initialState: HelperState = { | ||
const initialState: DevHelperState = { | ||
count: 0, | ||
lastTimestamp: 0, | ||
}; | ||
|
||
const countAction = (): HelperAction => ({ type: COUNT_ACTION }); | ||
const incrementAction = (): DevHelperAction_Increment => ({ type: INCREMENT_ACTION }); | ||
const decrementAction = (): DevHelperAction_Decrement => ({ type: DECREMENT_ACTION }); | ||
const setAction = (newValue: number): DevHelperAction_Set => ({ | ||
type: SET_ACTION, | ||
payload: { newValue }, | ||
}); | ||
|
||
const reducer = (state: HelperState = initialState, action: HelperAction): HelperState => { | ||
const reducer = (state: DevHelperState = initialState, action: DevHelperAction): DevHelperState => { | ||
const { type } = action; | ||
if (type === COUNT_ACTION) { | ||
return { | ||
...state, | ||
count: state.count + 1, | ||
lastTimestamp: Date.now(), | ||
}; | ||
switch (type) { | ||
case INCREMENT_ACTION: { | ||
return { | ||
...state, | ||
count: state.count + 1, | ||
}; | ||
} | ||
|
||
case DECREMENT_ACTION: { | ||
return { | ||
...state, | ||
count: state.count - 1, | ||
}; | ||
} | ||
|
||
case SET_ACTION: { | ||
const { | ||
payload: { newValue }, | ||
} = action as DevHelperAction_Set; | ||
return { | ||
...state, | ||
count: newValue, | ||
}; | ||
} | ||
|
||
default: | ||
break; | ||
} | ||
|
||
return state; | ||
}; | ||
|
||
const store = createStore(reducer, initialState, composeWithDevTools()); | ||
const createDevHelperStore = (): Store => createStore(reducer, initialState, composeWithDevTools()); | ||
|
||
export default store; | ||
export { countAction }; | ||
export { createDevHelperStore, incrementAction, decrementAction, setAction }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# Unless https://github.com/prettier/prettier/issues/4081 is ever resolved, | ||
# this MUST be copied into each package dir for local runs to work. | ||
|
||
# Whitelist supported files only | ||
**/*.* | ||
!**/*.css | ||
!**/*.html | ||
!**/*.js | ||
!**/*.jsx | ||
!**/*.json | ||
!**/*.less | ||
!**/*.md | ||
!**/*.scss | ||
!**/*.ts | ||
!**/*.tsx | ||
|
||
# Unless they're somewhere we can ignore | ||
build/ | ||
dist/ | ||
coverage/ | ||
coverage-local/ | ||
node_modules/ | ||
storybook-static/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
# Redux-Pauseable-Store | ||
|
||
**This package is in active development. Things will change rapidly, and it is not yet production-ready. Feedback is welcome.** | ||
|
||
Derive one redux store from another, then pause it. | ||
|
||
Part of [React Hibernate](../../) | ||
|
||
[![npm version](https://img.shields.io/npm/v/redux-pauseable-store.svg)](https://www.npmjs.com/package/redux-pauseable-store) | ||
[![gzip size](https://img.shields.io/bundlephobia/minzip/redux-pauseable-store)](https://bundlephobia.com/result?p=redux-pauseable-store@latest) | ||
|
||
## Danger | ||
|
||
You probably don't need this library. When used incorrectly it will do more harm than good. | ||
|
||
Consider using [`<PauseableReduxContainer>`](../react-pauseable-containers#pauseablereduxcontainer) | ||
from [React Pauseable Containers](../react-pauseable-containers) instead. | ||
|
||
## Overview | ||
|
||
Whenever Redux updates, its new data will be provided to all components that are subscribed to it. This library | ||
interrupts that. | ||
|
||
## Details | ||
|
||
If you want to **completely** stop a subtree from rerendering for some time -- as [React Router Hibernate](../react-router-hibernate/), | ||
[React hibernate](../react-hibernate), and [React Pauseable Containers](../react-pauseable-containers) do -- then you | ||
have to prevent anything which would trigger a state change in any component in that subtree. | ||
|
||
React-Redux's [useSelector hook](https://react-redux.js.org/next/api/hooks#useselector) works by | ||
[forcing a rerender](https://github.com/reduxjs/react-redux/blob/5402f24db139f7ff01c7f873d136ea7ee3b8d1cb/src/hooks/useSelector.js#L15) | ||
outside of the normal render cycle: it triggers a state change. | ||
|
||
This library overwrites the context provider for React Redux to trick it into subscribing to a second Redux store -- | ||
which we can then pause and unpause. | ||
|
||
## Usage | ||
|
||
``` | ||
// @TODO | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
/* eslint-env node */ | ||
const baseConfig = require('../../jest-base.config'); | ||
|
||
module.exports = { | ||
...baseConfig, | ||
coverageDirectory: 'coverage-local', | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
{ | ||
"name": "redux-pauseable-store", | ||
"version": "0.0.2", | ||
"description": "Derive one redux store from another, then pause it", | ||
"keywords": [], | ||
"license": "MIT", | ||
"homepage": "https://github.com/spautz/react-hibernate/packages/redux-pauseable-store#readme", | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/spautz/react-hibernate.git" | ||
}, | ||
"bugs": { | ||
"url": "https://github.com/spautz/react-hibernate/issues" | ||
}, | ||
"author": "Steven Pautz <spautz@gmail.com>", | ||
"files": [ | ||
"dist/", | ||
"src/", | ||
"LICENSE", | ||
"README.md" | ||
], | ||
"source": "src/index.ts", | ||
"main": "dist/index.js", | ||
"module": "dist/index.esm.js", | ||
"jsnext:main": "dist/index.esm.js", | ||
"types": "dist/index.d.ts", | ||
"sideEffects": false, | ||
"scripts": { | ||
"____ BASE COMMANDS _________________________________________________": "", | ||
"build": "microbundle build --jsx React.createElement", | ||
"build:watch": "microbundle watch --jsx React.createElement", | ||
"clean": "rimraf dist/ node_modules/.cache/", | ||
"format": "prettier --write \"**/*.*\"", | ||
"format:checkup": "prettier --list-different \"**/*.*\"", | ||
"lint": "eslint \"**/*.{js,jsx,json,ts,tsx}\"", | ||
"release": "standard-version --sign --release-as ", | ||
"test": "jest", | ||
"test:clean": "rimraf coverage-local/", | ||
"test:coverage": "jest --coverage", | ||
"test:watch": "jest --watch", | ||
"types": "tsc --noEmit --p tsconfig.json --jsx react", | ||
"____ HOOKS _________________________________________________________": "", | ||
"prepare": "yon build", | ||
"prebuild": "yon clean", | ||
"prerelease": "yon clean", | ||
"prepublishOnly": "yon checkup && yon build", | ||
"pretest": "yon test:clean", | ||
"____ INTEGRATION ___________________________________________________": "", | ||
"dev": "yon run format && yon run types && yon run lint", | ||
"checkup": "yon format:checkup && yon run types && yon run lint", | ||
"all": "yon run dev && yon run test:coverage && yon run build" | ||
}, | ||
"dependencies": {}, | ||
"devDependencies": {}, | ||
"peerDependencies": { | ||
"redux": "^4.0.0" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
/* eslint-env node */ | ||
const baseConfig = require('../../prettier.config'); | ||
|
||
module.exports = baseConfig; |
81 changes: 81 additions & 0 deletions
81
packages/redux-pauseable-store/src/createPauseableStore.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
import { Action, Store } from 'redux'; | ||
|
||
import { PauseableStoreInstance, PauseableStoreOptions } from './types'; | ||
|
||
const createPauseableStore = ( | ||
parentStore: Store, | ||
options?: PauseableStoreOptions, | ||
): PauseableStoreInstance => { | ||
const { isPaused: isInitiallyPaused = false, canDispatch: canInitiallyDispatch = 'warn' } = | ||
options || {}; | ||
|
||
const pauseableStore = {} as PauseableStoreInstance; | ||
let stateAtPause = isInitiallyPaused ? parentStore.getState() : null; | ||
|
||
const dispatch = (action: Action) => { | ||
if (pauseableStore.canDispatch === 'warn') { | ||
console.warn( | ||
'Something is trying to dispatch an action to a PauseableStore. Set `canDispatch` to true or false to disable this warning.', | ||
{ action, pauseableStore }, | ||
); | ||
} | ||
if (pauseableStore.canDispatch) { | ||
return parentStore.dispatch(action); | ||
} | ||
return null; | ||
}; | ||
|
||
const subscribe = (listener: () => void) => { | ||
return parentStore.subscribe(() => { | ||
// Ignore when paused | ||
if (!pauseableStore.isPaused) { | ||
listener(); | ||
} | ||
}); | ||
}; | ||
|
||
const getState = () => { | ||
if (pauseableStore.isPaused) { | ||
return stateAtPause; | ||
} | ||
return parentStore.getState(); | ||
}; | ||
|
||
const setPaused = (newIsPaused: boolean) => { | ||
pauseableStore.isPaused = newIsPaused; | ||
|
||
stateAtPause = newIsPaused ? parentStore.getState() : null; | ||
}; | ||
|
||
const setDispatch = (newCanDispatch: boolean | 'warn') => { | ||
pauseableStore.canDispatch = newCanDispatch; | ||
}; | ||
|
||
const replaceReducer = () => { | ||
throw new Error( | ||
'Cannot replaceReducer on a pauseableStore: replaceReducer on the parent store, instead', | ||
); | ||
}; | ||
|
||
Object.assign(pauseableStore, { | ||
// Redux Store interface | ||
...parentStore, | ||
dispatch, | ||
subscribe, | ||
getState, | ||
replaceReducer, | ||
// @TODO: Do we also need to handle [$$observable]: observable, ? | ||
|
||
// PauseableStore additions | ||
isPaused: isInitiallyPaused, | ||
setPaused, | ||
canDispatch: canInitiallyDispatch, | ||
setDispatch, | ||
|
||
_parentStore: parentStore, | ||
}); | ||
|
||
return pauseableStore; | ||
}; | ||
|
||
export default createPauseableStore; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export { default as createPauseableStore } from './createPauseableStore'; | ||
export * from './createPauseableStore'; | ||
|
||
export * from './types'; |
Oops, something went wrong.