Project Kanban

Optimistic Updates

Оптимістичне оновлення — це стратегія, коли ми оновлюємо інтерфейс так, ніби запит на сервер вже пройшов успішно. Це створює відчуття миттєвої реакції.

Optimistic Updates

Оптимістичне оновлення — це стратегія, коли ми оновлюємо інтерфейс так, ніби запит на сервер вже пройшов успішно. Це створює відчуття миттєвої реакції.

Якщо сервер поверне помилку, ми повинні "відкотити" (rollback) зміни назад.

Реалізація в RTK Query

RTK Query має спеціальний колбек onQueryStarted для мутацій, який дозволяє маніпулювати кешем.

src/features/api/apiSlice.ts
updateBoard: builder.mutation<void, BoardData>({
  query: (board) => ({
    url: 'board',
    method: 'POST',
    body: board,
  }),
  async onQueryStarted(board, { dispatch, queryFulfilled }) {
    // 1. Оптимістичне оновлення кешу 'getBoard'
    const patchResult = dispatch(
      apiSlice.util.updateQueryData('getBoard', undefined, (draft) => {
        // draft - це поточний стан кешу. Ми замінюємо його на нові дані (board).
        // Оскільки це Immer, ми можемо просто присвоїти або мутувати.
        Object.assign(draft, board);
      })
    );

    try {
      // 2. Чекаємо відповіді сервера
      await queryFulfilled;
    } catch {
      // 3. Якщо помилка — відкочуємо зміни (undo)
      patchResult.undo();
      alert('Не вдалося зберегти зміни! Синхронізація не вдалася.');
    }
  },
}),

Як це працює в нашому випадку?

У нас гібридна система:

  1. Локальний boardSlice оновлюється миттєво через діспатч moveTask.
  2. Ми викликаємо мутацію updateBoard(newState).
  3. Завдяки onQueryStarted, ми також оновлюємо кеш RTK Query.

Але чекайте... Якщо ми вже оновили boardSlice локально, навіщо нам оновлювати кеш RTK Query?

У нашому випадку, оскільки boardSlice є "джерелом правди" для UI, нам не обов'язково робити оптимістичне оновлення кешу RTK Query для відображення змін (бо ми і так їх бачимо з локального слайсу).

АЛЕ, це корисно для узгодженості даних. Якщо інші компоненти підписані на useGetBoardQuery, вони теж миттєво отримають оновлення.

Сценарій відкату (Rollback)

Якщо сервер впав:

  1. queryFulfilled кидає помилку.
  2. patchResult.undo() відкочує кеш RTK Query.
  3. Проблема: Наш локальний boardSlice все ще має "невірні" дані (переміщену картку).

Нам потрібно синхронізувати локальний слайс назад з кешем.

Ми можемо додати extraReducers в boardSlice, щоб слухати apiSlice.endpoints.updateBoard.matchRejected.

src/features/board/boardSlice.ts
// ...
builder.addMatcher(
  apiSlice.endpoints.updateBoard.matchRejected,
  (state, action) => {
    // Якщо мутація впала — можливо, варто перезапитати дані з сервера
    // або якось повідомити користувача.
    // Найпростіший варіант відкату: ми не знаємо попередній стан тут легко.
    
    // Стратегія: Позначити стан як "out of sync" і форсувати refetch.
  }
);

Складність синхронізації клієнтського і серверного стану — це вічна проблема. Саме тому підхід "Тільки RTK Query (без локального слайсу)" часто простіший, але він не дозволяє синхронних оновлень для drag-and-drop.

У більшості випадків для DnD ми просто ігноруємо помилки або показуємо "Toast" повідомлення, і при наступному завантаженні сторінки дані синхронізуються.

Підсумок проекту

Ми створили потужну архітектуру:

  • BoardSlice: для миттєвого UI (60 FPS).
  • RTK Query: для фонової синхронізації.
  • Normalization: для зручної роботи з даними.
  • Typescript: для безпеки.

Це база, на якій будуються такі гіганти як Trello, Jira чи Asana.

👉 Повернутися до головного меню

Copyright © 2026