Last active
February 3, 2020 02:34
-
-
Save g4rcez/8274b8065e9506b33315baf05eca8645 to your computer and use it in GitHub Desktop.
A simple example with useReducer custom hook
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
import React, { useState, useMemo, Fragment } from "react"; | |
// Lembrando pro cara que não pode haver reatribuição | |
// no estado pois ele é imutável (ou deveria ser) | |
type Immutable<State> = Partial<Readonly<State>>; | |
// Inferência dos tipos da função primária | |
type Infer< | |
State, | |
Fn extends (...args: never) => (state: State) => Immutable<State> | |
> = (...args: Parameters<Fn>) => (state: State) => Immutable<State>; | |
// apenas um utils para extender nos tipos | |
type ReducerChunk<Actions, State> = { | |
[key in keyof Actions]: (args: any) => (state: State) => Immutable<State>; | |
}; | |
export type Dispatches<State, Actions extends ReducerChunk<Actions, State>> = { | |
[key in keyof Actions]: Infer<State, Actions[key]>; | |
}; | |
const useReducer = <State, Actions extends ReducerChunk<Actions, State>>( | |
initialState: State, | |
actions: Actions | |
): [State, Dispatches<State, Actions>] => { | |
const [state, setState] = useState(initialState); | |
// memoizando as actions para evitar novos objetos | |
const dispatches = useMemo( | |
() => | |
Object.entries(actions).reduce( | |
(acc, [name, dispatch]: [string, any]) => ({ | |
...acc, | |
[name]: (...params: any) => { | |
const event = dispatch(...params); | |
setState(currentState => ({ ...currentState, ...event(...params) })); | |
} | |
}), | |
{} as Dispatches<State, Actions> | |
), | |
[actions] | |
); | |
return [state, dispatches]; | |
}; | |
type STATE = { | |
name: string; | |
age: number; | |
points: number; | |
isApproved: boolean; | |
}; | |
const initialState: STATE = { | |
name: "", | |
age: 0, | |
points: 0, | |
isApproved: false | |
}; | |
const App = () => { | |
const [state, reducers] = useReducer(initialState as STATE, { | |
onChangeName: (e: React.ChangeEvent<HTMLInputElement>) => { | |
const { value } = e.target; | |
// Se for passar o evento para essa próxima função | |
// não se esqueça de usar e.persist() | |
// mais informações: | |
// https://reactjs.org/docs/events.html#event-pooling | |
return (): Partial<STATE> => ({ name: value }); | |
}, | |
onChangeNumber: (e: React.ChangeEvent<HTMLInputElement>) => { | |
const { name, value } = e.target; | |
return (): Partial<STATE> => ({ | |
[name as "age" | "points"]: Number.parseFloat(value) | |
}); | |
}, | |
onChangeCheckbox: (e: React.ChangeEvent<HTMLInputElement>) => { | |
const { checked } = e.target; | |
return (): Partial<STATE> => ({ isApproved: checked }); | |
} | |
}); | |
return ( | |
<Fragment> | |
<input name="name" onChange={reducers.onChangeName} value={state.name} /> | |
<input | |
type="number" | |
name="age" | |
onChange={reducers.onChangeNumber} | |
value={state.age} | |
/> | |
<input | |
type="number" | |
name="points" | |
onChange={reducers.onChangeNumber} | |
value={state.points} | |
/> | |
<input | |
type="checkbox" | |
name="isApproved" | |
onChange={reducers.onChangeCheckbox} | |
checked={state.isApproved} | |
/> | |
</Fragment> | |
); | |
}; | |
export default App; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment