Writing a simple React-Redux app with Typescript — Part 2

Zac Tolley
Scropt

--

In part 1 we setup a React Typescript project and created some Redux Thunk actions and regular action creators with type information. In part 2 we will create a reducer that will consume the actions we dispatch and update the application state.

In part 1 we said we needed to create a store to maintain a list of contacts and a currently selected contact. The reducer is responsible for managing this state information. When using plain Javascript you would typically create a default state and modify that, with Typescript you need to go one step further and describe the state you are maintaining which can be done by creating an interface.

# src/store/contacts/IContactsState.tsimport IContact from '../../models/IContact'export default interface IContactsState {
contacts: IContact[]
currentContact: IContact | null
}
# src/store/contacts/reducer.tsimport IContactState from './IContactState'const defaultState: IContactsState = {
contacts: [],
currentContact: null,
}

The interface created says that the contacts store holds an array of objects that confirm to the IContact interface. The currentContact property in the store can be null or contain an object that conforms to the IContact interface.

The example also sets up a default state that conforms to the IContactsState interface. If I put an invalid value in it’s properties I would get a Typescript error.

The reducer itself looks almost identical to a regular one, the only difference is you tell typescript that you are creating a ‘Reducer’ that maintains state that conforms to the IContactsState interface. The parameters for the reducer are typed to help with type validation

# src/store/contacts/reducer.tsimport { Reducer } from 'redux'import { Action, actionTypes } from './actions'
import IContactState from './IContactState'
const defaultState: IContactsState = {
contacts: [],
currentContact: null,
}
const contactsReducer: Reducer<IContactsState> = (
state: IContactsState = defaultState,
action: Action
) => {
switch (action.type) {
case actionTypes.LOAD_CONTACTS:
return {
...state,
contacts: action.payload,
}
case actionTypes.LOAD_CONTACT:
return {
...state,
currentContact: action.payload,
}
case actionTypes.CLEAR_CURRENT_CONTACT:
return {
...state,
currentContact: null,
}
default:
return state
}
}
export default contactsReducer

Typescript is smart enough to know when you try to apply an invalid type to a property of the state and, even better, it knows if you try to apply the payload to the incorrect property state based on the action type and payload type. Try it. Change the handler for LOAD_CONTACTS to assign the payload to ‘contact’ instead of ‘contacts’. It’s not perfect, the error will show on the ‘const contactsReducer’ line, but it still pretty darn handy.

So that’s the store. As I mentioned this is just one store and there may be others, to finish the job we need to create a root store and create a redux store that combines that with any store middleware we wish to use, such as the redux-thunk middleware used earlier.

Like the contact store state we need to create an interface to describe the root store state.

# src/store/IAppState.tsimport IContactsState from './contacts/IContactsState'export interface IAppState {
contacts: IContactsState
}

I know some of the naming here isn’t perfect, like the fact that this means we have ‘contacts.contacts’ but this is just to give you an idea how to use Redux with Typescript. In real life I wouldn’t use these names.

# src/store/index.tsimport { 
applyMiddleware,
combineReducers,
createStore,
Store
} from 'redux'
import logger from 'redux-logger'
import thunk from 'redux-thunk'
import contactsReducer from './contacts/reducer'
import IAppState from './IAppState'
const rootStore = combineReducers({
contacts: contactsReducer,
})
export function createAppStore(): Store<IAppState> {
return applyMiddleware(thunk, logger)(createStore)(rootStore)
}

We provide a factory to create a store, like the reducer we tell the caller that we will return a ‘Store’ that manages ‘IAppState’. Other than that it’s pretty normal. I’ve also added ‘redux-logger’ as it helps track actions and state updates, add it with: yarn add redux-logger

So it’s all ready to go, the final little bit is to wire it into your React application.

# src/App.tsximport * as React from 'react'
import { Provider } from 'react-redux'
import { createAppStore } from './store'const appStore = createAppStore()class App extends React.Component {
public render() {
return (
<Provider store={appStore}>
<div className="App">Hello World</div>
</Provider>
)
}
}
export default App

Nothing special here and Typescript knows the appStore type from the function signature.

Finally you can yarn start

Congratulations, all that just to see hello world. Well that and a react-redux store using Typescript.

The next step, lets get some stuff on the screen.

--

--