├── .editorconfig ├── .eslintrc.js ├── .gitignore ├── .prettierrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── example ├── .env ├── .gitignore ├── README.md ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ └── manifest.json ├── src │ ├── Counter.js │ └── index.js └── yarn.lock ├── package.json ├── src ├── StateInspector.tsx ├── context.ts ├── index.ts ├── useReducer.ts └── useState.ts ├── tests ├── index.test.tsx ├── test.setup.js └── tsconfig.json ├── tsconfig.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | trim_trailing_whitespace = true 3 | insert_final_newline = true -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | "react-app", 4 | "prettier/@typescript-eslint", 5 | "plugin:prettier/recommended" 6 | ], 7 | settings: { 8 | react: { 9 | version: "detect" 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | /node_modules 3 | .vscode 4 | *.log 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "semi": false 4 | } 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v1.1.0 2 | 3 | Maintenance release: 4 | 5 | - Update dependencies 6 | - Move to tsdx 7 | - TypeScript strict mode 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Thomas Roch 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # reinspect 2 | 3 | > Under development 4 | 5 | Connect React state hooks (`useState` and `useReducer`) to redux dev tools. 6 | 7 | [See it live](https://7ypv9qw6j0.codesandbox.io/) 8 | 9 | ![useState with Redux dev tools](https://user-images.githubusercontent.com/1777517/49508706-4b223900-f87b-11e8-9c64-534e3dc51047.gif) 10 | 11 | ## Why? 12 | 13 | Hooks are great, they are a joy to use to create state in components. On the other hand, with something global and centralised like Redux, we have [great dev tools](https://github.com/zalmoxisus/redux-devtools-extension). 14 | 15 | Why not both? That's exactly what this package offers: connecting `useState` and `useReducer` to redux dev tools, so you can do the following: 16 | 17 | - Inspect actions and state for each hook 18 | - Time travel through state changes in your app 19 | - Hot reloading: save your current state and re-inject it when re-rendering 20 | 21 | ## API 22 | 23 | You need [redux devtools](https://github.com/zalmoxisus/redux-devtools-extension) installed. This package provides: 24 | 25 | - `StateInspector`: a provider which will be used by `useState` and `useReducer` to connect them to a store and redux dev tools. 26 | 27 | - It accepts optionally a `name` (name of the store in dev tools) and `initialState` (if you want to start with a given state) 28 | - You can have more than one `StateInspector` in your application, hooks will report to the nearest one 29 | - Without a `StateInspector`, `useState` and `useReducer` behave normally 30 | 31 | ```js 32 | import React from "react" 33 | import { StateInspector } from "reinspect" 34 | import App from "./App" 35 | 36 | function AppWrapper() { 37 | return ( 38 | 39 | 40 | 41 | ) 42 | } 43 | 44 | export default AppWrapper 45 | ``` 46 | 47 | - `useState(initialState, id?)`: like [useState](https://reactjs.org/docs/hooks-reference.html#usestate) but with a 2nd argument `id` (a unique ID to identify it in dev tools). If no `id` is supplied, the hook won't be connected to dev tools. 48 | 49 | ```js 50 | import React from "react" 51 | import { useState } from "reinspect" 52 | 53 | export function CounterWithUseState({ id }) { 54 | const [count, setCount] = useState(0, id) 55 | 56 | return ( 57 |
58 | 59 | {count} 60 |
61 | ) 62 | } 63 | ``` 64 | 65 | - `useReducer(reducer, initialState, initializer?, id?)`: like [useReducer](https://reactjs.org/docs/hooks-reference.html#usereducer) but with a 4th argument `id` (a unique ID to identify it in dev tools). If no `id` is supplied, the hook won't be connected to dev tools. You can use identity function (`state => state`) as 3rd parameter to mock lazy initialization. 66 | -------------------------------------------------------------------------------- /example/.env: -------------------------------------------------------------------------------- 1 | SKIP_PREFLIGHT_CHECK=true 2 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # Reinspect example 2 | 3 | To run: 4 | 5 | - install dependencies with yarn (`yarn`) 6 | - run the example with `yarn start` 7 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "react": "~16.13.0", 7 | "react-dom": "~16.13.0", 8 | "react-scripts": "3.4.0", 9 | "reinspect": "file:../dist" 10 | }, 11 | "scripts": { 12 | "start": "react-scripts start" 13 | }, 14 | "eslintConfig": { 15 | "extends": "react-app" 16 | }, 17 | "browserslist": [ 18 | ">0.2%", 19 | "not dead", 20 | "not ie <= 11", 21 | "not op_mini all" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /example/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/troch/reinspect/4d0b4160afe6f51596b984ba9fde737d609ddedd/example/public/favicon.ico -------------------------------------------------------------------------------- /example/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 22 | React App 23 | 24 | 25 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /example/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /example/src/Counter.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { useState } from "reinspect" 3 | 4 | export function Counter({ id }) { 5 | const [count, setCount] = useState(0, id) 6 | 7 | return ( 8 |
9 | {count}{" "} 10 | 11 |
12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /example/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import ReactDOM from "react-dom" 3 | import { StateInspector, useState } from "reinspect" 4 | import { Counter } from "./Counter" 5 | 6 | function App() { 7 | return ( 8 | 9 | 10 | 11 | ) 12 | } 13 | 14 | function Counters() { 15 | const [count, setCount] = useState(4, "count") 16 | 17 | return ( 18 |
19 | {window.__REDUX_DEVTOOLS_EXTENSION__ ? ( 20 |

Open redux devtools to see it in action!

21 | ) : ( 22 |

23 | You need Redux dev tools:{" "} 24 | 25 | https://github.com/zalmoxisus/redux-devtools-extension 26 | 27 |

28 | )} 29 |

30 | 31 | 32 | 35 |

36 | 37 | {Array.from({ length: count }).map((_, index) => ( 38 | 39 | ))} 40 |
41 | ) 42 | } 43 | 44 | ReactDOM.render(, document.getElementById("root")) 45 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reinspect", 3 | "version": "1.1.0", 4 | "description": "Use redux devtools to inspect useState and useReducer hooks", 5 | "files": [ 6 | "dist" 7 | ], 8 | "main": "dist/index.js", 9 | "module": "dist/reinspect.esm.js", 10 | "typings": "dist/index.d.ts", 11 | "repository": "git@github.com:troch/reinspect.git", 12 | "author": "Thomas Roch ", 13 | "license": "MIT", 14 | "scripts": { 15 | "start": "tsdx watch", 16 | "build": "tsdx build", 17 | "test": "tsdx test", 18 | "lint": "tsdx lint src tests" 19 | }, 20 | "devDependencies": { 21 | "tsdx": "0.12.3", 22 | "@types/enzyme": "~3.9.1", 23 | "@types/enzyme-adapter-react-16": "~1.0.5", 24 | "@types/jest": "~24.0.11", 25 | "@types/react": "~16.8.3", 26 | "conventional-changelog-cli": "~2.0.12", 27 | "enzyme": "~3.9.0", 28 | "enzyme-adapter-react-16": "~1.12.1", 29 | "husky": "^4.2.3", 30 | "react": "~16.8.6", 31 | "react-dom": "~16.8.6", 32 | "react-test-renderer": "~16.8.2", 33 | "typescript": "~3.8.3" 34 | }, 35 | "dependencies": { 36 | "redux": "^4.0.5" 37 | }, 38 | "peerDependencies": { 39 | "react": ">=16.8.1" 40 | }, 41 | "jest": { 42 | "preset": "ts-jest", 43 | "testEnvironment": "jsdom", 44 | "setupFiles": [ 45 | "/tests/test.setup.js" 46 | ], 47 | "globals": { 48 | "ts-jest": { 49 | "tsConfig": "/tests/tsconfig.json" 50 | } 51 | } 52 | }, 53 | "husky": { 54 | "hooks": { 55 | "pre-commit": "tsdx lint src tests" 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/StateInspector.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReducerAction, Reducer, useMemo, useEffect } from "react" 2 | import { createStore } from "redux" 3 | import { EnhancedStore, StateInspectorContext } from "./context" 4 | 5 | declare global { 6 | interface Window { 7 | __REDUX_DEVTOOLS_EXTENSION__?: any 8 | } 9 | } 10 | 11 | interface StateInspectorProps { 12 | name?: string 13 | initialState?: any 14 | } 15 | 16 | interface StoreReducerAction { 17 | type: string 18 | payload: any 19 | } 20 | 21 | const omit = (obj: Record, keyToRemove: string) => 22 | Object.keys(obj) 23 | .filter(key => key !== keyToRemove) 24 | .reduce>((acc, key) => { 25 | acc[key] = obj[key] 26 | 27 | return acc 28 | }, {}) 29 | 30 | export const StateInspector: React.FC = ({ 31 | name, 32 | initialState = {}, 33 | children 34 | }) => { 35 | const store = useMemo(() => { 36 | if (typeof window === "undefined" || !window.__REDUX_DEVTOOLS_EXTENSION__) { 37 | return undefined 38 | } 39 | 40 | const registeredReducers: Record< 41 | string | number, 42 | Reducer> 43 | > = {} 44 | 45 | const storeReducer: Reducer = (state, action) => { 46 | const actionReducerId = action.type.split("/")[0] 47 | const isInitAction = /\/_init$/.test(action.type) 48 | const isTeardownAction = /\/_teardown$/.test(action.type) 49 | 50 | const currentState = isTeardownAction 51 | ? omit(state, actionReducerId) 52 | : { ...state } 53 | 54 | return Object.keys(registeredReducers).reduce((acc, reducerId) => { 55 | const reducer = registeredReducers[reducerId] 56 | const reducerState = state[reducerId] 57 | const reducerAction = action.payload 58 | const isForCurrentReducer = actionReducerId === reducerId 59 | 60 | if (isForCurrentReducer) { 61 | acc[reducerId] = isInitAction 62 | ? action.payload 63 | : reducer(reducerState, reducerAction) 64 | } else { 65 | acc[reducerId] = reducerState 66 | } 67 | 68 | return acc 69 | }, currentState) 70 | } 71 | 72 | const store: EnhancedStore = createStore( 73 | storeReducer, 74 | initialState, 75 | window.__REDUX_DEVTOOLS_EXTENSION__({ 76 | name: name || "React state", 77 | actionsBlacklist: ["/_init", "/_teardown"] 78 | }) 79 | ) 80 | 81 | store.registerHookedReducer = (reducer, initialState, reducerId) => { 82 | registeredReducers[reducerId] = reducer 83 | 84 | store.dispatch({ 85 | type: `${reducerId}/_init`, 86 | payload: initialState 87 | }) 88 | 89 | return () => { 90 | delete registeredReducers[reducerId] 91 | 92 | store.dispatch({ 93 | type: `${reducerId}/_teardown` 94 | }) 95 | } 96 | } 97 | 98 | return store 99 | }, []) 100 | 101 | useEffect(() => { 102 | store && store.dispatch({ type: "REINSPECT/@@INIT", payload: {} }) 103 | }, []) 104 | 105 | return ( 106 | 107 | {children} 108 | 109 | ) 110 | } 111 | -------------------------------------------------------------------------------- /src/context.ts: -------------------------------------------------------------------------------- 1 | import React, { Reducer } from "react" 2 | import { Store } from "redux" 3 | 4 | type UnsubscribeFn = () => void 5 | 6 | export type EnhancedStore = Store & { 7 | registerHookedReducer: ( 8 | reducer: Reducer, 9 | initialState: any, 10 | reducerId: string | number 11 | ) => UnsubscribeFn 12 | } 13 | 14 | export const StateInspectorContext = React.createContext< 15 | EnhancedStore | undefined 16 | >(undefined) 17 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { StateInspector } from "./StateInspector" 2 | export { useReducer } from "./useReducer" 3 | export { useState } from "./useState" 4 | -------------------------------------------------------------------------------- /src/useReducer.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable react-hooks/rules-of-hooks */ 2 | import { 3 | useReducer as useReactReducer, 4 | Reducer, 5 | useMemo, 6 | Dispatch, 7 | useState, 8 | useEffect, 9 | useContext, 10 | ReducerState, 11 | ReducerAction 12 | } from "react" 13 | import { StateInspectorContext, EnhancedStore } from "./context" 14 | 15 | export function useHookedReducer( 16 | reducer: Reducer, 17 | initialState: S, 18 | store: EnhancedStore, 19 | reducerId: string | number 20 | ): [S, Dispatch] { 21 | const initialReducerState = useMemo(() => { 22 | const initialStateInStore = store.getState()[reducerId] 23 | return initialStateInStore === undefined 24 | ? initialState 25 | : initialStateInStore 26 | }, []) 27 | 28 | const [localState, setState] = useState(initialReducerState) 29 | 30 | const dispatch = useMemo>(() => { 31 | const dispatch = (action: any) => { 32 | if ( 33 | action && 34 | typeof action === "object" && 35 | typeof action.type === "string" 36 | ) { 37 | store.dispatch({ 38 | type: `${reducerId}/${action.type}`, 39 | payload: action 40 | }) 41 | } else { 42 | store.dispatch({ 43 | type: reducerId, 44 | payload: action 45 | }) 46 | } 47 | } 48 | 49 | return dispatch 50 | }, []) 51 | 52 | useEffect(() => { 53 | const teardown = store.registerHookedReducer( 54 | reducer, 55 | initialReducerState, 56 | reducerId 57 | ) 58 | 59 | let lastReducerState = localState 60 | const unsubscribe = store.subscribe(() => { 61 | const storeState: any = store.getState() 62 | const reducerState = storeState[reducerId] 63 | 64 | if (lastReducerState !== reducerState) { 65 | setState(reducerState) 66 | } 67 | 68 | lastReducerState = reducerState 69 | }) 70 | 71 | return () => { 72 | unsubscribe() 73 | teardown() 74 | } 75 | }, []) 76 | 77 | return [localState, dispatch] 78 | } 79 | 80 | export function useReducer>( 81 | reducer: R, 82 | initialState: ReducerState, 83 | id?: string | number 84 | ): [ReducerState, Dispatch>] 85 | export function useReducer, I>( 86 | reducer: R, 87 | initialState: I, 88 | initializer: (arg: I) => ReducerState, 89 | id?: string | number 90 | ): [ReducerState, Dispatch>] 91 | export function useReducer, I>( 92 | reducer: R, 93 | initialState: I & ReducerState, 94 | initializer: (arg: I & ReducerState) => ReducerState, 95 | id?: string | number 96 | ): [ReducerState, Dispatch>] 97 | export function useReducer, I>( 98 | reducer: R, 99 | initialState: I & ReducerState, 100 | ...args: any[] 101 | ) { 102 | let id: string | number | undefined 103 | let initializer: (arg: I | (I & ReducerState)) => ReducerState = args[0] 104 | 105 | if (args.length === 2) { 106 | initializer = args[0] 107 | id = args[1] 108 | } else if (typeof args[0] === "string" || typeof args[0] === "number") { 109 | id = args[0] 110 | } else { 111 | initializer = args[0] 112 | id = args[1] 113 | } 114 | 115 | const store = useContext(StateInspectorContext) 116 | 117 | const initializedState = initializer 118 | ? initializer(initialState) 119 | : initialState 120 | 121 | return store && id 122 | ? useHookedReducer(reducer, initializedState, store, id) 123 | : useReactReducer(reducer, initialState, initializer) 124 | } 125 | -------------------------------------------------------------------------------- /src/useState.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable react-hooks/rules-of-hooks */ 2 | import { useHookedReducer } from "./useReducer" 3 | import { useMemo, useContext, useState as useReactState } from "react" 4 | import { EnhancedStore, StateInspectorContext } from "./context" 5 | 6 | type StateAction = S | ((s: S) => S) 7 | 8 | function stateReducer(state: S, action: StateAction): S { 9 | return typeof action === "function" ? (action as (s: S) => S)(state) : action 10 | } 11 | 12 | export const useState = ( 13 | initialState: S | (() => S), 14 | id: string | number 15 | ) => { 16 | const inspectorStore = useContext(StateInspectorContext) 17 | // Keeping the first values 18 | const [store, reducerId] = useMemo< 19 | [EnhancedStore | undefined, string | number] 20 | >(() => [inspectorStore, id], []) 21 | 22 | if (!store || !reducerId) { 23 | return useReactState(initialState) 24 | } 25 | 26 | const finalInitialState = useMemo( 27 | () => 28 | typeof initialState === "function" 29 | ? (initialState as () => S)() 30 | : initialState, 31 | [] 32 | ) 33 | 34 | return useHookedReducer( 35 | stateReducer, 36 | finalInitialState, 37 | store, 38 | reducerId 39 | ) 40 | } 41 | -------------------------------------------------------------------------------- /tests/index.test.tsx: -------------------------------------------------------------------------------- 1 | import React, { useReducer as useReactReducer } from "react" 2 | import { StateInspector, useReducer as useReinspectReducer } from "../src" 3 | import { shallow, mount } from "enzyme" 4 | 5 | const Wrapper: React.FC = ({ children }) => { 6 | return ( 7 | 8 |
9 | {children} 10 | 11 | ) 12 | } 13 | 14 | const initArg = { 15 | start: 3 16 | } 17 | 18 | const init = (arg: typeof initArg) => ({ 19 | count: arg.start 20 | }) 21 | 22 | type State = ReturnType 23 | 24 | const INCREMENT = "INCREMENT" 25 | 26 | type Actions = { 27 | type: typeof INCREMENT 28 | } 29 | 30 | const myReducer = (state: State, action: Actions) => { 31 | switch (action.type) { 32 | case INCREMENT: 33 | return { ...state, count: state.count + 1 } 34 | default: 35 | return state 36 | } 37 | } 38 | 39 | describe("reinspect", () => { 40 | it("render StateInspector", () => { 41 | const result = shallow() 42 | .find({ name: "Test" }) 43 | .exists() 44 | 45 | expect(result).toBeTruthy() 46 | }) 47 | 48 | it("react useReducer", () => { 49 | const Counter: React.FC = () => { 50 | const [state, dispatch] = useReactReducer(myReducer, initArg, init) 51 | 52 | const handleClick = () => { 53 | dispatch({ type: INCREMENT }) 54 | } 55 | 56 | return ( 57 |
58 | Count: {state.count} 59 | 62 |
63 | ) 64 | } 65 | 66 | const counter = mount( 67 | 68 | 69 | 70 | ) 71 | 72 | expect(counter.find(".display").text()).toBe("Count: 3") 73 | counter.find(".increment").simulate("click") 74 | expect(counter.find(".display").text()).toBe("Count: 4") 75 | }) 76 | 77 | it("reinspect useReducer", () => { 78 | const Counter: React.FC = () => { 79 | const [state, dispatch] = useReinspectReducer( 80 | myReducer, 81 | initArg, 82 | init, 83 | "NAME" 84 | ) 85 | 86 | const handleClick = () => { 87 | dispatch({ type: INCREMENT }) 88 | } 89 | 90 | return ( 91 |
92 | Count: {state.count} 93 | 96 |
97 | ) 98 | } 99 | 100 | const counter = mount( 101 | 102 | 103 | 104 | ) 105 | 106 | expect(counter.find(".display").text()).toBe("Count: 3") 107 | counter.find(".increment").simulate("click") 108 | expect(counter.find(".display").text()).toBe("Count: 4") 109 | }) 110 | 111 | it("reinspect useReducer type overloads", () => { 112 | const Counter: React.FC = () => { 113 | type MyState = { 114 | start: number 115 | count: number 116 | } 117 | 118 | const initArg: Partial = { start: 2 } 119 | 120 | const inti = ({ start }: typeof initArg) => ({ 121 | start, 122 | count: start ? start : 0 + 1 123 | }) 124 | 125 | // inital arg as partial of state 126 | const [state, dispatch] = useReinspectReducer( 127 | myReducer, 128 | initArg, 129 | inti, 130 | "NAME" 131 | ) 132 | 133 | // any init arg 134 | const [state2, dispatch2] = useReinspectReducer( 135 | myReducer, 136 | { 137 | color: "black" 138 | }, 139 | ({ color }) => ({ count: 123 }), 140 | "NAME" 141 | ) 142 | 143 | // inital state only 144 | const [state3, dispatch3] = useReinspectReducer( 145 | myReducer, 146 | { count: 2 }, 147 | "NAME" 148 | ) 149 | 150 | return ( 151 |
152 |

Hello

153 |
154 | ) 155 | } 156 | 157 | const counter = shallow( 158 | 159 | 160 | 161 | ) 162 | 163 | expect(counter.exists()).toBeTruthy() 164 | }) 165 | }) 166 | -------------------------------------------------------------------------------- /tests/test.setup.js: -------------------------------------------------------------------------------- 1 | const Enzyme = require("enzyme") 2 | const Adapter = require("enzyme-adapter-react-16") 3 | 4 | Enzyme.configure({ adapter: new Adapter() }) 5 | -------------------------------------------------------------------------------- /tests/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs" 5 | // "strict": true, 6 | }, 7 | "files": ["./index.test.tsx"] 8 | } 9 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "allowSyntheticDefaultImports": true, 5 | "esModuleInterop": true, 6 | "jsx": "react", 7 | "lib": ["es2015", "dom"], 8 | "declaration": true, 9 | "declarationDir": "types", 10 | "strict": true, 11 | "allowJs": true 12 | }, 13 | "exclude": ["node_modules"], 14 | "include": ["src"] 15 | } 16 | --------------------------------------------------------------------------------