Skip to content

Instantly share code, notes, and snippets.

@brunolemos
Last active May 27, 2020 14:00
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save brunolemos/ff75131ec8887f2035f2e9eb121bba5e to your computer and use it in GitHub Desktop.
Save brunolemos/ff75131ec8887f2035f2e9eb121bba5e to your computer and use it in GitHub Desktop.
Redux + TypeScript - Strongly Typed
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')
}
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
}
}
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>
)
}
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
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 }
}
// 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],
)
}
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
Copy link

Super cool! Gonna take for a spin soon 🚀

@brunolemos
Copy link
Author

@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>.

@brunolemos
Copy link
Author

brunolemos commented Nov 18, 2018

Fixed using hooks and simplified action creators.

@tcodes0
Copy link

tcodes0 commented Dec 2, 2018

@brunolemas

export type RootState = typeof rootReducer extends ReduxReducer<infer S>
  ? S
: never

no meu redux o nome do tipo eh so Reducer nao ReduxReducer :)

@Liteolika
Copy link

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.

@brunolemos
Copy link
Author

brunolemos commented Mar 31, 2020

@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