Skip to content

A TypeScript React/Redux wrapper for reducing boilerplate

Notifications You must be signed in to change notification settings

Seikho/typedstate

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

28 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

TypedState

A React/Redux wrapper to reduce boilerplate and increase type safety and productivity

Why?

After using React with Redux, I found that using connect, creating reducers, and sagas required a lot of boilerplate.
In order to get the type safety I wanted, I would regularly have to modify old code to do so.

TypedState is an attempt to reduce the amount of effort introducing React and Redux into a project and to make it easier to use without being burdened by passing types around.

Project Goals

  • Provide a very low barrier for entry to React+Redux
  • Provide complete type safety in components, reducers, and sagas
  • Reduce boilerplate in React/Redux projects
  • Maintain a small and discoverable API to ensure high developer experience

Benefits

Reducers, sagas, and components gain a huge amount of type safety and type inference.
Action types, state

Example Setup

You will still need to add React and Redux dependencies!

  • react
  • redux
  • react-redux
  • react-dom

See the example folder for a more complete example project.

API

createStore()

Returns setup and saga functions

import { createStore } from 'typedstate'

function createStore(name: string, reducers: { [key: string]: Reducer })

createReducer()

Returns handle and reducer functions.
The reducer must be exported and passed to the createStore function.

import { createReducer } from 'typedstate'

function createReducer<State, Action>(initState: State, handler?: HandlerBody)

setup()

Returns the Redux store and the withState and withDispatch hooks.
Think of withState as a wrapper of the react-redux connect function.

See examples/App.tsx for several examples.

Example

See the example/ folder for a more complete example

Creating a Store

The amount of code required to create the store is small and less complicated.
You no longer need to maintain a union type of all of your actions nor an interface of your application state.
Simply adding a reducer will provide all of the type safety you need when using store and withState().

import { createStore } from 'typedstate'
import * as user from './user'
import * as game from './game'

const { setup, saga } = createStore('my app', {
  user: user.reducer,
  game: game.reducer,
})

const { store, withDispatch, withState } = setup()

export { saga, store, withDispatch, withState }

Creating a Reducer

Creating reducers and sagas is fast, easy, and declarative.

import { createReducer } from 'typedstate'

export { reducer }

interface State {
  state: 'init' | 'loading' | 'loaded'
  name?: string
  loggedIn: boolean
  error?: string
}

type Action =
  | { type: 'USER_REQUEST_LOGIN'; username: string; password: string }
  | { type: 'USER_RECEIVE_LOGIN'; name?: string; error?: string }
  | { type: 'USER_REQUEST_LOGOUT' }

const { reducer, handle } = createReducer<State, Action>({ state: 'init', loggedIn: false })

handle('USER_REQUEST_LOGIN', { error: undefined, name: undefined, state: 'loading' })
handle('USER_RECEIVE_LOGIN', (_state, action) => {
  return {
    state: 'loaded',
    name: action.name,
    error: action.error,
    loggedIn: action.error === undefined,
  }
})

handle('USER_REQUEST_LOGOUT', { name: undefined, error: undefined, loggedIn: false })

Alternative way of Creating a Reducer

import { createReducer } from 'typedstate'

interface State {
  state: 'init' | 'loading' | 'loaded'
  name?: string
  loggedIn: boolean
  error?: string
}

type Action =
  | { type: 'USER_REQUEST_LOGIN'; username: string; password: string }
  | { type: 'USER_RECEIVE_LOGIN'; name?: string; error?: string }
  | { type: 'USE