README
React-Use-Methods
a minimalist state management hooks inspired by react-use
Instead of useReducer, give you the best typescript experience.
Install
npm install react-use-methods
# or
yarn add react-use-methods
API
useMethods
Just like react-use/useMethods
, and make some extensions based on it.
Usage
import React from 'react'
import { useMethods } from 'react-use-methods'
function App() {
const [{ count }, methods] = useMethods(
(state) => {
return {
methods: {
increment() {
return { ...state, count: state.count + 1 }
},
incrementDouble() {
return { ...state, count: state.count * 2 }
},
decrement() {
return { ...state, count: state.count - 1 }
},
set(current) {
return { ...state, count: current }
},
reset() {
return { ...state, count: 0 }
},
},
actions: {
// custom action here,support async function
midReset(...args) {
// return a function and dispatch custom action
return async ({ type, dispatch, payload }) => {
console.log(type) // midReset
console.log(dispatch) // the dispatch of useReducer
console.log(payload) // args
setTimeout(() => {
dispatch({
type: 'reset',
payload,
})
}, 1000)
}
},
},
}
},
{
count: 0,
}
)
// methods contains all functions in methods and actions and combines them
return (
<div>
{count}
<button onClick={methods.methods.increment}>increment</button>
<button onClick={methods.incrementDouble}>incrementDouble</button>
<button onClick={methods.decrement}>decrement</button>
<button onClick={() => methods.set(10)}>set 10</button>
<button onClick={() => methods.reset()}>reset</button>
<button onClick={() => methods.midReset()}>midReset</button>
</div>
)
}
You can also use effects:
import React from 'react'
import { useMethods } from 'react-use-methods'
function App() {
const [{ count }, methods] = useMethods(
(state) => {
return {
methods: {
increment() {
return { ...state, count: state.count + 1 }
},
incrementDouble() {
return { ...state, count: state.count * 2 }
},
decrement() {
return { ...state, count: state.count - 1 }
},
set(current) {
return { ...state, count: current }
},
reset() {
return { ...state, count: 0 }
},
},
actions: {
midReset(...args) {
// return a function and dispatch custom action
return ({ type, dispatch, payload }) => {
console.log(type) // midReset
console.log(dispatch) // the dispatch of useReducer
console.log(payload) // args
// custom action here
dispatch({
type: 'reset',
payload,
})
}
},
},
effects: {
count(dispatch, newValue, oldValue) {
console.log(newValue, oldValue)
if (newValue < 0) {
dispatch({
type: 'increment',
})
}
},
},
}
},
{
count: 0,
}
)
return (
<div>
{count}
<button onClick={methods.increment}>increment</button>
<button onClick={methods.incrementDouble}>incrementDouble</button>
<button onClick={methods.decrement}>decrement</button>
<button onClick={() => methods.set(10)}>set 10</button>
<button onClick={() => methods.reset()}>reset</button>
<button onClick={() => methods.midReset()}>midReset</button>
</div>
)
}
Reference
const [state, methods] = useMethods(
createMethods,
initialState,
useMethodsOptions
)
createMethods
: function that takes current state or An object containing methods, actions and effects, return an object containing methods that return updated state.initialState
: initial value of the state.useMethodsOptions
: an object that customizes the internal behavior of useMethods for users.import { useReducer, Reducer } from 'react' interface UseMethodsOptions<S, A> { reducerMapper?: (reducer: Reducer<S, A>) => Reducer<S, A> customUseReducer?: typeof useReducer }
- reducerMapper: an interface for user to change the native reducer of useMethods (like immer).
- customUseReducer: a custom hook like
React.useReducer
, you can create it bycreateUseReducer
( not recommended to use it directly, it should be generated in createUseMethods ).
createUseReducer
A factory to create the useReducer hook, same as react-use/createReducer
createUseMethods
A factory to create the useMethods hook and you can add middlewares (like redux-thunk) to extend it.
Usage
import thunk from 'redux-thunk'
import { createUseMethods } from 'react-use-methods'
// make sure immer has been installed
import { combineReducers } from 'react-use-methods/reducer-mapper/es/immer'
const useMethods = createUseMethods(
{
// use immer
reducerMapper: combineReducers,
},
thunk
)
function App() {
const [{ count }, methods] = useMethods(
(state) => {
return {
methods: {
increment() {
state.count += 1
return state
},
decrement() {
return { ...state, count: state.count - 1 }
},
reset() {
state.count = 0
return state
},
},
actions: {
addAndReset() {
return ({ dispatch }) => {
const addAndReset = () => {
return (thunkDispatch) => {
thunkDispatch({ type: 'increment' })
setTimeout(() => {
thunkDispatch({ type: 'reset' })
}, 1000)
}
}
dispatch(addAndReset())
}
},
},
}
},
{
count: 0,
}
)
return (
<div>
{count}
<button onClick={methods.increment}>increment</button>
<button onClick={methods.decrement}>decrement</button>
<button onClick={methods.addAndReset}>addAndReset</button>
</div>
)
}
Reference
const useMethods = createUseMethods(useMethodsOptions, ...middlewares)
// or
const useMethods = createUseMethods(...middlewares)
useMethodsOptions
: same as useMethods.middlewares
: custom middlewares for dispatch actions, like redux-thunk.
createUseMethodsContext
A state management factory function that allows all components in the provider to easily share state.
Usage
// provider.js
import { createMethodsContext, createUseMethods } from 'react-use-methods'
// make sure immer has been installed
import { combineReducers } from 'react-use-methods/reducer-mapper/es/immer'
const useMethods = createUseMethods({
reducerMapper: combineReducers,
})
const [useCountContext, CounterProvider, withCountProvider] =
createMethodsContext(
(state) => {
return {
// if we don't need actions,we can move the methods to the fist level
increment() {
state.count += 1
return state
},
incrementDouble() {
return { ...state, count: state.count * 2 }
},
decrement() {
state.count -= 1
return state
},
set(current) {
return { ...state, count: current }
},
reset() {
return { ...state, count: 0 }
},
}
},
{
count: 0,
},
useMethods
)
export { useCountContext, CounterProvider, withCountProvider }
// index.jsx
import React from 'react'
import { useCountContext, CounterProvider } from './provider'
function Counter() {
const [state, methods] = useCountContext()
return (
<div>
{state.count}
<button onClick={methods.increment}>increment</button>
<button onClick={methods.incrementDouble}>incrementDouble</button>
<button onClick={methods.decrement}>decrement</button>
<button onClick={() => methods.set(10)}>set 10</button>
<button onClick={() => methods.reset()}>reset</button>
</div>
)
}
function App() {
return (
// you can give a new initialValue for different provider
<CounterProvider initialValue={{ count: 10 }}>
<Counter />
</CounterProvider>
)
}
export default App
or
// index.jsx
import React from 'react'
import { useCountContext, withCountProvider } from './provider'
function Counter() {
const [state, methods] = useCountContext()
return (
<div>
{state.count}
<button onClick={methods.increment}>increment</button>
<button onClick={methods.incrementDouble}>incrementDouble</button>
<button onClick={methods.decrement}>decrement</button>
<button onClick={() => methods.set(10)}>set 10</button>
<button onClick={() => methods.reset()}>reset</button>
</div>
)
}
export default withCountProvider(Counter, {
// provider props
initialValue: {
count: 10,
},
})
Reference
const [useMethods, MethodsProvider, withMethodsProvider, methodsContext] =
createUseMethodsContext(createMethods, defaultInitialValue, customUseMethods)