Last active
May 27, 2020 14:00
-
-
Save brunolemos/ff75131ec8887f2035f2e9eb121bba5e to your computer and use it in GitHub Desktop.
Redux + TypeScript - Strongly Typed
This file contains hidden or 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 { GitHubUser } from '../../types' | |
import { createAction, createErrorAction } from '../../utils/helpers/redux' | |
export function loginRequest(payload: { token: string }) { | |
return createAction('LOGIN_REQUEST', payload) | |
} | |
export function loginSuccess(user: GitHubUser) { | |
return createAction('LOGIN_SUCCESS', user) | |
} | |
export function loginFailure<E extends Error>(error: E) { | |
return createErrorAction('LOGIN_FAILURE', error) | |
} | |
export function logout() { | |
return createAction('LOGOUT') | |
} |
This file contains hidden or 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 { User, Reducer } from '../../types' | |
export interface State { | |
token: string | |
user: User | null | |
} | |
const initialState: State = { | |
token: '', | |
user: null, | |
} | |
export const authReducer: Reducer<State> = (state = initialState, action) => { | |
switch (action.type) { | |
// ... | |
case 'LOGIN_SUCCESS': | |
return { | |
user: action.payload, | |
} | |
default: | |
return state | |
} | |
} |
This file contains hidden or 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 from 'react' | |
import { Button, Text, View } from 'react-native' | |
import { useDispatch } from 'react-redux' | |
import { useReduxState } from '../hooks/use-redux-state' | |
import * as actions from '../redux/actions' | |
import * as selectors from '../redux/selectors' | |
export function LoginScreen() { | |
const dispatch = useDispatch() | |
const user = useReduxState(selectors.currentUserSelector) | |
return ( | |
<View> | |
{user ? ( | |
<> | |
<Text children="Logged" /> | |
<Button onPress={() => dispatch(logout())} title="Logout" /> | |
</> | |
) : ( | |
<> | |
<Button onPress={() => dispatch(login({ token: '123' }))} title="login" /> | |
</> | |
)} | |
</View> | |
) | |
} |
This file contains hidden or 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 { InferableComponentEnhancerWithProps } from 'react-redux' | |
import { Action as ReduxAction, Reducer as ReduxReducer, Dispatch, MiddlewareAPI } from 'redux' | |
import * as actions from '../actions' | |
import { rootReducer } from '../reducers' | |
export interface Action<T extends string, P> extends ReduxAction<T> { | |
payload: P | |
} | |
export interface ActionWithError< | |
T extends string, | |
P, | |
E extends object = Record<string, any> | |
> extends Action<T, P> { | |
payload: P | |
error: E | |
} | |
export type ExtractPayloadFromActionCreator<AC> = AC extends () => any | |
? void | |
: (AC extends (payload: infer P) => any ? P : never) | |
export type ExtractDispatcherFromActionCreator< | |
AC | |
> = ExtractPayloadFromActionCreator<AC> extends void | |
? () => void | |
: (payload: ExtractPayloadFromActionCreator<AC>) => void | |
export type ExtractActionFromActionCreator<AC> = AC extends () => infer A | |
? A | |
: (AC extends (payload: any) => infer A | |
? A | |
: AC extends (payload: any, error: any) => infer A | |
? A | |
: never) | |
export type ExtractPropsFromConnector< | |
Connector | |
> = Connector extends InferableComponentEnhancerWithProps<infer T, any> | |
? T | |
: never | |
export type ExtractStateFromReducer<R> = R extends ReduxReducer<infer S> | |
? S | |
: never | |
export type AllActions = ExtractActionFromActionCreator< | |
typeof actions[keyof typeof actions] | |
> | |
export type Reducer<S = any> = (state: S | undefined, action: AllActions) => S | |
export type RootState = ExtractStateFromReducer<typeof rootReducer> | |
export type Middleware = ( | |
store: MiddlewareAPI, | |
) => (next: Dispatch<AllActions>) => (action: AllActions) => any |
This file contains hidden or 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 { Action, ActionWithError } from '../types' | |
export function createAction<T extends string>(type: T): Action<T, void> | |
export function createAction<T extends string, P>( | |
type: T, | |
payload: P, | |
): Action<T, P> | |
export function createAction<T extends string, P>(type: T, payload?: P) { | |
return typeof payload === 'undefined' ? { type } : { type, payload } | |
} | |
export function createErrorAction<T extends string, E extends object>( | |
type: T, | |
error: E, | |
): ActionWithError<T, void, E> | |
export function createErrorAction< | |
T extends string, | |
E extends object = Record<string, any> | |
>(type: T, error: E) { | |
return { type, error } | |
} | |
export function createErrorActionWithPayload< | |
T extends string, | |
P, | |
E extends object | |
>(type: T, payload: P, error: E): ActionWithError<T, P, E> | |
export function createErrorActionWithPayload< | |
T extends string, | |
P, | |
E extends object = Record<string, any> | |
>(type: T, payload: P, error: E) { | |
return { type, payload, error } | |
} |
This file contains hidden or 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
// NOT NECESSARY ANYMORE SINCE useDispatch WAS OFFICIALLY INTRODUCED | |
import { useMemo } from 'react' | |
import { useDispatch } from 'react-redux' | |
type ActionCreator = (...args: any) => any | |
export function useReduxAction<AC extends ActionCreator>(actionCreator: AC) { | |
const dispatch = useDispatch() | |
return useMemo( | |
() => ( | |
...args: AC extends ((...args: infer Args) => any) ? Args : any[] | |
) => { | |
dispatch(actionCreator(...(args as any[]))) | |
}, | |
[actionCreator], | |
) | |
} |
This file contains hidden or 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 _ from 'lodash' | |
import { useSelector } from 'react-redux' | |
type Result<S> = S extends (...args: any[]) => infer R ? R : any | |
export function useReduxState< | |
S extends (state: any) => any, | |
R extends Result<S> | |
>(selector: S, equalityFn?: (left: R, right: R) => boolean) { | |
return useSelector(selector, equalityFn) as R | |
} |
@guilhermedecampo the connect(
part is still verbose, I might fix that later or just wait for the redux hooks instead, but if you make it better let me know. Ideally it should just be @connect(...)
without the need of LoginScreenProps & ExtractPropsFromConnector<typeof connectToStore>
.
Fixed using hooks and simplified action creators.
@brunolemas
export type RootState = typeof rootReducer extends ReduxReducer<infer S>
? S
: never
no meu redux o nome do tipo eh so Reducer
nao ReduxReducer
:)
This looks really good. Is there a complete example of the code? I think i miss some files :)
Trying to learn react/redux with typescript.
@Liteolika I think all files are in this gist, maybe with the wrong import, but you can check them on this project: https://github.com/devhubapp/devhub/tree/master/packages/components/src/redux
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Super cool! Gonna take for a spin soon 🚀