React-Redux with Typescript

Sharath Holla
9 min readJan 23, 2019

--

React is one of the most popular framework to build a Web Application. However, as we start building more complex application state management becomes a necessity. Redux is one such tool, which helps in managing the state of entire application in a single entity called Store.

Brief description of Redux

Official Redux documentation describes it as a predictable state container for JavaScript apps. Lets break the definition here.

In traditional react, each component maintains it’s own state. If you want to move state component from parent to child component, you can pass it using props. However, if one wants to share state between two components which are not in parent-child relationship, then it gets messy

This is where state management tools like Redux come in handy. Redux maintains a single Store. Any component connected to this store can update state, or read state. The connected component that has access to store is called container. When any component wants to update the store, it calls an Action. Then we have functions called Reducers, which reads these actions and return an updated state. All the components update based on the new state.

Redux workflow

We use react-redux library in our example.

Typescript with react

Most of the react applications are developed using JavaScript. This is beneficial if you want to develop application quickly, without worrying about type-checking. However, for better maintainability, or if more developers are involved in app development, typescript comes handy, by adding type checking into the code. This helps in avoiding many possible bugs during development. It also helps while using IDE’s as they can provide better auto completes.

Create React App has added support for generation of a typescript project during project creation (Version 2.1.0 and onward). However for existing projects, one can install typescript using npm or yarn.

Getting hands dirty with code

Let us build an app, where you enter 2 numbers, and results of few binary operations will be listed. Following is the screenshot of the final application we are planning

The project is shared at https://github.com/ksholla20/react-redux-typescript-starter/tree/master
The checkpoint versions are shared in respective sections

Project creation

Let’s start with creation of a typescript project

npx create-react-app calculator-app --typescript

This will create a folder calculator-app with typescript
The code checkpoint is shared here

Adding View layer

We will add 2 input components, operandA and operandB
Also there will be paragraphs listing the binary operation results

We will define a component to take in numbers and display

//src/components/numberInput.tsximport * as React from "react";
interface Props {
name: string,
value: number
}
interface ClassState {
value: number;
}
const inputStyle = {
marginTop: '10px',
display: 'inline-flex'
}
const divStyle = {
width: '150px'
}
export class NumberInput extends React.Component<Props, ClassState> {
constructor(props: any, context: any) {
super(props, context);
this.state = {value: props.value};
this.onNumberChange = this.onNumberChange.bind(this);
}
onNumberChange(e:any){
this.setState({value: e.target.value});
}
render(){
return (
<div style={inputStyle}>
<div style={divStyle}>{this.props.name}</div>
<input
name={this.props.name}
type="number"
value={this.state.value}
onChange={this.onNumberChange}
/>
</div>
);
}
}
export default NumberInput;

Unlike in javascript, we import react as import * as React from “react”;
To define Props, we need to define an interface and define all the prop types. Similarly, to define local state, we need to first define an interface declaring all state variables, and then define it in the component

Now let us define a component which calls 2 input components. As we have not yet integrated state management, we are currently calling a dummy value.

//src/components/inputHolder.tsx
import * as React from "react";
import NumberInput from '../numberInput'
interface Props {
}
const holderStyle = {
marginTop: '20px',
display: 'inline-grid'
}
export class InputHolder extends React.Component<Props> {
render(){
const initValue = [0, 1];
return (
<div style = {holderStyle}>
<NumberInput name = "OPERANDA" value = {initValue[0]} />
<NumberInput name = "OPERANDB" value = {initValue[1]} />
</div>
);
}
}
export default InputHolder;

In the same way, we define output holders. Again, as state management in not integrated yet, we have dummy values in the components

//src/components/numberOutput
import * as React from "react";
interface Props {
name: string,
value: number
}
const inputStyle = {
marginTop: '10px',
display: 'inline-flex'
}
const divStyle = {
width: '150px'
}
export class NumberOutput extends React.Component<Props> {
render(){
return (
<div style={inputStyle}>
<div style={divStyle}>{this.props.name}</div>
<div>: {this.props.value}</div>
</div>
);
}
}
export default NumberOutput;

And the holder corresponding to this

//src/components/outputHolder.tsx
import * as React from "react";
import NumberOutput from '../numberOutput'
interface Props {
}
const holderStyle = {
marginTop: '20px',
display: 'inline-grid'
}
export class OutputHolder extends React.Component<Props> {
render(){
const initValue = [0,1];
const operandA = initValue[0], operandB = initValue[1];
const sum = operandA + operandB;
const dif = operandA - operandB;
const mul = operandA * operandB;
const dvs = operandB ? 0:operandA / operandB;
return (
<div style = {holderStyle}>
<NumberOutput name = "ADDITION" value = {sum}/>
<NumberOutput name = "SUBTRACTION" value = {dif}/>
<NumberOutput name = "MULTIPLICATION" value = {mul}/>
<NumberOutput name = "DIVISION" value = {dvs}/>
</div>
);
}
}
export default OutputHolder;

And finally, we piece it all in a container

//src/containers/calculator.tsx
import * as React from "react";
import InputHolder from '../../components/inputHolder'
import OutputHolder from '../../components/outputHolder'
interface Props {
}
const calculatorStyle = {
marginLeft: '30%',
marginTop: '20px',
display: 'grid'
}
const divStyle = {
color: 'indigo',
fontSize: 'xx-large'
}
export class Calculator extends React.Component<Props> {
render(){
return (
<div style={calculatorStyle}>
<div style = {divStyle}>This is a simple calculator</div>
<InputHolder/>
<OutputHolder/>
</div>
);
}
}
export default Calculator;

We now call this in our react application. We modified the App.tsx to call this container

//src/App.tsx
import * as React from "react";
import Calculator from './containers/calculator'
export const App = (): JSX.Element => {
return (
<div>
<Calculator />
</div>
);
};
export default App;

With this, we have a react app which just displays the required UI layer.
The code checkpoint is shared here

Adding Redux State management

We will be installing following npm packages for this project.

  • react-redux
  • react-router-dom
  • react-router-redux
  • redux-actions
  • redux-observable

And corresponding typescript packages as dev dependencies

With standard release version of react-router-redux, we faced issue in provider. Hence we moved to react-router-redux@next

npm install -S react-redux react-router-dom react-router-redux@next redux-actions redux-observable
npm install -D @types/react-redux @types/react-router-dom @types/react-router-redux @types/redux-actions

Lets define actions and states. In current example, 2 types of actions are required. Change in operandA and change in operandB. Similarly, state contains 2 variables.

Following is the model file, where we define require interface

//src/interfaces/calculatorModels.ts
export interface CalculatorModel {
operandA: number;
operandB: number;
}
export interface OperandAPayload {
operandA: number;
}
export interface OperandBPayload {
operandB: number;
}

Define the actions file

//src/actions/calculatorActions.ts
import { createAction } from "redux-actions";
import { OperandAPayload, OperandBPayload } from "../interfaces/calculatorModels";
export const Type = {
SET_OPERANDA: "SET_OPERANDA",
SET_OPERANDB: "SET_OPERANDB"
};
export const setOperandA = createAction<OperandAPayload>(
Type.SET_OPERANDA
);
export const setOperandB = createAction<OperandBPayload>(
Type.SET_OPERANDB
);

Finally define reducers

//src/reducers/calculatorReducer.ts
import { combineReducers } from "redux";
import { handleActions, Action } from "redux-actions";
import { CalculatorModel, OperandAPayload, OperandBPayload} from "../interfaces/calculatorModels";
import * as CalculatorActions from "../actions/calculatorActions";
export type State = {
readonly operandA: number;
readonly operandB: number;
};
const initialState: State = {
operandA: 0,
operandB: 1
};
export const calculatorReducer = handleActions<State, CalculatorModel>(
{
[CalculatorActions.Type.SET_OPERANDA]: (
state: State,
action: Action<OperandAPayload>
) => {
return {
operandA: action.payload ? (action.payload as any) : 0,
operandB: state.operandB
};
},
[CalculatorActions.Type.SET_OPERANDB]: (
state: State,
action: Action<OperandBPayload>
) => {
return {
operandA: state.operandA,
operandB: action.payload ? (action.payload as any) : 0
};
}
},
initialState
);

We might have many reducers. Hence we will have a master reducer which will combine all the reducers

//src/reducers/index.ts
import { combineReducers } from "redux";
import { routerReducer as router, RouterState } from "react-router-redux";
import { calculatorReducer, State as CalculatorState
} from "./calculatorReducer";
import { CalculatorModel } from "../interfaces/calculatorModels";
interface StoreEnhancerState {}export interface RootState extends StoreEnhancerState {
router: RouterState;
calculatorReducer: CalculatorState;
}
export const rootReducer = combineReducers<RootState>({
router,
calculatorReducer: calculatorReducer as any
});

We will also define a store, which will apply this reducer as middleware. This will be used while defining the root component

//src/reducers/store.ts
import { createStore, applyMiddleware, compose } from "redux";
import { createEpicMiddleware } from "redux-observable";
import { routerMiddleware as createRouterMiddleware } from "react-router-redux";
import createHistory from "history/createBrowserHistory";
import { rootReducer, RootState } from "./index";
export const history = createHistory();export const routerMiddleware = createRouterMiddleware(history);export function configureStore(initialState?: RootState) {
// configure middlewares
const middlewares = [routerMiddleware];
// compose enhancers
const enhancer = compose(applyMiddleware(...middlewares));
// create store
return createStore(rootReducer, initialState!, enhancer);
}

We have our actions and reducers ready. Let us use this in the existing application

First, let us modify index.tsx in src folder to use the store, and pass it as a prop to its children

//src/index.tsx
import * as React from "react";
import * as ReactDOM from "react-dom";
import * as serviceWorker from "./serviceWorker";
import { Provider } from "react-redux";
import { configureStore, history } from "./reducers/store";
import { ConnectedRouter } from "react-router-redux";
import App from './App';const store = configureStore();ReactDOM.render(
<Provider store={store}>
<ConnectedRouter history={history} store={store}>
<App />
</ConnectedRouter>
</Provider>,
document.getElementById("root")
);
serviceWorker.unregister();

We can optionally change the App.tsx to add routes. This is not a necessary step in this demo, but nice to have one

//src/App.tsx
import * as React from "react";
import { Route, Switch } from "react-router-dom";
import Calculator from './containers/calculator'
export const App = (): JSX.Element => { return (
<div>
<Route exact={true} path="/" component={Calculator} />
</div>
);
};
export default App;

We will now modify the container to pack these states and actions into props

//src/containers/calculator.tsx
import * as React from "react";
import { connect } from "react-redux";
import { bindActionCreators, Dispatch } from "redux";
import * as calculatorActions from "../../actions/calculatorActions";
import { RootState, rootReducer } from "../../reducers";
import { CalculatorModel } from "../../interfaces/calculatorModels";
import InputHolder from '../../components/inputHolder'
import OutputHolder from '../../components/outputHolder'
interface Props {
calculatorState: CalculatorModel;
actions: any;
}
const calculatorStyle = {
marginLeft: '30%',
marginTop: '20px',
display: 'grid'
}
const divStyle = {
color: 'indigo',
fontSize: 'xx-large'
}
export class Calculator extends React.Component<Props> {
render(){
return (
<div style={calculatorStyle}>
<div style = {divStyle}>This is a simple calculator</div>
<InputHolder
actions = {this.props.actions}
calculatorState = {this.props.calculatorState}
/>
<OutputHolder
calculatorState = {this.props.calculatorState}
/>
</div>
);
}
}
const actions: any = Object.assign({}, calculatorActions);function mapStateToProps(state: RootState) {
return {
calculatorState: state.calculatorReducer
};
}
function mapDispatchToProps(dispatch: Dispatch) {
return {
actions: bindActionCreators(actions, dispatch)
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(Calculator);

We can now change the individual components to handle these states

//src/components/inputHolder.tsx
import * as React from "react";
import NumberInput from '../numberInput'
import { CalculatorModel } from "../../interfaces/calculatorModels";
interface Props {
calculatorState: CalculatorModel;
actions: any;
}
const holderStyle = {
marginTop: '20px',
display: 'inline-grid'
}
export class InputHolder extends React.Component<Props> {
render(){
return (
<div style = {holderStyle}>
<NumberInput
name = "OPERANDA"
value = {this.props.calculatorState.operandA}
callback = {this.props.actions.setOperandA}
/>
<NumberInput
name = "OPERANDB"
value = {this.props.calculatorState.operandB}
callback = {this.props.actions.setOperandB}
/>
</div>
);
}
}
export default InputHolder;

Note that we now are sending the operand value from global state

//src/components/numberInput.tsx
import * as React from "react";
interface Props {
name: string,
value: number
callback: any;
}
const inputStyle = {
marginTop: '10px',
display: 'inline-flex'
}
const divStyle = {
width: '150px'
}
export class NumberInput extends React.Component<Props> {
constructor(props: any, context: any) {
super(props, context);
this.onNumberChange = this.onNumberChange.bind(this);
}
onNumberChange(e:any){
this.props.callback(e.target.value);
}
render(){
return (
<div style={inputStyle}>
<div style={divStyle}>{this.props.name}</div>
<input
type="number"
value={this.props.value}
onChange={this.onNumberChange}
/>
</div>
);
}
}
export default NumberInput;

Note here that we removed the local state, and we now call actions when number is changed

//src/components/outputHolder.tsx
import * as React from "react";
import NumberOutput from '../numberOutput'
import { CalculatorModel } from "../../interfaces/calculatorModels";
interface Props {
calculatorState: CalculatorModel;
}
const holderStyle = {
marginTop: '20px',
display: 'inline-grid'
}
export class OutputHolder extends React.Component<Props> {
render(){
const operandA:number = this.props.calculatorState.operandA;
const operandB:number = this.props.calculatorState.operandB;
const sum = Number(operandA) + Number(operandB);
const dif = operandA - operandB;
const mul = operandA * operandB;
const dvs = operandB ? operandA / operandB:0;
return (
<div style = {holderStyle}>
<NumberOutput name = "ADDITION" value = {sum}/>
<NumberOutput name = "SUBTRACTION" value = {dif}/>
<NumberOutput name = "MULTIPLICATION" value = {mul}/>
<NumberOutput name = "DIVISION" value = {dvs}/>
</div>
);
}
}
export default OutputHolder;

Now we have all our pieces ready to run our application

Conclusion

The goal of this article is to get started with react-redux using Typescript. Emphasis was not given on beautifying the UI, but add a basic redux code to a new project. The project is shared in https://github.com/ksholla20/react-redux-typescript-starter/tree/master

--

--