♻️ Todos store: Refactor to use thunks

This commit is contained in:
Philipp Stracker 2025-02-06 14:40:38 +01:00
parent 1ecb5ae610
commit 95fef43e77
No known key found for this signature in database
6 changed files with 102 additions and 144 deletions

View file

@ -6,16 +6,11 @@
export default {
// Transient data
SET_TRANSIENT: 'TODOS:SET_TRANSIENT',
SET_COMPLETED_TODOS: 'TODOS:SET_COMPLETED_TODOS',
SET_TRANSIENT: 'ppcp/todos/SET_TRANSIENT',
SET_COMPLETED_TODOS: 'ppcp/todos/SET_COMPLETED_TODOS',
// Persistent data
SET_TODOS: 'TODOS:SET_TODOS',
SET_DISMISSED_TODOS: 'TODOS:SET_DISMISSED_TODOS',
// Controls
DO_FETCH_TODOS: 'TODOS:DO_FETCH_TODOS',
DO_PERSIST_DATA: 'TODOS:DO_PERSIST_DATA',
DO_RESET_DISMISSED_TODOS: 'TODOS:DO_RESET_DISMISSED_TODOS',
DO_COMPLETE_ONCLICK: 'TODOS:DO_COMPLETE_ONCLICK',
SET_TODOS: 'ppcp/todos/SET_TODOS',
SET_DISMISSED_TODOS: 'ppcp/todos/SET_DISMISSED_TODOS',
RESET_DISMISSED_TODOS: 'ppcp/todos/RESET_DISMISSED_TODOS',
};

View file

@ -7,9 +7,15 @@
* @file
*/
import { select } from '@wordpress/data';
import apiFetch from '@wordpress/api-fetch';
import ACTION_TYPES from './action-types';
import { STORE_NAME } from './constants';
import {
REST_COMPLETE_ONCLICK_PATH,
REST_PATH,
REST_PERSIST_PATH,
REST_RESET_DISMISSED_TODOS_PATH,
} from './constants';
export const setIsReady = ( isReady ) => ( {
type: ACTION_TYPES.SET_TRANSIENT,
@ -26,42 +32,76 @@ export const setDismissedTodos = ( dismissedTodos ) => ( {
payload: dismissedTodos,
} );
export const fetchTodos = function* () {
yield { type: ACTION_TYPES.DO_FETCH_TODOS };
};
export const persist = function* () {
const data = yield select( STORE_NAME ).persistentData();
yield { type: ACTION_TYPES.DO_PERSIST_DATA, data };
};
export const resetDismissedTodos = function* () {
const result = yield { type: ACTION_TYPES.DO_RESET_DISMISSED_TODOS };
if ( result && result.success ) {
yield setDismissedTodos( [] );
}
return result;
};
export const setCompletedTodos = ( completedTodos ) => ( {
type: ACTION_TYPES.SET_COMPLETED_TODOS,
payload: completedTodos,
} );
export const completeOnClick = function* ( todoId ) {
const result = yield {
type: ACTION_TYPES.DO_COMPLETE_ONCLICK,
todoId,
// Thunks
export function fetchTodos() {
return async () => {
const response = await apiFetch( { path: REST_PATH } );
return response?.data || [];
};
}
if ( result && result.success ) {
// Set transient completed state for visual feedback
const currentTransientCompleted =
yield select( STORE_NAME ).getCompletedTodos();
yield setCompletedTodos( [ ...currentTransientCompleted, todoId ] );
}
export function persist() {
return async ( { select } ) => {
const data = await select.persistentData();
return await apiFetch( {
path: REST_PERSIST_PATH,
method: 'POST',
data,
} );
};
}
return result;
};
export function resetDismissedTodos() {
return async ( { dispatch } ) => {
try {
const result = await apiFetch( {
path: REST_RESET_DISMISSED_TODOS_PATH,
method: 'POST',
} );
if ( result && result.success ) {
await dispatch.setDismissedTodos( [] );
}
return result;
} catch ( e ) {
return {
success: false,
error: e,
message: e.message,
};
}
};
}
export function completeOnClick( todoId ) {
return async ( { select, dispatch } ) => {
try {
const result = await apiFetch( {
path: REST_COMPLETE_ONCLICK_PATH,
method: 'POST',
data: { todoId },
} );
if ( result?.success ) {
// Set transient completed state for visual feedback
const completed = await select.getCompletedTodos();
await dispatch.setCompletedTodos( [ ...completed, todoId ] );
}
return result;
} catch ( e ) {
return {
success: false,
error: e,
message: e.message,
};
}
};
}

View file

@ -1,65 +0,0 @@
/**
* Controls: Implement side effects, typically asynchronous operations.
*
* Controls use ACTION_TYPES keys as identifiers.
* They are triggered by corresponding actions and handle external interactions.
*
* @file
*/
import apiFetch from '@wordpress/api-fetch';
import {
REST_PATH,
REST_PERSIST_PATH,
REST_RESET_DISMISSED_TODOS_PATH,
REST_COMPLETE_ONCLICK_PATH,
} from './constants';
import ACTION_TYPES from './action-types';
export const controls = {
async [ ACTION_TYPES.DO_FETCH_TODOS ]() {
const response = await apiFetch( {
path: REST_PATH,
method: 'GET',
} );
return response?.data || [];
},
async [ ACTION_TYPES.DO_PERSIST_DATA ]( { data } ) {
return await apiFetch( {
path: REST_PERSIST_PATH,
method: 'POST',
data,
} );
},
async [ ACTION_TYPES.DO_RESET_DISMISSED_TODOS ]() {
try {
return await apiFetch( {
path: REST_RESET_DISMISSED_TODOS_PATH,
method: 'POST',
} );
} catch ( e ) {
return {
success: false,
error: e,
message: e.message,
};
}
},
async [ ACTION_TYPES.DO_COMPLETE_ONCLICK ]( { todoId } ) {
try {
const response = await apiFetch( {
path: REST_COMPLETE_ONCLICK_PATH,
method: 'POST',
data: { todoId },
} );
return response;
} catch ( e ) {
return {
success: false,
error: e,
message: e.message,
};
}
},
};

View file

@ -1,13 +1,11 @@
import { createReduxStore, register } from '@wordpress/data';
import { controls as wpControls } from '@wordpress/data-controls';
import { STORE_NAME } from './constants';
import reducer from './reducer';
import * as selectors from './selectors';
import * as actions from './actions';
import * as hooks from './hooks';
import { resolvers } from './resolvers';
import { controls } from './controls';
import * as resolvers from './resolvers';
/**
* Initializes and registers the todos store with WordPress data layer.
@ -18,7 +16,6 @@ import { controls } from './controls';
export const initStore = () => {
const store = createReduxStore( STORE_NAME, {
reducer,
controls: { ...wpControls, ...controls },
actions,
selectors,
resolvers,

View file

@ -94,25 +94,10 @@ const reducer = createReducer( defaultTransient, defaultPersistent, {
* @param {Object} state Current state
* @return {Object} Updated state
*/
[ ACTION_TYPES.DO_RESET_DISMISSED_TODOS ]: ( state ) => {
[ ACTION_TYPES.RESET_DISMISSED_TODOS ]: ( state ) => {
return changePersistent( state, { dismissedTodos: [] } );
},
/**
* Resets state to defaults while maintaining initialization status
*
* @param {Object} state Current state
* @return {Object} Reset state
*/
[ ACTION_TYPES.RESET ]: ( state ) => {
const cleanState = changeTransient(
changePersistent( state, defaultPersistent ),
defaultTransient
);
cleanState.isReady = true; // Keep initialization flag
return cleanState;
},
/**
* Initializes persistent state with data from the server
*

View file

@ -8,27 +8,33 @@
* @file
*/
import { dispatch } from '@wordpress/data';
import { __ } from '@wordpress/i18n';
import { apiFetch } from '@wordpress/data-controls';
import { STORE_NAME, REST_PATH } from './constants';
import apiFetch from '@wordpress/api-fetch';
export const resolvers = {
*getTodos() {
import { REST_PATH } from './constants';
/**
* Retrieve settings from the site's REST API.
*/
export function getTodos() {
return async ( { dispatch, registry } ) => {
try {
const response = yield apiFetch( { path: REST_PATH } );
const response = await apiFetch( { path: REST_PATH } );
const { todos = [], dismissedTodos = [] } = response?.data || {};
yield dispatch( STORE_NAME ).setTodos( todos );
yield dispatch( STORE_NAME ).setDismissedTodos( dismissedTodos );
yield dispatch( STORE_NAME ).setIsReady( true );
await dispatch.setTodos( todos );
await dispatch.setDismissedTodos( dismissedTodos );
await dispatch.setIsReady( true );
} catch ( e ) {
console.error( 'Resolver error:', e );
yield dispatch( STORE_NAME ).setIsReady( false );
yield dispatch( 'core/notices' ).createErrorNotice(
__( 'Error retrieving todos.', 'woocommerce-paypal-payments' )
);
await registry
.dispatch( 'core/notices' )
.createErrorNotice(
__(
'Error retrieving todos.',
'woocommerce-paypal-payments'
)
);
}
},
};
};
}