redux-react-hook
Advanced tools
Comparing version 1.0.0 to 2.0.0
@@ -0,3 +1,5 @@ | ||
/// <reference path="src/typings.d.ts" /> | ||
/// <reference types="react" /> | ||
import { Action, Dispatch, Store } from 'redux'; | ||
export declare const StoreProvider: import("react").ComponentType<import("react").ProviderProps<Store<any, import("redux").AnyAction> | null>>; | ||
/** | ||
@@ -16,3 +18,3 @@ * Your passed in mapState function should be memoized to avoid | ||
*/ | ||
export declare function useMappedState<TState, TResult>(storeContext: React.Context<Store<TState>>, mapState: (state: TState) => TResult): TResult; | ||
export declare function useDispatch<TAction extends Action>(storeContext: React.Context<Store<any, TAction>>): Dispatch<TAction>; | ||
export declare function useMappedState<TState, TResult>(mapState: (state: TState) => TResult): TResult; | ||
export declare function useDispatch<TAction extends Action>(): Dispatch<TAction>; |
@@ -1,30 +0,3 @@ | ||
function __$styleInject(css, ref) { | ||
if ( ref === void 0 ) ref = {}; | ||
var insertAt = ref.insertAt; | ||
import { createContext, useContext, useEffect, useRef, useState } from 'react'; | ||
if (!css || typeof document === 'undefined') { return; } | ||
var head = document.head || document.getElementsByTagName('head')[0]; | ||
var style = document.createElement('style'); | ||
style.type = 'text/css'; | ||
if (insertAt === 'top') { | ||
if (head.firstChild) { | ||
head.insertBefore(style, head.firstChild); | ||
} else { | ||
head.appendChild(style); | ||
} | ||
} else { | ||
head.appendChild(style); | ||
} | ||
if (style.styleSheet) { | ||
style.styleSheet.cssText = css; | ||
} else { | ||
style.appendChild(document.createTextNode(css)); | ||
} | ||
} | ||
import { useContext, useEffect, useRef, useState } from 'react'; | ||
// From https://github.com/reduxjs/react-redux/blob/3e53ff96ed10f71c21346f08823e503df724db35/src/utils/shallowEqual.js | ||
@@ -41,4 +14,5 @@ var hasOwn = Object.prototype.hasOwnProperty; | ||
function shallowEqual(objA, objB) { | ||
if (is(objA, objB)) | ||
if (is(objA, objB)) { | ||
return true; | ||
} | ||
if (typeof objA !== 'object' || | ||
@@ -52,4 +26,6 @@ objA === null || | ||
var keysB = Object.keys(objB); | ||
if (keysA.length !== keysB.length) | ||
if (keysA.length !== keysB.length) { | ||
return false; | ||
} | ||
// tslint:disable-next-line:prefer-for-of | ||
for (var i = 0; i < keysA.length; i++) { | ||
@@ -63,2 +39,4 @@ if (!hasOwn.call(objB, keysA[i]) || !is(objA[keysA[i]], objB[keysA[i]])) { | ||
var Context = createContext(null); | ||
var StoreProvider = Context.Provider; | ||
/** | ||
@@ -77,4 +55,7 @@ * Your passed in mapState function should be memoized to avoid | ||
*/ | ||
function useMappedState(storeContext, mapState) { | ||
var store = useContext(storeContext); | ||
function useMappedState(mapState) { | ||
var store = useContext(Context); | ||
if (!store) { | ||
throw new Error('redux-react-hook requires your Redux store to be passed through context via the <StoreProvider>'); | ||
} | ||
var runMapState = function () { return mapState(store.getState()); }; | ||
@@ -120,8 +101,11 @@ var _a = useState(function () { return runMapState(); }), mappedState = _a[0], setMappedState = _a[1]; | ||
} | ||
function useDispatch(storeContext) { | ||
var store = useContext(storeContext); | ||
function useDispatch() { | ||
var store = useContext(Context); | ||
if (!store) { | ||
throw new Error('redux-react-hook requires your Redux store to be passed through context via the <StoreProvider>'); | ||
} | ||
return store.dispatch; | ||
} | ||
export { useMappedState, useDispatch }; | ||
export { StoreProvider, useMappedState, useDispatch }; | ||
//# sourceMappingURL=index.es.js.map |
'use strict'; | ||
function __$styleInject(css, ref) { | ||
if ( ref === void 0 ) ref = {}; | ||
var insertAt = ref.insertAt; | ||
if (!css || typeof document === 'undefined') { return; } | ||
var head = document.head || document.getElementsByTagName('head')[0]; | ||
var style = document.createElement('style'); | ||
style.type = 'text/css'; | ||
if (insertAt === 'top') { | ||
if (head.firstChild) { | ||
head.insertBefore(style, head.firstChild); | ||
} else { | ||
head.appendChild(style); | ||
} | ||
} else { | ||
head.appendChild(style); | ||
} | ||
if (style.styleSheet) { | ||
style.styleSheet.cssText = css; | ||
} else { | ||
style.appendChild(document.createTextNode(css)); | ||
} | ||
} | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
@@ -45,4 +18,5 @@ | ||
function shallowEqual(objA, objB) { | ||
if (is(objA, objB)) | ||
if (is(objA, objB)) { | ||
return true; | ||
} | ||
if (typeof objA !== 'object' || | ||
@@ -56,4 +30,6 @@ objA === null || | ||
var keysB = Object.keys(objB); | ||
if (keysA.length !== keysB.length) | ||
if (keysA.length !== keysB.length) { | ||
return false; | ||
} | ||
// tslint:disable-next-line:prefer-for-of | ||
for (var i = 0; i < keysA.length; i++) { | ||
@@ -67,2 +43,4 @@ if (!hasOwn.call(objB, keysA[i]) || !is(objA[keysA[i]], objB[keysA[i]])) { | ||
var Context = react.createContext(null); | ||
var StoreProvider = Context.Provider; | ||
/** | ||
@@ -81,4 +59,7 @@ * Your passed in mapState function should be memoized to avoid | ||
*/ | ||
function useMappedState(storeContext, mapState) { | ||
var store = react.useContext(storeContext); | ||
function useMappedState(mapState) { | ||
var store = react.useContext(Context); | ||
if (!store) { | ||
throw new Error('redux-react-hook requires your Redux store to be passed through context via the <StoreProvider>'); | ||
} | ||
var runMapState = function () { return mapState(store.getState()); }; | ||
@@ -124,9 +105,13 @@ var _a = react.useState(function () { return runMapState(); }), mappedState = _a[0], setMappedState = _a[1]; | ||
} | ||
function useDispatch(storeContext) { | ||
var store = react.useContext(storeContext); | ||
function useDispatch() { | ||
var store = react.useContext(Context); | ||
if (!store) { | ||
throw new Error('redux-react-hook requires your Redux store to be passed through context via the <StoreProvider>'); | ||
} | ||
return store.dispatch; | ||
} | ||
exports.StoreProvider = StoreProvider; | ||
exports.useMappedState = useMappedState; | ||
exports.useDispatch = useDispatch; | ||
//# sourceMappingURL=index.js.map |
{ | ||
"name": "redux-react-hook", | ||
"version": "1.0.0", | ||
"version": "2.0.0", | ||
"description": "React hook for accessing a Redux store.", | ||
@@ -37,14 +37,16 @@ "author": "ianobermiller", | ||
"cross-env": "^5.1.4", | ||
"gh-pages": "^1.2.0", | ||
"gh-pages": "^2.0.1", | ||
"react": "^16.7.0-alpha.0", | ||
"react-dom": "^16.7.0-alpha.0", | ||
"react-scripts-ts": "^2.16.0", | ||
"rollup": "^0.62.0", | ||
"rollup-plugin-babel": "^3.0.7", | ||
"react-scripts-ts": "^3.1.0", | ||
"redux": "^4.0.1", | ||
"rollup": "^0.66.6", | ||
"rollup-plugin-babel": "^4.0.3", | ||
"rollup-plugin-commonjs": "^9.1.3", | ||
"rollup-plugin-cpy": "^1.1.0", | ||
"rollup-plugin-node-resolve": "^3.3.0", | ||
"rollup-plugin-peer-deps-external": "^2.2.0", | ||
"rollup-plugin-postcss-modules": "1.0.8", | ||
"rollup-plugin-typescript2": "^0.13.0", | ||
"rollup-plugin-url": "^1.4.0", | ||
"typescript": "^2.8.3" | ||
"rollup-plugin-typescript2": "^0.17.2", | ||
"rollup-plugin-url": "^2.0.1", | ||
"typescript": "^3.1.3" | ||
}, | ||
@@ -51,0 +53,0 @@ "files": [ |
108
README.md
# redux-react-hook | ||
> React hook for accessing mapped state from a Redux store. Basically a hooks version of `react-redux`. | ||
> React hook for accessing mapped state from a Redux store. | ||
[](https://www.npmjs.com/package/redux-react-hook) | ||
[](https://bundlephobia.com/result?p=redux-react-hook) | ||
@@ -19,9 +20,9 @@ ## Install | ||
All the hooks take a `storeContext` as the first parameter, which should be a | ||
context object, the value returned by `React.createContext(...)`, that contains | ||
your Redux store. See Custom Wrappers below to make this less cumbersome. | ||
NOTE: React hooks currently require `react` and `react-dom` version `16.7.0-alpha.0` or higher. | ||
In order to use the hooks, your Redux store must be in available in the React context from `StoreProvider`. | ||
### Store in Context | ||
Before you can use the hook, you must put your Redux store into `Context`: | ||
Before you can use the hook, you must provide your Redux store via `StoreProvider`: | ||
@@ -31,3 +32,2 @@ ```tsx | ||
import React from 'react'; | ||
import {createStore} from 'redux'; | ||
@@ -39,4 +39,2 @@ import reducer from './reducer'; | ||
} | ||
export const Context = React.createContext(null); | ||
``` | ||
@@ -47,3 +45,4 @@ | ||
import {Context, makeStore} from './Store'; | ||
import {StoreProvider} from 'redux-react-hook'; | ||
import {makeStore} from './Store'; | ||
@@ -53,5 +52,5 @@ const store = makeStore(); | ||
ReactDOM.render( | ||
<Context.Provider value={store}> | ||
<StoreProvider value={store}> | ||
<App /> | ||
</Context.Provider>, | ||
</StoreProvider>, | ||
document.getElementById('root'), | ||
@@ -61,3 +60,3 @@ ); | ||
### `useMappedState(storeContext, mapState)` | ||
### `useMappedState(mapState)` | ||
@@ -68,3 +67,3 @@ Runs the given `mapState` function against your store state, just like | ||
```tsx | ||
const state = useMappedState(storeContext, mapState); | ||
const state = useMappedState(mapState); | ||
``` | ||
@@ -77,3 +76,2 @@ | ||
import {useMappedState} from 'redux-react-hook'; | ||
import {Context} from './Store'; | ||
@@ -89,3 +87,3 @@ // Note how mapState is declared outside of the function -- this is critical, as | ||
export default function TodoSummary() { | ||
const {lastUpdated, todoCount} = useMappedState(Context, mapState); | ||
const {lastUpdated, todoCount} = useMappedState(mapState); | ||
return ( | ||
@@ -105,9 +103,8 @@ <div> | ||
import {useMappedState} from 'redux-react-hook'; | ||
import {Context} from './Store'; | ||
function TodoItem({index}) { | ||
// Note that we pass the index as a memoization parameter -- this causes | ||
// Note that we pass the index as a dependency parameter -- this causes | ||
// useCallback to return the same function every time unless index changes. | ||
const mapState = useCallback(state => state.todos[index], [index]); | ||
const todo = useMappedState(storeContext, mapState); | ||
const todo = useMappedState(mapState); | ||
@@ -118,3 +115,3 @@ return <li>{todo}</li>; | ||
### `useDispatch(storeContext)` | ||
### `useDispatch()` | ||
@@ -125,6 +122,5 @@ Simply returns the dispatch method. | ||
import {useMappedState} from 'redux-react-hook'; | ||
import {Context} from './Store'; | ||
function DeleteButton({index}) { | ||
const dispatch = useDispatch(Context); | ||
const dispatch = useDispatch(); | ||
const deleteTodo = useCallback(() => dispatch({type: 'delete todo', index}), [ | ||
@@ -138,43 +134,2 @@ index, | ||
### Custom wrappers | ||
To avoid having to pass in a `storeContext` with every call, we recommend adding | ||
project specific wrappers for `useMappedState` and `useDispatch`: | ||
```tsx | ||
// Store.js | ||
import React from 'react'; | ||
import { | ||
useDispatch as useDispatchGeneric, | ||
useMappedState as useMappedStateGeneric, | ||
} from 'redux-react-hook'; | ||
export const Context = React.createContext(null); | ||
export function useMappedState(mapState) { | ||
return useMappedStateGeneric(Context, mapState); | ||
} | ||
export function useDispatch() { | ||
return useDispatchGeneric(Context); | ||
} | ||
``` | ||
The `useMappedState` wrapper is also an ideal place to restrict the store state | ||
that you want passed to `mapState`. For example, if your store schema has an | ||
undo stack, and you only want to pass the current state. | ||
```tsx | ||
export function useMappedState(mapState) { | ||
const mapRestrictedState = useCallback( | ||
fullState => mapState(fullState.currentState), | ||
[mapState], | ||
); | ||
return useMappedStateGeneric(Context, mapRestrictedState); | ||
} | ||
``` | ||
See the example project for the full code. | ||
## Example | ||
@@ -185,2 +140,7 @@ | ||
```bash | ||
# In one terminal, run `yarn start` in the root to rebuild the library itself | ||
cd ./redux-react-example | ||
yarn start | ||
# In another terminal, run `yarn start` in the `example` folder | ||
cd example | ||
@@ -198,11 +158,25 @@ yarn start | ||
## Future | ||
## More info | ||
Contributions welcome! | ||
Hooks are really new, and we are just beginning to see what people do with them. There is an [open issue on `react-redux`](https://github.com/reduxjs/react-redux/issues/1063) discussing the potential. Here are some other projects that are adding hooks for Redux: | ||
- Add `useMappedDispatch`, which is analagous to `mapDispatchToProps` | ||
- Add tests using Karma (hooks don't seem to run correctly in JSDOM yet) | ||
- [`use-substate`](https://github.com/philipp-spiess/use-substate) | ||
- [`react-use-redux`](https://github.com/martynaskadisa/react-use-redux) | ||
## Thanks | ||
Special thanks to @sawyerhood and @sophiebits for writing most of the hook! This repo was setup with the help of the excellent [`create-react-library`](https://www.npmjs.com/package/create-react-library). | ||
## Contributing | ||
Contributions are definitely welcome! Check out the [issues](https://github.com/ianobermiller/redux-react-hook/issues) | ||
for ideas on where you can contribute. | ||
## Changelog | ||
- v2.0.0 - Export `StoreProvider` instead of requiring you to pass in context | ||
- v1.0.0 - Initial release | ||
## License | ||
MIT © [ianobermiller](https://github.com/ianobermiller) | ||
MIT © Facebook Inc. |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
30437
11
0
20
245
168