Latest Post: What Do We Do with You, Old React?

What Do We Do with You, Old React?

This is a Fun Guide to Upgrading Class-Based React & Redux Stores. Class components and outdated Redux? Discover how to modernize your code with React Hooks and @reduxjs/toolkit, complete with code examples and project structure tips.

7 min read
React Logo with some big cartoon muscles.

Picture this: you just opened an ancient React project that’s pinned to (or using things from) a version prior to React 16.8 (we’ll call it Old React, with class components all over). All around you: creaky Redux code reliant on createStore, legacy tests in react-test-renderer, and a general sense of “we-should-really-update-this” floating in the air.

But how do you make that jump to a more modern version of React (like React 18 or React 19), Hooks, and the latest Redux libraries without rewriting everything from scratch? Let’s dive in, step-by-step, to see how you can modernize your React + Redux codebase—without losing your sanity with a very simple project.

Next.js Banner Image
Do you want to prototype your ideas faster?

Check out my latest Nextjs 15 template! Done with motion, shadcn, support for several languages, newsletter, etc!

Learn More

Project File Overview

Here’s a small glimpse into the file structure so you won’t get lost:

my-old-react-project
├─ package.json
├─ src
│ ├─ components
│ │ ├─ OldDashboard.js
│ │ ├─ OldUserProfile.js
│ ├─ hooks
│ │ └─ useFormHook.js (we'll add this if we decide to modernize)
│ ├─ redux
│ │ ├─ store.js (old -> new redux store config)
│ │ ├─ actions
│ │ │ └─ userActions.js
│ │ └─ reducers
│ │ │ └─ userReducer.js
│ ├─ tests
│ │ ├─ OldDashboard.test.js
│ │ └─ OldUserProfile.test.js
│ └─ index.js
└─ .babelrc

For clarity, I won’t be using TypeScript examples. However, if you spot prop-types in the legacy code you are working with, feel free to explore alternatives like TypeScript, since prop-types is no longer actively recommended and is somewhat deprecated in the React main package. While PropTypes was once a good way to type-check components, it hasn’t evolved as strongly as TypeScript.

We will be covering three topics:

  • Modernizing React Components: From class components to functional components that use Hooks like useState, useEffect and more.
  • Upgrading Redux: Switching from createStore and redux to configureStore and @reduxjs/toolkit.
  • Upgrading Tests: Transitioning from react-test-renderer to @testing-library/react.

But before we do all that, what if we change the library/framework?

Should You Jump to Another Framework?

While alternatives like Vue, Svelte, or Angular can be tempting, React has a large community, plenty of ongoing support, and an ever-growing ecosystem. If your current project is deeply tied to React, upgrading within the ecosystem is often the path of least resistance (and less code rewrite).

Let’s say you’re sticking with React. What’s next?

Refactoring Class Components to Hooks

Old Class-Based Component

// src/components/OldUserProfile.js
import React, { Component } from 'react'

class OldUserProfile extends Component {
  constructor(props) {
    super(props)
    this.state = {
      user: null,
      loading: true,
    }
  }

  componentDidMount() {
    // Simulate fetching user data
    setTimeout(() => {
      this.setState({ user: { name: 'Jane Doe' }, loading: false })
    }, 1000)
  }

  render() {
    const { user, loading } = this.state
    if (loading) {
      return <p>Loading...</p>
    }
    return (
      <div>
        <h1>User Profile</h1>
        <p>Name: {user.name}</p>
      </div>
    )
  }
}

export default OldUserProfile

Modern Hooks-Based Component

// src/components/UserProfile.js
import React, { useState, useEffect } from 'react'

function UserProfile() {
  const [user, setUser] = useState(null)
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    // Simulate fetching user data
    const timer = setTimeout(() => {
      setUser({ name: 'Jane Doe' })
      setLoading(false)
    }, 1000)

    return () => clearTimeout(timer)
  }, [])

  if (loading) {
    return <p>Loading...</p>
  }

  return (
    <div>
      <h1>User Profile (Updated)</h1>
      <p>Name: {user.name}</p>
    </div>
  )
}

export default UserProfile

As you can see, the modern version of the component is much cleaner and easier to read. We’ve replaced the class component with a functional component that uses useState and useEffect hooks to manage state and side effects.

Modernizing Redux—And Why Smaller Libraries Might Be Enough for You

One of the most common upgrades you’ll face is modernizing your Redux store. Up to this point, we can even wonder: “Do we still need Redux?“. The answer is: it depends.

  1. If You Have a Complex Application: Large enterprise-level apps can benefit from Redux’s established ecosystem, robust dev tools, and consistent patterns. @reduxjs/toolkit drastically reduces boilerplate, offers built-in best practices, and integrates well with TypeScript.

  2. If You Want a Lightweight Solution: Libraries like Jotai and Zustand avoid some of Redux’s overhead, focusing on fewer abstractions and less config. They’re ideal for apps that only need to manage a small amount of state or don’t require advanced middleware features.

Ultimately, there’s no one-size-fits-all answer. If your existing code heavily relies on Redux patterns (or your team is already trained in Redux), staying with Redux—especially with Redux Toolkit—makes sense. Let’s assume you’re sticking with Redux for now.

We will know see what is the difference between redux and redux toolkit.

What Is Redux?

At its core, Redux is a predictable state container for JavaScript applications. It centralizes your app’s state in a single store so that data flow is more transparent and easier to debug. Key features include:

  • Single Source of Truth: The entire application state is stored in one place.
  • Predictable State Updates: Actions and reducers update the state in a straightforward, testable way.
  • Easy Debugging: Time-travel debugging and DevTools support make it simpler to track changes.

Why Use Redux Toolkit?

  • Less Boilerplate: Redux Toolkit (@reduxjs/toolkit) dramatically simplifies the store configuration compared to the old createStore approach.
  • Built-In Best Practices: It includes Redux DevTools and popular middleware patterns by default, so you don’t need to configure them separately.
  • Immer Integration: Built-in support for immutable updates makes reducer logic more concise and less error-prone.

Let’s see how you can migrate your old Redux store to Redux Toolkit.

Old Redux Store Configuration

import { createStore } from 'redux'

function todos(state = [], action) {
  switch (action.type) {
    case 'ADD_TODO':
      return state.concat([action.text])
    default:
      return state
  }
}

const store = createStore(todos, ['Use Redux'])

store.dispatch({
  type: 'ADD_TODO',
  text: 'Read the docs',
})

console.log(store.getState())

While this approach still works, @reduxjs/toolkit (RTK) drastically simplifies store setup and reduces boilerplate. Here’s how you can migrate.

Modern Redux Store Configuration

// src/redux/store.js
import { configureStore, createSlice } from '@reduxjs/toolkit'

// 1. Create a slice
const todosSlice = createSlice({
  name: 'todos',
  initialState: ['Use Redux'],
  reducers: {
    addTodo: (state, action) => {
      state.push(action.payload)
    },
  },
})

// 2. Extract the action creator
export const { addTodo } = todosSlice.actions

// 3. Configure the store
export const store = configureStore({
  reducer: {
    todos: todosSlice.reducer,
  },
})

// 4. Dispatching an action
store.dispatch(addTodo('Read the docs'))
console.log(store.getState().todos)

Key Improvements

  • Less Boilerplate: No need to write out action types and separate action creators manually. createSlice does it all in one shot.
  • Immutable Updates: Redux Toolkit uses Immer under the hood, making immutable state updates much simpler.
  • Easier Store Configuration: configureStore automatically sets up Redux DevTools and other recommended defaults.

Okay, we have some new cool functional components and a modern Redux store. But our tests are failing? Let’s fix that next.

Upgrading Tests with @testing-library/react

If your tests use React Test Renderer or Enzyme, consider switching to React Testing Library, which aligns more closely with modern React patterns (including Hooks).

Old Test with React Test Renderer

// src/tests/OldUserProfile.test.js
import React from 'react'
import TestRenderer from 'react-test-renderer'
import OldUserProfile from '../components/OldUserProfile'

test('OldUserProfile renders loading state initially', () => {
  const testInstance = TestRenderer.create(<OldUserProfile />)
  const p = testInstance.root.findByType('p')
  expect(p.props.children).toBe('Loading...')
})

Modern Test with @testing-library/react

// src/tests/UserProfile.test.js
import React from 'react'
import { render, screen } from '@testing-library/react'
import UserProfile from '../components/UserProfile'

test('UserProfile renders loading state initially', () => {
  render(<UserProfile />)
  expect(screen.getByText(/Loading.../i)).toBeInTheDocument()
})

test('UserProfile eventually shows the user name', async () => {
  render(<UserProfile />)
  const userNameElement = await screen.findByText(/Jane Doe/)
  expect(userNameElement).toBeInTheDocument()
})

Conclusion

Upgrading a large, older codebase can feel daunting—but it’s absolutely doable with an incremental approach. Move one component at a time from class-based to Hooks. Tweak your Redux store to @reduxjs/toolkit. And modernize your tests to React Testing Library.

Before you know it, you’ll have replaced the squeaky, dusty sections of your inherited codebase with fresh, maintainable modern React—and hopefully had a bit of fun in the process.

Next.js Banner Image
Do you want to prototype your ideas faster?

Check out my latest Nextjs 15 template! Done with motion, shadcn, support for several languages, newsletter, etc!

Learn More

FAQ about Upgrading Old React Projects

Biceps flex arm vector isolated on white background Vectors by Vecteezy


Share article