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

Zac Tolley
Scropt

--

In the first 2 parts of this tutorial, we created a React Redux project that uses Typescript and implemented a store make up of actions and reducers with type information to assist in writing correct code. The overall aim is to create a React Redux application that uses type information to reduce errors and mistakes, and hopefully the amount of test code too.

Part 3 will build the presentation layer made up of containers and components and demonstrate how type information can assist in providing better intellisense so you know things like properties and methods available to you, along with their types, without having to go find out yourself.

Let's use Bootstrap for styling because everyone uses Bootstrap right?

yarn add bootstrap reactstrap
yarn add --dev @types/reactstrap

Add the Bootstrap package along with Reactstrap, a handy UI Component library, along with it’s associated type information. Then update the top Index.tsx file to include Bootstrap styles.

# src/index.tsximport * as React from 'react'
import * as ReactDOM from 'react-dom'
import App from './App'
import 'bootstrap/dist/css/bootstrap.min.css'
import './index.css'
import registerServiceWorker from './registerServiceWorker'ReactDOM.render(<App />, document.getElementById('root')
as HTMLElement)
registerServiceWorker()

Let's prove that we have a store and can use it.

Typically when you have a component that is linked to the store you call it a ‘Container’. Some people create a container that just connects a component to the store, some put it all in one file. For now, to keep things simple we will create a container that provides the layout, logic and connects to the store in a single file.

# src/containers/ContactList.tsximport * as React from 'react'
import { connect } from 'react-redux'
import { Container } from 'reactstrap'
import IContact from '../models/IContact'
import IAppState from '../store/IAppState'
import { getContacts } from '../store/contacts/actions'interface IContactListProps {
contacts: IContact[]
getContacts: typeof getContacts
}
class ContactList extends React.Component<IContactListProps, object> {
public componentDidMount() {
this.props.getContacts()
}
public render() {
const { contacts } = this.props
return (
<Container>
<h1>Contact List</h1>
{contacts.map(this.renderContactResult)}
</Container>
)
}
private renderContactResult(contact: IContact) {
return (
<div key={contact.id}>{contact.name}</div>
)
}
}
function mapStateToProps(state: IAppState) {
return {
contacts: state.contacts.contacts,
}
}
export default connect(
mapStateToProps,
{ getContacts }
)(ContactList)

It’s a bit long but it looks like a regular React Redux container. The only differences are that functions specify the types for their parameters and the component itself requires type information for properties and state.

When writing a component in Typescript you need to specify type information for its properties and any state it holds, this is a bit like PropTypes but instead of working at runtime it is used at build time and helps your IDE provide better intellisense. Specify the type information and then use that in the class declaration, first the props interface then the state interface. The second parameter in this example is set to ‘object’, this is because there is no state information and this will end up being an empty object.

I chose to completely remove PropTypes from my component as the majority of bugs are now caught at build time. PropTypes may still be useful during development if your back-end doesn’t conform to the agreed model.

Throughout the process of writing this component, it was much easier to specify properties, i.e. when writing the function to render a contact the editor suggested the properties available and their associated types when typing ‘contact.’. The whole experience was easier than using plain JavaScript.

Now we can see a list of contacts, let’s add a contact detail screen. Being a SPA (Single Page Application) we need to add React Router to allow the user to navigate to different parts of the application.

yarn add react-router
yarn add --dev @types/react-router-dom

If you only add react-router the Typescript compiler will tell you there’s no type information and suggest the name of the type library to add.

# src/containers/ContactDetails.tsximport * as React from 'react'
import { connect } from 'react-redux'
import { Container } from 'reactstrap'
import IContact from '../models/IContact'
import IAppState from '../store/IAppState'
import { clearCurrentContact, getContact } from '../store/contacts/actions'interface IContactDetailsProps {
contact: IContact | null
getContact: typeof getContact
clearCurrentContact: typeof clearCurrentContact
match: any
}
class ContactDetails extends React.Component<IContactDetailsProps, object> {
public componentDidMount() {
const contactId = this.props.match.params.contactId
this.props.getContact(contactId)
}
public componentWillUnmount() {
this.props.clearCurrentContact()
}
public render() {
if (!this.props.contact) {
return null
}
const contact = this.props.contactreturn (
<Container>
<h1>Contact Details: {contact.name}</h1>
<dl>
<dt>email</dt>
<dd>{contact.email}</dd>
</dl>
</Container>
)
}
}
function mapStateToProps(state: IAppState) {
return {
contact: state.contacts.currentContact,
}
}
export default connect(
mapStateToProps,
{ getContact, clearCurrentContact }
)(ContactDetails)

Just like the ContactList component, we specify the type information for the container properties. The match property comes from React router, and I’ll admit I wasn’t too bothered about type information so it was easiest to bypass type checking for that property and say it could be ‘any’ type. I’ll go back and look at React router later and find out if there is type information for that but I’m not sure there’s any benefit.

This container uses react life-cycle hooks to say when the component has mounted, go and fetch the contact associated with the ID passed in. When the component it unmounted then reset that, so the next time we load this component it doesn’t flash up with the old contact information.

So now we need to allow the contact to see this page via a link. First lets setup React router

# /src/App.tsximport * as React from 'react'
import { Provider } from 'react-redux'
import { BrowserRouter, Route, Switch } from 'react-router-dom'
import ContactDetails from './containers/ContactDetails'
import ContactList from './containers/ContactList'
import { createAppStore } from './store'const appStore = createAppStore()class App extends React.Component {
public render() {
return (
<React.Fragment>
<Provider store={appStore}>
<BrowserRouter>
<React.Fragment>
<Switch>
<Route
path="/"
component={ContactList}
exact={true}
/>
<Route
path="/details/:contactId"
component={ContactDetails}
exact={true}
/>
</Switch>
</React.Fragment>
</BrowserRouter>
</Provider>
</React.Fragment>
)
}
}
export default App

In the ContactList component, you can now tell it to link the detail screen. Add an import for the react router Link function and update the line displaying the contact to:

<Link to={`/details/${contact.id}`}>{contact.name}</Link>

Fire it all up and now you can view a list of contacts (add some more fake data) and select a contact to see it’s details.

A note on React router. I found relatively little information for using React router with Typescript. Normally I would use a common layout and then use that in the router declaration but could not find a good example of this and struggled to get it to work, so chose not to use them for this tutorial. I will come back to this later.

The next part of this will add a form to allow editing and adding new contacts.

--

--