Developing component-based web apps using React & Redux

Carlos Paelinck
5 min readJul 12, 2016

Publications is a fun side project Mike Kelly and I use as a sandbox for exploring different web app development toolkits. It’s a web app for simple page layout and print design. Let’s explore how React and Redux are ideal for component-based web apps that require real-time UI updates based on a changing data model.

Why React & Redux?

In Publications there are numerous parts of the UI that must be updated when a shape on a canvas is changed. React is a thin library for creating user interfaces. Redux is a predictable state container. It’s important when data changes only the relevant parts of the UI are updated. React and Redux make this easier to develop and understand. As seen below in green outlines, when a shape is resized only the shape, width and height input boxes are re-rendered.

Smooth 60 frames per second shape manipulation

Immutability & Redux

A common user action in Publications is a change to a shape on the canvas. Shapes can have different color, borders, corners, dimensions and placements. Redux allows you to maintain a predicatble state for your entire app using a series of actions and reducers.

Actions define the changes you want to happen to your state. Actions are also where async API calls are made. Reducers define how the new state is calculated based on the action that was dispatched.

Let’s look at a simplified version of a changing a property on the selected shape using Redux.

// action to change a property on a shape
export const
updateShape = shape => dispatch => {
dispatch({
type: 'UPDATE_SELECTED_SHAPE',
payload: shape
})
}
// action to async download a document from the API
export
const getDocument = id => dispatch => {
fetch(`api.com/document/${id}`)
.then(res => res.json())
.then(json => dispatch({
type: 'RECIEVE_DOCUMENT',
payload: json
})
}

Redux actions can be called from any React container component and passed down as callbacks to presentational components. This is useful since the user can fire an action from multiple UI components. A typical pattern for actions is exporting a function that may accept parameters needed to describe the change and curry the dispatcher into the exported function.

The reducer mutates our app state based on the dispatched action. Shown below is the logic for returning a new state with the updated shape.

export function reducer(state = {}, action) {

switch (action.type) {
case 'UPDATE_CURRENT_SHAPE':
const { payload } = action
const idx = currentShapes.findIndex(shape => {
return payload.id === shape.id
})
// Spread out the properties of the old shape object
// and replace them with the updated shape properties
const selectedShape = {
...state.selectedShape,
...payload
}
// Splice out the old shape object from the shapes array
// and spread out the shapes around it. Include the new
// shape in the shapes array.
const shapes = [
...currentShapes.slice(0, idx),
...currentShapes.slice(idx + 1, currentShapes.length),
selectedShape
]
const updatedDocument = { ...state.currentDocument, shapes } return { ...state, currentDocument: updatedDocument }
}
}

To prevent side-effects and ensure React will render only the minimum updates required to the DOM, we must never directly modify the objects but instead create new ones. The UI components bound to the document reducer will automatically re-render once the new state is returned.

Component-Based UI with Redux

There are two types of components often found in React-based web apps— containers and presentational components. Containers define how the view works, calls actions and are bound to Redux stores. Presentational components are visual, have data passed down as props and are often reusable between different views.

Let’s look at a simplified example of container and presentational components for a document in Publications.

class DocumentContainer extends React.Component {

shapeSelected
(shape = {}) {
// The dispatcher is automatically included on props
// for bound container components.


const { dispatch } = this.props
DocumentActions.shapeSelected(shape)(dispatch)
}
render() {
return <div>
<Canvas
document={ this.props.document }
shapeSelected={ this.shapeSelected } />
</div>
}
}
// The connect function subscribes the component to the state
// specified in the params.
export default connect(
state => ({ document: state.document })
)(DocumentContainer)

The document container component defines the actions and passes action callbacks and data to presentational components via props. The connect function subscribes the component to the store’s updates. When a reducer returns a new state, this.props are updated and child presentational components are automatically re-rendered.

A presentational component is unaware of Redux or how data is obtained. It is responsible for how something should look and generally be free of any business logic. It can also contain other presentational components.

export const Canvas = ({ document, shapeSelected }) => {  const shapes = document.shapes.map(shape => (
<Shape
shape={ shape }
onClick={ () => shapeSelected(shape) } />
))
return <div>{ shapes }</div>
}

The canvas presentational component renders all the shapes belonging to that document and attaches an onClick hander when the shape is selected.

Closing Thoughts

React and Redux, while separate libraries, work very well together to build component-based web apps. React prefers an unidirectional data flow where data flows down from container to presentational components and action callbacks go upwards.

React & Redux Unidirectional Data Flow

The key things to remember when using React and Redux are:

  • The app state is the single source of data.
  • Actions describe what should happen to state.
  • Reducers calculate the new state based on the action.
  • Container components and their child presentation components automatically re-render when a new state returned from the reducer.
  • Components never mutate the app state.

Redux can seem overwhelming and confusing at first, but a good start is watching the tutorials by Dan Abramov. You can see Publications in action or visit the source on GitHub.

--

--

Carlos Paelinck

Software Developer at Formidable. React, Node & Apple native platforms.