Skip to content

Instantly share code, notes, and snippets.

@fre-sch
Last active November 21, 2022 09:31
Show Gist options
  • Save fre-sch/65a40a02dbade9f3f8ece0708fd05534 to your computer and use it in GitHub Desktop.
Save fre-sch/65a40a02dbade9f3f8ece0708fd05534 to your computer and use it in GitHub Desktop.
Some utils for dealing with preact/hooks
/*
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