Last active
November 21, 2022 09:31
-
-
Save fre-sch/65a40a02dbade9f3f8ece0708fd05534 to your computer and use it in GitHub Desktop.
Some utils for dealing with preact/hooks
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
/* | |
Add some structure to the `[state, dispatch]` pattern of `useReducer`. | |
`useActionReducer` provides `dispatch(action: str, data: any)`, and wraps | |
it to call `handler[action](state, data)` | |
Example object based handler: | |
const todoListHandler = { | |
text: (state, {text}) => { | |
return { ...state, text } | |
}, | |
add: (state, data) => { | |
let { todos, text } = state | |
todos = todos.concat({ text }) | |
return { ...state, todos, text: "" } | |
}, | |
delete: (state, {index}) => { | |
let { todos } = state | |
todos.splice(index, 1) | |
return { ...state, todos } | |
} | |
} | |
const TodoList = () => { | |
const [state, dispatch] = useActionReducer(todoListHandler, { | |
todos: [], | |
text: "" | |
}) | |
return ( | |
<form onSubmit={e => dispatch("add")} action="javascript:"> | |
<input value={state.text} onInput={ e => dispatch("text", {text: e.target.value}) } /> | |
<button type="submit">Add</button> | |
<ul> | |
{state.todos.map((todo, index) => ( | |
<li> | |
{todo.text} | |
<button type="button" onClick={e => dispatch("delete", {index})}>Delete</button> | |
</li> | |
))} | |
</ul> | |
</form> | |
) | |
} | |
Example class based handler: | |
class TodoListHandler { | |
text(state, {text}) { | |
return { ...state, text } | |
} | |
add(state, data) { | |
let { todos, text } = state | |
todos = todos.concat({ text }) | |
return { ...state, todos, text: "" } | |
} | |
delete(state, {index}) { | |
let { todos } = state | |
todos.splice(index, 1) | |
return { ...state, todos } | |
} | |
} | |
const TodoList = () => { | |
const [state, dispatch] = useActionReducer(new TodoListHandler(), { | |
todos: [], | |
text: "" | |
}) | |
return ( | |
<form onSubmit={e => dispatch("add")} action="javascript:"> | |
<input value={state.text} onInput={ e => dispatch("text", {text: e.target.value}) } /> | |
<button type="submit">Add</button> | |
<ul> | |
{state.todos.map((todo, index) => ( | |
<li> | |
{todo.text} | |
<button type="button" onClick={e => dispatch("delete", {index})}>Delete</button> | |
</li> | |
))} | |
</ul> | |
</form> | |
) | |
} | |
*/ | |
const actionObjectHandler = (handler) => (state, {type, ...data}) => { | |
try { | |
const result = handler[type](state, data) | |
if (result === undefined || result === null) { | |
return state | |
} | |
return result | |
} | |
catch (TypeError) { | |
console.error(`handler doesn't have action`, type, handler) | |
return state | |
} | |
} | |
const actionObjectDispatch = (dispatch) => (type, data) => dispatch({type, ...data}) | |
const actionObjectDispatchWrap = ([ state, dispatch ]) => [ state, actionObjectDispatch(dispatch) ] | |
const useActionReducer = (handler, init) => actionObjectDispatchWrap(useReducer(actionObjectHandler(handler), init)) | |
/** | |
Delay `useEffect`. Useful for delaying triggering of requests on fast changing user input, like search querys. | |
*/ | |
export const debounceEffect = (fun, deps, time=250) => | |
useEffect(() => { | |
const timeoutId = setTimeout(fun, time) | |
return () => clearTimeout(timeoutId) | |
}, deps) | |
// utilities for combineReducers | |
export const compose = (g, f) => (...x) => g(f(...x)) | |
export const identity = (x) => x | |
const sideEffect = (dispatch) => (...action) => { dispatch(...action); return action } | |
/** | |
Combine multiple reducer. Useful to separate concerns into separate reducing handlers. | |
Reducers is expected to be an object with keys being identifiers, | |
and values being an array with reducer handler and initial state. | |
All reducer handlers will be called when using the resulting dispatch. | |
Example: | |
const [ state, dispatch ] = combineReducers({ | |
login: [ handleLogin, { user: null, status: null } ], | |
app: [ handleApp, { errors: null, notifications: null } ], | |
loginModal: [ handleModal, { current: null, status: null } ] | |
}) | |
*/ | |
export const combineReducers = (reducers) => | |
Object.keys(reducers) | |
.map(key => | |
[key, ...useReducer(...reducers[key])] | |
) | |
.reduce((agg, [key, state, dispatch]) => { | |
agg[0][key] = state | |
agg[1] = compose(agg[1], sideEffect(dispatch)) | |
return agg | |
} | |
, [{}, identity]) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment