Migrate woo data options to TS
This commit is contained in:
parent
78f96109da
commit
5f7d0cd0e1
|
@ -1,9 +0,0 @@
|
||||||
const TYPES = {
|
|
||||||
RECEIVE_OPTIONS: 'RECEIVE_OPTIONS',
|
|
||||||
SET_IS_REQUESTING: 'SET_IS_REQUESTING',
|
|
||||||
SET_IS_UPDATING: 'SET_IS_UPDATING',
|
|
||||||
SET_REQUESTING_ERROR: 'SET_REQUESTING_ERROR',
|
|
||||||
SET_UPDATING_ERROR: 'SET_UPDATING_ERROR',
|
|
||||||
};
|
|
||||||
|
|
||||||
export default TYPES;
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
const TYPES = {
|
||||||
|
RECEIVE_OPTIONS: 'RECEIVE_OPTIONS' as const,
|
||||||
|
SET_IS_REQUESTING: 'SET_IS_REQUESTING' as const,
|
||||||
|
SET_IS_UPDATING: 'SET_IS_UPDATING' as const,
|
||||||
|
SET_REQUESTING_ERROR: 'SET_REQUESTING_ERROR' as const,
|
||||||
|
SET_UPDATING_ERROR: 'SET_UPDATING_ERROR' as const,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TYPES;
|
|
@ -8,15 +8,16 @@ import { apiFetch } from '@wordpress/data-controls';
|
||||||
*/
|
*/
|
||||||
import TYPES from './action-types';
|
import TYPES from './action-types';
|
||||||
import { WC_ADMIN_NAMESPACE } from '../constants';
|
import { WC_ADMIN_NAMESPACE } from '../constants';
|
||||||
|
import { Options } from './types';
|
||||||
|
|
||||||
export function receiveOptions( options ) {
|
export function receiveOptions( options: Options ) {
|
||||||
return {
|
return {
|
||||||
type: TYPES.RECEIVE_OPTIONS,
|
type: TYPES.RECEIVE_OPTIONS,
|
||||||
options,
|
options,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setRequestingError( error, name ) {
|
export function setRequestingError( error: unknown, name: string ) {
|
||||||
return {
|
return {
|
||||||
type: TYPES.SET_REQUESTING_ERROR,
|
type: TYPES.SET_REQUESTING_ERROR,
|
||||||
error,
|
error,
|
||||||
|
@ -24,35 +25,51 @@ export function setRequestingError( error, name ) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setUpdatingError( error ) {
|
export function setUpdatingError( error: unknown ) {
|
||||||
return {
|
return {
|
||||||
type: TYPES.SET_UPDATING_ERROR,
|
type: TYPES.SET_UPDATING_ERROR,
|
||||||
error,
|
error,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setIsUpdating( isUpdating ) {
|
export function setIsUpdating( isUpdating: boolean ) {
|
||||||
return {
|
return {
|
||||||
type: TYPES.SET_IS_UPDATING,
|
type: TYPES.SET_IS_UPDATING,
|
||||||
isUpdating,
|
isUpdating,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function* updateOptions( data ) {
|
export function* updateOptions( data: Options ) {
|
||||||
yield setIsUpdating( true );
|
yield setIsUpdating( true );
|
||||||
yield receiveOptions( data );
|
yield receiveOptions( data );
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const results = yield apiFetch( {
|
const results: unknown = yield apiFetch( {
|
||||||
path: WC_ADMIN_NAMESPACE + '/options',
|
path: WC_ADMIN_NAMESPACE + '/options',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
data,
|
data,
|
||||||
} );
|
} );
|
||||||
|
|
||||||
yield setIsUpdating( false );
|
yield setIsUpdating( false );
|
||||||
|
|
||||||
|
if ( typeof results !== 'object' ) {
|
||||||
|
throw new Error(
|
||||||
|
`Invalid update options response from server: ${ results }`
|
||||||
|
);
|
||||||
|
}
|
||||||
return { success: true, ...results };
|
return { success: true, ...results };
|
||||||
} catch ( error ) {
|
} catch ( error ) {
|
||||||
yield setUpdatingError( error );
|
yield setUpdatingError( error );
|
||||||
|
if ( typeof error !== 'object' ) {
|
||||||
|
throw new Error( `Unexpected error: ${ error }` );
|
||||||
|
}
|
||||||
return { success: false, ...error };
|
return { success: false, ...error };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type Action = ReturnType<
|
||||||
|
| typeof receiveOptions
|
||||||
|
| typeof setRequestingError
|
||||||
|
| typeof setUpdatingError
|
||||||
|
| typeof setIsUpdating
|
||||||
|
>;
|
|
@ -1 +1 @@
|
||||||
export const STORE_NAME = 'wc/admin/options';
|
export const STORE_NAME = 'wc/admin/options' as const;
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
* External dependencies
|
* External dependencies
|
||||||
*/
|
*/
|
||||||
import { controls as dataControls } from '@wordpress/data-controls';
|
import { controls as dataControls } from '@wordpress/data-controls';
|
||||||
|
import { Action } from '@wordpress/data';
|
||||||
import apiFetch from '@wordpress/api-fetch';
|
import apiFetch from '@wordpress/api-fetch';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -9,10 +10,12 @@ import apiFetch from '@wordpress/api-fetch';
|
||||||
*/
|
*/
|
||||||
import { WC_ADMIN_NAMESPACE } from '../constants';
|
import { WC_ADMIN_NAMESPACE } from '../constants';
|
||||||
|
|
||||||
let optionNames = [];
|
let optionNames: string[] = [];
|
||||||
const fetches = {};
|
const fetches: {
|
||||||
|
[ key: string ]: Promise< unknown >;
|
||||||
|
} = {};
|
||||||
|
|
||||||
export const batchFetch = ( optionName ) => {
|
export const batchFetch = ( optionName: string ) => {
|
||||||
return {
|
return {
|
||||||
type: 'BATCH_FETCH',
|
type: 'BATCH_FETCH',
|
||||||
optionName,
|
optionName,
|
||||||
|
@ -21,12 +24,15 @@ export const batchFetch = ( optionName ) => {
|
||||||
|
|
||||||
export const controls = {
|
export const controls = {
|
||||||
...dataControls,
|
...dataControls,
|
||||||
BATCH_FETCH( { optionName } ) {
|
BATCH_FETCH( { optionName }: Action ) {
|
||||||
optionNames.push( optionName );
|
optionNames.push( optionName );
|
||||||
|
|
||||||
return new Promise( ( resolve ) => {
|
return new Promise( ( resolve ) => {
|
||||||
setTimeout( function () {
|
setTimeout( function () {
|
||||||
if ( fetches[ optionName ] ) {
|
if (
|
||||||
|
fetches.hasOwnProperty( optionName ) &&
|
||||||
|
fetches[ optionName ]
|
||||||
|
) {
|
||||||
return fetches[ optionName ].then( ( result ) => {
|
return fetches[ optionName ].then( ( result ) => {
|
||||||
resolve( result );
|
resolve( result );
|
||||||
} );
|
} );
|
|
@ -1,25 +0,0 @@
|
||||||
/**
|
|
||||||
* External dependencies
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { registerStore } from '@wordpress/data';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal dependencies
|
|
||||||
*/
|
|
||||||
import { STORE_NAME } from './constants';
|
|
||||||
import * as selectors from './selectors';
|
|
||||||
import * as actions from './actions';
|
|
||||||
import * as resolvers from './resolvers';
|
|
||||||
import { controls } from './controls';
|
|
||||||
import reducer from './reducer';
|
|
||||||
|
|
||||||
registerStore( STORE_NAME, {
|
|
||||||
reducer,
|
|
||||||
actions,
|
|
||||||
controls,
|
|
||||||
selectors,
|
|
||||||
resolvers,
|
|
||||||
} );
|
|
||||||
|
|
||||||
export const OPTIONS_STORE_NAME = STORE_NAME;
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
/**
|
||||||
|
* External dependencies
|
||||||
|
*/
|
||||||
|
import { registerStore } from '@wordpress/data';
|
||||||
|
import { SelectFromMap, DispatchFromMap } from '@automattic/data-stores';
|
||||||
|
import { Reducer, AnyAction } from 'redux';
|
||||||
|
/**
|
||||||
|
* Internal dependencies
|
||||||
|
*/
|
||||||
|
import { STORE_NAME } from './constants';
|
||||||
|
import * as selectors from './selectors';
|
||||||
|
import * as actions from './actions';
|
||||||
|
import * as resolvers from './resolvers';
|
||||||
|
import reducer, { State } from './reducer';
|
||||||
|
import { controls } from './controls';
|
||||||
|
import { WPDataSelectors } from '../types';
|
||||||
|
export * from './types';
|
||||||
|
export type { State };
|
||||||
|
|
||||||
|
registerStore< State >( STORE_NAME, {
|
||||||
|
reducer: reducer as Reducer< State, AnyAction >,
|
||||||
|
actions,
|
||||||
|
controls,
|
||||||
|
selectors,
|
||||||
|
resolvers,
|
||||||
|
} );
|
||||||
|
|
||||||
|
export const OPTIONS_STORE_NAME = STORE_NAME;
|
||||||
|
|
||||||
|
declare module '@wordpress/data' {
|
||||||
|
function dispatch(
|
||||||
|
key: typeof STORE_NAME
|
||||||
|
): DispatchFromMap< typeof actions >;
|
||||||
|
function select(
|
||||||
|
key: typeof STORE_NAME
|
||||||
|
): SelectFromMap< typeof selectors > & WPDataSelectors;
|
||||||
|
}
|
|
@ -1,38 +1,46 @@
|
||||||
|
/**
|
||||||
|
* External dependencies
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { Reducer } from 'redux';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
import TYPES from './action-types';
|
import TYPES from './action-types';
|
||||||
|
import { Action } from './actions';
|
||||||
|
import { OptionsState } from './types';
|
||||||
|
|
||||||
const optionsReducer = (
|
const optionsReducer: Reducer< OptionsState, Action > = (
|
||||||
state = { isUpdating: false, requestingErrors: {} },
|
state = { isUpdating: false, requestingErrors: {} },
|
||||||
{ type, options, error, isUpdating, name }
|
action
|
||||||
) => {
|
) => {
|
||||||
switch ( type ) {
|
switch ( action.type ) {
|
||||||
case TYPES.RECEIVE_OPTIONS:
|
case TYPES.RECEIVE_OPTIONS:
|
||||||
state = {
|
state = {
|
||||||
...state,
|
...state,
|
||||||
...options,
|
...action.options,
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
case TYPES.SET_IS_UPDATING:
|
case TYPES.SET_IS_UPDATING:
|
||||||
state = {
|
state = {
|
||||||
...state,
|
...state,
|
||||||
isUpdating,
|
isUpdating: action.isUpdating,
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
case TYPES.SET_REQUESTING_ERROR:
|
case TYPES.SET_REQUESTING_ERROR:
|
||||||
state = {
|
state = {
|
||||||
...state,
|
...state,
|
||||||
requestingErrors: {
|
requestingErrors: {
|
||||||
[ name ]: error,
|
[ action.name ]: action.error,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
case TYPES.SET_UPDATING_ERROR:
|
case TYPES.SET_UPDATING_ERROR:
|
||||||
state = {
|
state = {
|
||||||
...state,
|
...state,
|
||||||
error,
|
error: action.error,
|
||||||
updatingError: error,
|
updatingError: action.error,
|
||||||
isUpdating: false,
|
isUpdating: false,
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
|
@ -40,4 +48,5 @@ const optionsReducer = (
|
||||||
return state;
|
return state;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type State = ReturnType< typeof optionsReducer >;
|
||||||
export default optionsReducer;
|
export default optionsReducer;
|
|
@ -3,15 +3,16 @@
|
||||||
*/
|
*/
|
||||||
import { receiveOptions, setRequestingError } from './actions';
|
import { receiveOptions, setRequestingError } from './actions';
|
||||||
import { batchFetch } from './controls';
|
import { batchFetch } from './controls';
|
||||||
|
import { Options } from './types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request an option value.
|
* Request an option value.
|
||||||
*
|
*
|
||||||
* @param {string} name - Option name
|
* @param {string} name - Option name
|
||||||
*/
|
*/
|
||||||
export function* getOption( name ) {
|
export function* getOption( name: string ) {
|
||||||
try {
|
try {
|
||||||
const result = yield batchFetch( name );
|
const result: Options = yield batchFetch( name );
|
||||||
yield receiveOptions( result );
|
yield receiveOptions( result );
|
||||||
} catch ( error ) {
|
} catch ( error ) {
|
||||||
yield setRequestingError( error, name );
|
yield setRequestingError( error, name );
|
|
@ -1,10 +1,15 @@
|
||||||
|
/**
|
||||||
|
* Internal dependencies
|
||||||
|
*/
|
||||||
|
import { OptionsState } from './types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get option from state tree.
|
* Get option from state tree.
|
||||||
*
|
*
|
||||||
* @param {Object} state - Reducer state
|
* @param {Object} state - Reducer state
|
||||||
* @param {Array} name - Option name
|
* @param {Array} name - Option name
|
||||||
*/
|
*/
|
||||||
export const getOption = ( state, name ) => {
|
export const getOption = ( state: OptionsState, name: string ) => {
|
||||||
return state[ name ];
|
return state[ name ];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -14,7 +19,10 @@ export const getOption = ( state, name ) => {
|
||||||
* @param {Object} state - Reducer state
|
* @param {Object} state - Reducer state
|
||||||
* @param {string} name - Option name
|
* @param {string} name - Option name
|
||||||
*/
|
*/
|
||||||
export const getOptionsRequestingError = ( state, name ) => {
|
export const getOptionsRequestingError = (
|
||||||
|
state: OptionsState,
|
||||||
|
name: string
|
||||||
|
) => {
|
||||||
return state.requestingErrors[ name ] || false;
|
return state.requestingErrors[ name ] || false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -23,7 +31,7 @@ export const getOptionsRequestingError = ( state, name ) => {
|
||||||
*
|
*
|
||||||
* @param {Object} state - Reducer state
|
* @param {Object} state - Reducer state
|
||||||
*/
|
*/
|
||||||
export const isOptionsUpdating = ( state ) => {
|
export const isOptionsUpdating = ( state: OptionsState ) => {
|
||||||
return state.isUpdating || false;
|
return state.isUpdating || false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -32,6 +40,6 @@ export const isOptionsUpdating = ( state ) => {
|
||||||
*
|
*
|
||||||
* @param {Object} state - Reducer state
|
* @param {Object} state - Reducer state
|
||||||
*/
|
*/
|
||||||
export const getOptionsUpdatingError = ( state ) => {
|
export const getOptionsUpdatingError = ( state: OptionsState ) => {
|
||||||
return state.updatingError || false;
|
return state.updatingError || false;
|
||||||
};
|
};
|
|
@ -12,6 +12,7 @@ const defaultState = { isUpdating: false, requestingErrors: {} };
|
||||||
|
|
||||||
describe( 'options reducer', () => {
|
describe( 'options reducer', () => {
|
||||||
it( 'should return a default state', () => {
|
it( 'should return a default state', () => {
|
||||||
|
// @ts-expect-error reducer action should not be empty but it is
|
||||||
const state = reducer( undefined, {} );
|
const state = reducer( undefined, {} );
|
||||||
expect( state ).toEqual( defaultState );
|
expect( state ).toEqual( defaultState );
|
||||||
expect( state ).not.toBe( defaultState );
|
expect( state ).not.toBe( defaultState );
|
|
@ -39,7 +39,7 @@ describe( 'withOptionsHydration', () => {
|
||||||
const startResolutionMock = jest.fn();
|
const startResolutionMock = jest.fn();
|
||||||
const receiveOptionsMock = jest.fn();
|
const receiveOptionsMock = jest.fn();
|
||||||
beforeEach( () => {
|
beforeEach( () => {
|
||||||
useSelect.mockImplementation( ( callback ) => {
|
( useSelect as jest.Mock ).mockImplementation( ( callback ) => {
|
||||||
callback(
|
callback(
|
||||||
() => ( {
|
() => ( {
|
||||||
isResolving: isResolvingMock,
|
isResolving: isResolvingMock,
|
|
@ -17,3 +17,18 @@ export type OptionsSelectors = {
|
||||||
isOptionsUpdating: WPDataSelector< typeof isOptionsUpdating >;
|
isOptionsUpdating: WPDataSelector< typeof isOptionsUpdating >;
|
||||||
getOptionsUpdatingError: WPDataSelector< typeof getOptionsUpdatingError >;
|
getOptionsUpdatingError: WPDataSelector< typeof getOptionsUpdatingError >;
|
||||||
} & WPDataSelectors;
|
} & WPDataSelectors;
|
||||||
|
|
||||||
|
export type Options = {
|
||||||
|
[ key: string ]: unknown;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type OptionsState = {
|
||||||
|
isUpdating: boolean;
|
||||||
|
requestingErrors:
|
||||||
|
| {
|
||||||
|
[ name: string ]: unknown;
|
||||||
|
}
|
||||||
|
| Record< string, never >;
|
||||||
|
error?: unknown;
|
||||||
|
updatingError?: unknown;
|
||||||
|
} & Options;
|
||||||
|
|
|
@ -2,18 +2,20 @@
|
||||||
* External dependencies
|
* External dependencies
|
||||||
*/
|
*/
|
||||||
import { createHigherOrderComponent } from '@wordpress/compose';
|
import { createHigherOrderComponent } from '@wordpress/compose';
|
||||||
import { useSelect } from '@wordpress/data';
|
import { useSelect, select as WPSelect } from '@wordpress/data';
|
||||||
import { createElement, useRef } from '@wordpress/element';
|
import { createElement, useRef } from '@wordpress/element';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
import { STORE_NAME } from './constants';
|
import { STORE_NAME } from './constants';
|
||||||
|
import { Options } from './types';
|
||||||
|
|
||||||
export const useOptionsHydration = ( data ) => {
|
export const useOptionsHydration = ( data: Options ) => {
|
||||||
const dataRef = useRef( data );
|
const dataRef = useRef( data );
|
||||||
|
|
||||||
useSelect( ( select, registry ) => {
|
// @ts-expect-error registry is not defined in the wp.data typings
|
||||||
|
useSelect( ( select: typeof WPSelect, registry ) => {
|
||||||
if ( ! dataRef.current ) {
|
if ( ! dataRef.current ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -39,8 +41,8 @@ export const useOptionsHydration = ( data ) => {
|
||||||
}, [] );
|
}, [] );
|
||||||
};
|
};
|
||||||
|
|
||||||
export const withOptionsHydration = ( data ) =>
|
export const withOptionsHydration = ( data: Options ) =>
|
||||||
createHigherOrderComponent(
|
createHigherOrderComponent< Record< string, unknown > >(
|
||||||
( OriginalComponent ) => ( props ) => {
|
( OriginalComponent ) => ( props ) => {
|
||||||
useOptionsHydration( data );
|
useOptionsHydration( data );
|
||||||
|
|
Loading…
Reference in New Issue