easy-react-state
Advanced tools
Comparing version 1.0.1 to 1.0.2
@@ -1284,53 +1284,31 @@ import React from 'react'; | ||
var store = createStore(configStore, options); | ||
var useSelector = function (selector, computationFn) { | ||
var state = React.useRef(store.getState()); | ||
var _a = __read(React.useState({}), 2), _ = _a[0], forceRender = _a[1]; | ||
var prevSelectedState = React.useRef(); | ||
var computedValue = React.useMemo(function () { | ||
prevSelectedState.current = selector(state.current); | ||
if (computationFn) { | ||
return computationFn(prevSelectedState.current); | ||
var useSelector = function (selector, equalityFn) { | ||
if (equalityFn === void 0) { equalityFn = Object.is; } | ||
var _a = __read(React.useState({}), 2), forceRender = _a[1]; | ||
var prevSelectedState = React.useRef({}); | ||
var latestSelector = React.useRef(); | ||
// Computing the selector. If selector reference was not changed upon rendering, | ||
// then cached value is returned. | ||
try { | ||
if (selector !== latestSelector.current) { | ||
prevSelectedState.current = selector(store.getState()); | ||
latestSelector.current = selector; | ||
} | ||
return prevSelectedState.current; | ||
// We need to run the computation if the dummy `-` state was changed. | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
}, [_, computationFn, selector]); | ||
React.useEffect(function () { | ||
} | ||
catch (err) { | ||
var errorMessage = "An error occured while selecting the store state: " + err.message + "."; | ||
throw new Error(errorMessage); | ||
} | ||
React.useEffect(function subscribeToStore() { | ||
var unsubscribe = store.subscribe(function () { | ||
var nextState = store.getState(); | ||
var nextSelectedState = selector(nextState); | ||
// NOTE: If there is changes on the selected state, we need to re-render the Component | ||
// via updating a state because we want to have a re-computation which will run | ||
// in next render in `React.useMemo` above. | ||
if (Array.isArray(prevSelectedState.current) && | ||
!Object.is(prevSelectedState.current, nextSelectedState)) { | ||
state.current = nextState; | ||
var nextSelectedState = latestSelector.current(nextState); | ||
if (!equalityFn(prevSelectedState.current, nextSelectedState)) { | ||
prevSelectedState.current = nextSelectedState; | ||
forceRender({}); | ||
} | ||
// If object, we gonna use `shallowEqual` to check if the top-level keys of the 2 objects | ||
// are the same. | ||
else if (typeof prevSelectedState.current === 'object' && | ||
typeof nextSelectedState === 'object') { | ||
var prevSelectedStateSize = Object.keys(prevSelectedState.current) | ||
.length; | ||
var nextSelectedStateSize = Object.keys(nextSelectedState).length; | ||
if ((prevSelectedStateSize === 0 && | ||
!Object.is(prevSelectedState.current, nextSelectedState)) || | ||
// If the sizes are not equal, means that the properties are changed. Needs to re-compute | ||
(prevSelectedStateSize !== nextSelectedStateSize || | ||
!shallowEqual(prevSelectedState.current, nextSelectedState))) { | ||
state.current = nextState; | ||
forceRender({}); | ||
} | ||
} | ||
// If primitives like number, string, boolean | ||
else { | ||
if (!Object.is(prevSelectedState.current, nextSelectedState)) { | ||
state.current = nextState; | ||
forceRender({}); | ||
} | ||
} | ||
}); | ||
return function () { return unsubscribe(); }; | ||
}, [computedValue, selector]); | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
}, []); | ||
/** | ||
@@ -1342,3 +1320,3 @@ * `useSelector` will return a conditional type. If `computationFn` is undefined, it should | ||
*/ | ||
return computedValue; | ||
return prevSelectedState.current; | ||
}; | ||
@@ -1349,3 +1327,3 @@ return [useSelector, store.setters, store]; | ||
export default createStateManager; | ||
export { NOTHING as nothing }; | ||
export { shallowEqual }; | ||
//# sourceMappingURL=index.es.js.map |
@@ -1290,53 +1290,31 @@ 'use strict'; | ||
var store = createStore(configStore, options); | ||
var useSelector = function (selector, computationFn) { | ||
var state = React.useRef(store.getState()); | ||
var _a = __read(React.useState({}), 2), _ = _a[0], forceRender = _a[1]; | ||
var prevSelectedState = React.useRef(); | ||
var computedValue = React.useMemo(function () { | ||
prevSelectedState.current = selector(state.current); | ||
if (computationFn) { | ||
return computationFn(prevSelectedState.current); | ||
var useSelector = function (selector, equalityFn) { | ||
if (equalityFn === void 0) { equalityFn = Object.is; } | ||
var _a = __read(React.useState({}), 2), forceRender = _a[1]; | ||
var prevSelectedState = React.useRef({}); | ||
var latestSelector = React.useRef(); | ||
// Computing the selector. If selector reference was not changed upon rendering, | ||
// then cached value is returned. | ||
try { | ||
if (selector !== latestSelector.current) { | ||
prevSelectedState.current = selector(store.getState()); | ||
latestSelector.current = selector; | ||
} | ||
return prevSelectedState.current; | ||
// We need to run the computation if the dummy `-` state was changed. | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
}, [_, computationFn, selector]); | ||
React.useEffect(function () { | ||
} | ||
catch (err) { | ||
var errorMessage = "An error occured while selecting the store state: " + err.message + "."; | ||
throw new Error(errorMessage); | ||
} | ||
React.useEffect(function subscribeToStore() { | ||
var unsubscribe = store.subscribe(function () { | ||
var nextState = store.getState(); | ||
var nextSelectedState = selector(nextState); | ||
// NOTE: If there is changes on the selected state, we need to re-render the Component | ||
// via updating a state because we want to have a re-computation which will run | ||
// in next render in `React.useMemo` above. | ||
if (Array.isArray(prevSelectedState.current) && | ||
!Object.is(prevSelectedState.current, nextSelectedState)) { | ||
state.current = nextState; | ||
var nextSelectedState = latestSelector.current(nextState); | ||
if (!equalityFn(prevSelectedState.current, nextSelectedState)) { | ||
prevSelectedState.current = nextSelectedState; | ||
forceRender({}); | ||
} | ||
// If object, we gonna use `shallowEqual` to check if the top-level keys of the 2 objects | ||
// are the same. | ||
else if (typeof prevSelectedState.current === 'object' && | ||
typeof nextSelectedState === 'object') { | ||
var prevSelectedStateSize = Object.keys(prevSelectedState.current) | ||
.length; | ||
var nextSelectedStateSize = Object.keys(nextSelectedState).length; | ||
if ((prevSelectedStateSize === 0 && | ||
!Object.is(prevSelectedState.current, nextSelectedState)) || | ||
// If the sizes are not equal, means that the properties are changed. Needs to re-compute | ||
(prevSelectedStateSize !== nextSelectedStateSize || | ||
!shallowEqual(prevSelectedState.current, nextSelectedState))) { | ||
state.current = nextState; | ||
forceRender({}); | ||
} | ||
} | ||
// If primitives like number, string, boolean | ||
else { | ||
if (!Object.is(prevSelectedState.current, nextSelectedState)) { | ||
state.current = nextState; | ||
forceRender({}); | ||
} | ||
} | ||
}); | ||
return function () { return unsubscribe(); }; | ||
}, [computedValue, selector]); | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
}, []); | ||
/** | ||
@@ -1348,3 +1326,3 @@ * `useSelector` will return a conditional type. If `computationFn` is undefined, it should | ||
*/ | ||
return computedValue; | ||
return prevSelectedState.current; | ||
}; | ||
@@ -1355,3 +1333,3 @@ return [useSelector, store.setters, store]; | ||
exports.default = createStateManager; | ||
exports.nothing = NOTHING; | ||
exports.shallowEqual = shallowEqual; | ||
//# sourceMappingURL=index.js.map |
{ | ||
"name": "easy-react-state", | ||
"version": "1.0.1", | ||
"version": "1.0.2", | ||
"description": "Fun to use state management library for your awesome React app", | ||
@@ -19,2 +19,3 @@ "author": "ombori", | ||
"build": "rollup -c", | ||
"build:tsc": "npx tsc --declaration src/index.tsx", | ||
"start": "rollup -c -w", | ||
@@ -21,0 +22,0 @@ "prepare": "yarn run build", |
193
README.md
# easy-react-state | ||
> Fun to use state management library for your awesome React app | ||
Managing your app state should be easy and fun. `easy-react-state` is a minimal library | ||
for creating state management for your React project. | ||
[![NPM](https://img.shields.io/npm/v/easy-react-state.svg)](https://www.npmjs.com/package/easy-react-state) [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com) | ||
[![NPM](https://img.shields.io/npm/v/easy-react-state.svg)](https://www.npmjs.com/package/easy-react-state) | ||
@@ -11,6 +12,194 @@ ## Install | ||
npm install --save easy-react-state | ||
or | ||
yarn add easy-react-state | ||
``` | ||
## Features | ||
- easy to adopt with. Just think of `React.useState` where it has multiple setters which | ||
update the state object. | ||
- reduce boilerplate codes. No need to create actions. Just call and pass the right data. | ||
- intuitive selector system. | ||
- minimal API. Don't need to use some helper functions to support async updates. | ||
- typescript supports. | ||
## Usage | ||
### 1 - Configuring your store | ||
```jsx | ||
const configAppStore = { | ||
todos: { | ||
initialState: [], | ||
setters: state => ({ | ||
addTodo(todo) { | ||
state.push(todo) | ||
return state | ||
}, | ||
}), | ||
}, | ||
} | ||
``` | ||
### 2 - Creating state manager based on your store | ||
```jsx | ||
const [useAppSelector, appSetters] = createStateManager(configAppStore) | ||
``` | ||
### 3 - Consume to your React Component | ||
We don't need a `Provider` to consume the store. Just create a manager, then you can use it directly. | ||
```jsx | ||
const App = () => { | ||
const todos = useAppSelector(state => state.todos) | ||
console.log('todos', todos) | ||
return ( | ||
<div> | ||
<h3>Todos Control</h3> | ||
<button | ||
onClick={() => { | ||
const todo = { | ||
id: `todo-${Date.now()}`, | ||
label: `Todo ${Date.now()}`, | ||
} | ||
appSetters.todos.addTodo(todo) | ||
}} | ||
> | ||
Add todo | ||
</button> | ||
</div> | ||
) | ||
} | ||
``` | ||
## Use setters inside async | ||
Our setters are object which holds all the state setters. Upon creating, we can call setters just like normal functions. Indeed, we can call it everywhere. No need wrapper function like `thunk` to make it possible. Just call it immediately! | ||
```jsx | ||
const [useAppSelector, appSetters] = createStateManager(configAppStore) | ||
async function fetchUsers() { | ||
appSetters.users.loading() | ||
try { | ||
const res = await apiUsers() | ||
appSetters.users.setUsers(res) | ||
} catch (err) { | ||
appSetters.users.setError(err) | ||
} | ||
} | ||
``` | ||
## Invoking setters outside React | ||
React will batch the updates for subsequent calls of setters. But if you call these setters outside the React event system, like `Promise` or `setTimeout`, then every call will cause a re-render to Component. To avoid this, you can wrap your setters inside the `ReactDOM.unstable_batchedUpdates`. | ||
```jsx | ||
import { unstable_batchedUpdates as batch } from 'react-dom' | ||
// No batching | ||
const Test1 = () => { | ||
React.useEffect(() => { | ||
// This will cause 2 renders | ||
setTimeout(() => { | ||
obj1.setter1() | ||
obj2.setter2() | ||
}) | ||
}, []) | ||
return <div>Test 1</div> | ||
} | ||
// With batching | ||
const Test2 = () => { | ||
React.useEffect(() => { | ||
setTimeout(() => { | ||
// Single render | ||
batch(() => { | ||
obj1.setter1() | ||
obj2.setter2() | ||
}) | ||
}) | ||
}, []) | ||
return <div>Test 2</div> | ||
} | ||
``` | ||
## API | ||
#### Type Interfaces | ||
```tsx | ||
interface CreateState<S = any> { | ||
initialState: S | ||
setters: (state: S) => any | ||
} | ||
interface ConfigStore { | ||
[x: string]: CreateState | ||
} | ||
interface Options { | ||
label?: string | ||
logging?: boolean | ||
} | ||
interface Store<S, U> { | ||
getState: () => S | ||
subscribe: (Listener: Listener) => () => void | ||
setters: U | ||
} | ||
interface Selector<S> { | ||
<T>( | ||
selector: (state: S) => T, | ||
equalityFn?: (prevSelectedState: T, nextSelectedState: T) => boolean, | ||
): T | ||
} | ||
interface Setter { | ||
(...args: any[]): any | ||
} | ||
``` | ||
#### createStateManager | ||
```tsx | ||
createStateManager(configStore: ConfigStore, options?: Options): [useSelector, setters, store] | ||
``` | ||
This function creates a resources which we can use to manage the state based on the `configStore`. It also return a store object. | ||
#### store | ||
```tsx | ||
store: Store<State, Setters> | ||
``` | ||
An object which we can use to get the current state, subscribe and update state through setters. Its interface is just like `redux-store`. | ||
#### useSelector | ||
```tsx | ||
useSelector(selector: Selector): selectedState | ||
``` | ||
A function which we can use to extract data from the store state using a selector function. | ||
`useSelector` semantics are pretty the same with `useSelector` of react-redux. The difference | ||
is `useSelector` of react-redux uses strict `===` reference equality. Unlike `useSelector` of easy-react-state, it uses `Object.is` for comparing `selectedState`. [Check react-redux for more info](https://react-redux.js.org/api/hooks#useselector). `useSelector` supports the selector created by [reselect](https://github.com/reduxjs/reselect). | ||
#### setters | ||
```tsx | ||
setter(...args: any[]): void | ||
``` | ||
An object which holds functions which we can use to update the state. | ||
## Cons | ||
- doesnt have DevTools for now. But it has logging option. | ||
- it doesnt support nested state. All state are resided at the top level object. | ||
## License | ||
@@ -17,0 +206,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
244450
211
2288