Проблема заключается в том, что асинхронная функция img.onload выполняется после того, как функция-редюсер закончила свою работу, и к моменту выполнения img.onload объект state.canvas уже был обновлен и утрачен свою возможность использования. Это приводит к ошибке, которую вы видите.
Для решения этой проблемы можно воспользоваться библиотекой Redux-Thunk, которая позволяет создавать асинхронные функции внутри функций-редюсеров. В вашем случае, вы можете заменить pushToUndo и pushToRedo на асинхронные функции, которые будут использовать dispatch для вызова функций-редюсеров после завершения асинхронной операции.
Пример использования Redux-Thunk для вашего случая:
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
export const loadImage = createAsyncThunk(
'canvas/loadImage',
async (dataURL, { getState }) => {
const state = getState().canvas;
const img = new Image();
img.src = dataURL;
return new Promise((resolve, reject) => {
img.onload = () => {
const ctx = state.canvas.getContext('2d');
ctx.clearRect(0, 0, state.canvas.width, state.canvas.height);
ctx.drawImage(img, 0, 0, state.canvas.width, state.canvas.height);
resolve();
};
img.onerror = reject;
});
}
);
export const canvasSlice = createSlice({
name: 'canvas',
initialState: {
canvas: null,
undoList: [],
redoList: [],
},
reducers: {
setCanvas: (state, action) => {
state.canvas = action.payload;
},
pushToUndo: (state, action) => {
state.undoList.push(action.payload);
},
pushToRedo: (state, action) => {
state.redoList.push(action.payload);
},
undo: (state) => {
if (state.undoList.length) {
const dataURL = state.undoList.pop();
state.redoList.push(dataURL);
dispatch(loadImage(dataURL));
} else {
console.log('Нет элементов для отката');
}
},
redo: (state) => {
if (state.redoList.length) {
const dataURL = state.redoList.pop();
state.undoList.push(state.canvas.toDataURL());
dispatch(loadImage(dataURL));
}
},
},
extraReducers: (builder) => {
builder.addCase(loadImage.pending, (state) => {
// обработка начала загрузки изображения
});
builder.addCase(loadImage.fulfilled, (state) => {
// обработка завершения загрузки изображения
});
builder.addCase(loadImage.rejected, (state) => {
// обработка ошибки загрузки изображения
});
},
});
export const { setCanvas, pushToUndo, pushToRedo, undo, redo } = canvasSlice.actions;
export default canvasSlice.reducer