Skip to content
On this page

Migrating from Redux & RTK

Inglorious Store is a drop-in replacement for Redux, making migration straightforward. You can gradually adopt Inglorious Store while keeping your existing react-redux setup intact.

Why Migrate?

Redux/RTK drawbacks:

  • Verbose boilerplate (action creators, reducers, slices)
  • Complex async logic (thunks, createAsyncThunk)
  • Manual reshaping of state for multiple instances
  • Difficult to manage lifecycle events (create/destroy)

Inglorious Store benefits:

  • No action creators or reducers
  • Async handlers integrated natively
  • Built-in entity management
  • Lifecycle events (create/destroy) out of the box
  • Same Redux DevTools support

Step 1: Drop-in Replacement

The simplest migration: replace your Redux store with Inglorious Store while keeping your components unchanged.

Before (Redux)

javascript
// store.js
import { configureStore } from "@reduxjs/toolkit"
import todosReducer from "./todosSlice"

export const store = configureStore({
  reducer: {
    todos: todosReducer,
  },
})
1
2
3
4
5
6
7
8
9

After (Inglorious Store)

javascript
// store.js
import { createStore } from "@inglorious/store"

const types = {
  todoList: {
    addTodo(entity, text) {
      entity.todos.push({ id: Date.now(), text, done: false })
    },
    toggleTodo(entity, id) {
      const todo = entity.todos.find((t) => t.id === id)
      if (todo) todo.done = !todo.done
    },
  },
}

const entities = {
  todos: { type: "TodoList", todos: [] },
}

export const store = createStore({ types, entities })
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

Your React components stay the same:

javascript
// components/TodoList.jsx
import { useSelector, useDispatch } from "react-redux"

export function TodoList() {
  const todos = useSelector((state) => state.todos.todos)
  const dispatch = useDispatch()

  return (
    <div>
      {todos.map((todo) => (
        <input
          key={todo.id}
          type="checkbox"
          checked={todo.done}
          onChange={() => dispatch({ type: "toggleTodo", payload: todo.id })}
        />
      ))}
    </div>
  )
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

No component changes needed!

Step 2: Switch to notify() API

Once you're comfortable, replace dispatch with notify() for a cleaner API:

Before (dispatch)

javascript
dispatch({ type: "addTodo", payload: "Buy groceries" })
dispatch({ type: "toggleTodo", payload: 123 })
1
2

After (notify)

javascript
store.notify("addTodo", "Buy groceries")
store.notify("toggleTodo", 123)
1
2

Or in React components, use @inglorious/react-store:

javascript
import { useNotify } from "@inglorious/react-store"

export function TodoList() {
  const notify = useNotify()

  return <button onClick={() => notify("addTodo", "New task")}>Add Task</button>
}
1
2
3
4
5
6
7

Much cleaner!

Step 3: Convert Async Thunks

Before (Redux Thunk)

javascript
const fetchTodos = createAsyncThunk(
  "todos/fetchTodos",
  async (userId, { rejectWithValue }) => {
    try {
      const response = await fetch(`/api/users/${userId}/todos`)
      return response.json()
    } catch (error) {
      return rejectWithValue(error.message)
    }
  },
)

const todosSlice = createSlice({
  name: "todos",
  initialState: { data: [], loading: false, error: null },
  extraReducers: (builder) => {
    builder
      .addCase(fetchTodos.pending, (state) => {
        state.loading = true
      })
      .addCase(fetchTodos.fulfilled, (state, action) => {
        state.loading = false
        state.data = action.payload
      })
      .addCase(fetchTodos.rejected, (state, action) => {
        state.loading = false
        state.error = action.payload
      })
  },
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

After (Inglorious Store)

javascript
const types = {
  todoList: {
    async fetchTodos(entity, userId, api) {
      try {
        entity.loading = true
        entity.error = null

        const response = await fetch(`/api/users/${userId}/todos`)
        const data = await response.json()

        api.notify("fetchTodosSuccess", data)
      } catch (error) {
        api.notify("fetchTodosError", error.message)
      }
    },

    fetchTodosSuccess(entity, data) {
      entity.loading = false
      entity.data = data
    },

    fetchTodosError(entity, error) {
      entity.loading = false
      entity.error = error
    },
  },
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

Usage in components stays the same:

javascript
const notify = useNotify()
notify("fetchTodos", userId)
1
2

Step 4: Manage Multiple Instances

Redux requires manual state reshaping for multiple instances. Inglorious Store handles it automatically.

Before (Redux - Manual)

javascript
const store = configureStore({
  reducer: {
    workTodos: todosReducer,
    personalTodos: todosReducer,
  },
})

// To add a new list at runtime:
// You need to create new slices or use a complex reducer pattern
1
2
3
4
5
6
7
8
9

After (Inglorious Store - Automatic)

javascript
const entities = {
  workTodos: { type: "TodoList", todos: [] },
  personalTodos: { type: "TodoList", todos: [] },
}

// To add a new list at runtime:
store.notify("add", {
  id: "projectTodos",
  type: "TodoList",
  todos: [],
})
1
2
3
4
5
6
7
8
9
10
11

In components:

javascript
const workTodos = useSelector((state) => state.workTodos.todos)
const personalTodos = useSelector((state) => state.personalTodos.todos)
const projectTodos = useSelector((state) => state.projectTodos?.todos || [])
1
2
3

Step 5: Leverage Lifecycle Events

Inglorious Store has built-in lifecycle events (create, destroy) that Redux lacks.

javascript
const types = {
  todoList: {
    create(entity) {
      // Initialize when entity is added
      console.log(`Created todo list: ${entity.id}`)
    },

    destroy(entity) {
      // Cleanup when entity is removed
      console.log(`Destroyed todo list: ${entity.id}`)
    },

    addTodo(entity, text) {
      entity.todos.push({ id: Date.now(), text, done: false })
    },
  },
}

// These handlers fire automatically
store.notify("add", { id: "myList", type: "TodoList", todos: [] })
store.notify("remove", { id: "myList" }) // destroy handler fires
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

Step 6: Embrace the api Object

The api parameter in handlers gives you access to the entire state and event system:

javascript
const types = {
  todoList: {
    async loadUserTodos(entity, userId, api) {
      // Read from other entities
      const user = api.getEntity("user")
      const allEntities = api.getEntities()
      const allTodoLists = api.getEntities("todoList")

      // Make API call
      const todos = await fetch(`/api/users/${userId}/todos`).then((r) =>
        r.json(),
      )

      // Trigger other events (queued, not immediate)
      api.notify("todosLoaded", todos)
      api.notify("analytics:trackEvent", { action: "loadedTodos" })
    },

    todosLoaded(entity, todos) {
      entity.todos = todos
      entity.loading = false
    },
  },
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

Conversion Utilities

Inglorious Store provides helpers to automate migration from RTK:

convertAsyncThunk()

Convert Redux async thunks to Inglorious handlers:

javascript
import { convertAsyncThunk } from "@inglorious/store/rtk"

const fetchTodos = async (userId) => {
  const res = await fetch(`/api/users/${userId}/todos`)
  return res.json()
}

const handlers = convertAsyncThunk("fetchTodos", fetchTodos, {
  onPending: (entity) => (entity.loading = true),
  onError: (entity, error) => (entity.error = error),
})

const types = {
  todoList: {
    ...handlers,
  },
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

convertSlice()

Convert Redux Toolkit slices to Inglorious types:

javascript
import { convertSlice } from "@inglorious/store/rtk"

const todoListType = convertSlice(todosSlice, {
  asyncThunks: {
    fetchTodos: async (userId) =>
      (await fetch(`/api/users/${userId}/todos`)).json(),
  },
})

const types = {
  todoList: todoListType,
}
1
2
3
4
5
6
7
8
9
10
11
12

createRTKCompatDispatch()

For gradual migration, use Redux-style dispatch:

javascript
import { createRTKCompatDispatch } from "@inglorious/store/rtk"

const api = store.api // from context or plugin
const dispatch = createRTKCompatDispatch(api, "todos")

// Still works with Redux-style dispatch
dispatch({ type: "todos/addTodo", payload: "Buy milk" })
// becomes: api.notify("#todos:addTodo", "Buy milk")
1
2
3
4
5
6
7
8

Migration Checklist

  • [ ] Replace configureStore() with createStore()
  • [ ] Convert reducers to event handlers in types
  • [ ] Migrate action creators to handler event names
  • [ ] Convert async thunks to async handlers
  • [ ] Update entity selectors (same as before, usually)
  • [ ] Switch dispatch() to notify() in new code
  • [ ] Test with Redux DevTools (should work unchanged)
  • [ ] Remove Redux Toolkit dependencies

Common Patterns

Local Loading State

Redux:

javascript
const todosSlice = createSlice({
  name: "todos",
  initialState: { data: [], loading: false },
  // ... reducer logic
})
1
2
3
4
5

Inglorious:

javascript
const types = {
  todoList: {
    async fetchTodos(entity, id, api) {
      entity.loading = true
      const data = await fetchApi(id)
      api.notify("loaded", data)
    },
    loaded(entity, data) {
      entity.data = data
      entity.loading = false
    },
  },
}
1
2
3
4
5
6
7
8
9
10
11
12
13

Cross-Entity Communication

Redux: Requires complex selectors and merging reducers

Inglorious:

javascript
const types = {
  notification: {
    create(entity) {
      // Auto-dismiss after 3 seconds
      setTimeout(() => api.notify("remove", { id: entity.id }), 3000)
    },
  },
}
1
2
3
4
5
6
7
8

Undo/Redo

Both support Redux DevTools time-travel, but Inglorious gives you finer control:

javascript
const systems = [
  {
    onStateChange(state, prevState) {
      // Track history for undo/redo
      history.push({ state, prevState })
    },
  },
]
1
2
3
4
5
6
7
8

Next Steps

  1. Start Small: Migrate one slice at a time
  2. Keep Redux DevTools: Inglorious works seamlessly with them
  3. Use @inglorious/react-store: For better React integration
  4. Explore Advanced Features: Lifecycle events, systems, compute()

Your Redux knowledge transfers directly — Inglorious is just cleaner!

Released under the MIT License.