Quick Example of Reagent + Re-Frame to React + Redux

Re-Frame example in React+Redux

  • Core image from DeviantArt
  • Blog image made with Pixlr
  • prerequisite: React + Redux are JavaScript libraries used to build UI and state management for SPA apps. Reagent + Re-Frame are their cousins in ClojureScript town.

"Is the learning curve for Redux steeper than Re-Frame?"

A coworker admitted to me recently that he thought there is a higher learning curve for React + Redux compared with Reagent + Re-Frame. My first exposure to a state management library was React + Redux during my internship and then with my senior capstone project, and that allowed me to quickly change gears to using Clojure at my workplace. I have to admit, I was a little sour about not being able to use JavaScript to contribute to the team's Clojure codebase at first, and was prepping myself for the Clojure learning grind. But when I got to the parts of the codebase using Re-Frame I saw a familiar face: it was good 'ol Redux in a different outfit, maybe something with chainmail because of the parentheses used.

In this example I will be making a React + Redux app based off of a ClojureScript example that used Reagent and Re-Frame. It was the first example I learned from when practicing ClojureScript. This app will allow you to change the color of a HH:MM:SS clock being updated every second like the ClojureScript demo.

Now with Facebook's recent update of Redux with the Redux Toolkit, the similarities between Re-Frame and Redux are clearer to see than ever.


Reagent + Re-Frame demo referenced

We'll start by creating a template app based off of the essential create-react-app. This bundles Redux Toolkit and we will be using the shiny up to date new stuff (as of now) for this tutorial:

  • npx create-react-app timer-demo --template redux

When you run npm start you will see the demo application used in the template: a simple counter and looking through it is all you need to start writing your own piece based off the example app.

Starter App

Let's get started.

Here are some things to take note if you have experience with Re-Frame:

  • A selector behaves the same way as a sub in Re-Frame.
  • A dispatch emits actions that are the same as events in Re-Frame.
  • The store is the same as a db in Re-Frame.
  • The configureStore function is the same as the initialize-db event in Re-Frame. The extra touch is the parameters are all the reducers we use.

Creating our Feature + Slice

  1. Create a feature for color state (we are following the way the template created a feature for the counter). This will contain all the state we are using for this example: the color of our clock and the time shown on the clock.
  2. add a new feature by creating a new folder and naming it color
  3. add 3 files to it

    • Clock.jsx
    • ColorInput.jsx
    • colorSlice.js
  4. Add our Redux stuff in colorSlice.js
  5. Create our slice: this includes the label of our slice, initialState, and our reducers

    export const colorSlice = createSlice({
    name: 'color',
    initialState: {
        time: new Date().toTimeString().split(' ')[0],
        timeColor: "#f88"
    },
    reducers: {
        timeColorChange: (state, action) => {
            state.timeColor = action.payload
        },
        timeChange: (state, action) => {
            state.time = action.payload
        }
    }
    })
  6. Pull out our actions created from the slice: the combination of our reducer and action is the same as an event in Re-Frame.

    export const { timeColorChange, timeChange } = colorSlice.actions
  7. Add our selectors (which are the same as subs) and export our slice

    export const selectTimeColor = state => state.color.timeColor
    export const selectTime = state => state.color.time
    export default colorSlice.reducer

Creating our UI elements using the Redux state

  1. Add our Color Picker UI
  2. We use Redux hook functions from the Redux Toolkit. Note, before RTK we would have to connect our component and pass in mapStateToProps. Using hooks is much cleaner, a very welcome change in RTK!
  3. This component will be the input box that allows us to change the color of our time

Add the following to ColorInput.jsx:

import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { timeColorChange, selectTimeColor } from './colorSlice'
const ColorInput = () => {
    const color = useSelector(selectTimeColor)
    const dispatch = useDispatch()
    return (
        <div>
            <input type="text"
                value={color}
                onChange={(e) => dispatch(timeColorChange(e.target.value))} />
        </div>
    )
}
export default ColorInput
  1. Add our Clock UI
  2. Notice that we use the useEffect hook to cause a change in state (or in this case dispatching a time change in our redux state) when the component mounts. It automatically updates our component by causing a re-render each second.
  3. For the visible UI we are returning we only need a selector since we are just reading the value of the color from Redux state.
  4. Notice how the JS interopt used in ClojureScript matches how we are setting the time in our JS app:

    ; Clojure version
    (-> (js/Date.)
    .toTimeString
    (str/split " ")
    first)
    ;; JS version
    ;; new Date().toTimeString().split(' ')[0]

Add the following to Clock.jsx:

import React, { useEffect } from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { selectTimeColor, selectTime, timeChange } from './colorSlice'
const Clock = () => {
    const color = useSelector(selectTimeColor)
    const time = useSelector(selectTime)
    const dispatch = useDispatch()
    useEffect(() => {
        setInterval(() => {
            dispatch(timeChange(new Date().toTimeString().split(' ')[0]))
        }, 1000)
    }, [dispatch])
    return (
        <div style={{ color: `${color}` }}>
            <h1>{time.toString()}</h1>
        </div>
    )
}
export default Clock

Hook everything up

  1. Add your components to the main App.js
  2. Add your color slice to the configureStore function in store.js so it is registered. The store is like the db of Re-Frame, containing the "global" application state that can be referenced by any component subscribed. In index.js you see that a Provider component is the wrapper around the app and its components, providing that state.

    export default configureStore({
    reducer: {
        counter: counterReducer,
        color: colorReducer
    },
    });
  3. Test it! You should be able to update the color and the clock will match accordingly and the time updates every second too.

Here is the finished product:

Treat yourself!

Celebrate

Congratulations! You have successfully completed a port of a Reagent + Re-Frame app to React + Redux!