0

given my custom store, when I try to use readItems selector, I am always getting default store state. Selector works fine (tested in console with wp.data.select), however, it takes some time to catch values asynchronously I presume useSelect memoizes first returned value which is default store state and does not respond to store changes.

How to fix this?

Beginning of block edit function:

const LECTURERS_TABLE = 'tla_mit_lecturers';


export default function Edit(props) {
    const { attributes, setAttributes } = props;

    const allItems = useSelect(select => select('REST-extra_table').readItems(LECTURERS_TABLE)); 

Store:

import apiFetch from '@wordpress/api-fetch';
import { createReduxStore, register } from '@wordpress/data';

/**
 * returns table id from table name
 * @param  {} table
 */
function getTableId(table) {
    if (! table) {
        console.log('Table name not supplied for API');
        return undefined;
    }
    const tableId = tableIds.indexOf(table);
    if (tableId == -1) {
        console.log('Wrong table name supplied to the API');
        return undefined;
    }
    return tableId;
}

// items is array of tables - item['table_name'][index] is individual item
const tableIds = [
    'tla_mit_lecturers',
]

const DEFAULT_STATE = {
    tables: [
        ['default']
    ],
};

const actions = {
    *createItem( item, table ) {
        const tableId = getTableId(table);
        const retValue = yield actions.fetchFromAPI(
            {
                path: '/extra_table/v1/item?table=' + table,
                method: 'POST',
                data: item
            }
         );
        // retValue should be ID if insert is successful
        return retValue ?
            {
                type: 'CREATE_ITEM',
                item: {ID: retValue, ...item},
                tableId
            } :
            {
                type: 'FETCH_ERROR',
                error: 'unknown'
            };
    },

    *deleteItem( ID, table ) {
        const tableId = getTableId(table);
        const retValue = yield actions.fetchFromAPI(
            {
                path: '/extra_table/v1/item?table=' + table + '&ID=' + ID,
                method: 'DELETE'
            }
         );
        return retValue ?
            {
                type: 'DELETE_ITEM',
                tableId,
                ID
            } :
            {
                type: 'FETCH_ERROR',
                error: 'unknown'
            };
    },

    *updateItem( ID, item, table ) {
        const tableId = getTableId(table);
        const retValue = yield actions.fetchFromAPI(
            {
                path: '/extra_table/v1/item?table=' + table + '&ID=' + ID,
                method: 'PUT',
                data: item
            }
         );
        return retValue ?
            {
                type: 'UPDATE_ITEM',
                item: {ID, ...item},
                ID,
                tableId
            } :
            {
                type: 'FETCH_ERROR',
                error: 'unknown'
            };
    },

    // action to set item values - hydrate in https://unfoldingneurons.com/2020/wordpress-data-store-properties-resolvers
    setItem( item, tableId ) {
        return {
            type: 'SET_ITEM',
            item,
            tableId
        };
    },

    // action to set item values - hydrate in https://unfoldingneurons.com/2020/wordpress-data-store-properties-resolvers
    setAllItems( items, tableId ) {
        return {
            type: 'SET_ITEMS',
            items,
            tableId
        };
    },

    fetchFromAPI( action ) {
        return {
            type: 'FETCH_FROM_API',
            path: action.path,
                method: action.method,
                data: action.data,
                tableId: action.tableId
        };
    }
};

const store = createReduxStore( 'REST-extra_table', {
    reducer( state = DEFAULT_STATE, action ) {
        switch ( action.type ) {
            case 'CREATE_ITEM':
                return {
                    ...state,
                    tables: Object.assign([...state.tables], {[action.tableId]: [...state.tables[action.tableId], action.item] })
                };
            case 'UPDATE_ITEM':
                return {
                    ...state,
                    tables: Object.assign(
                            [...state.tables],
                            {[action.tableId]:
                                Object.assign(
                                    state.tables[action.tableId],
                                    {[state.tables[action.tableId].findIndex(el => el.ID === action.ID)]: action.item}
                                )
                    })
                };
            case 'DELETE_ITEM':
                return {
                    ...state,
                    tables: Object.assign([...state.tables], {[action.tableId]: state.tables[action.tableId].filter((el) => el.ID !== action.ID) })
                };
            // hydrate
            case 'SET_ITEM':
                return {
                    ...state,
                    tables: Object.assign([...state.tables], {[action.tableId]: [...state.tables[action.tableId], action.item] })
                }
            case 'SET_ITEMS':
                return {
                    ...state,
                    tables: Object.assign([...state.tables], {[action.tableId]: [...action.items] })
                }
            case 'FETCH_ERROR':
                console.log('AJAX fetch error, code: ' + action.error);
                return state;

        }
                // console.error('Table name is not given!!! '+action.type)

        return state;
    },

    actions,

    selectors: {
        readItem( state, ID, table ) {
            const tableId = getTableId(table);
            if (tableId === undefined)
                return undefined;
            if (state.tables[tableId])
                return state.tables[tableId].find(el => el.ID == ID);
            else
                return undefined;
        },
        readItems( state, table ) {
            const tableId = getTableId(table);
            if (tableId === undefined)
                return undefined;
            // return empty array if state.tables does not exist
            if (! state.tables[tableId])
                console.error('Requested table does not exist in state!');
            return state.tables[tableId] || undefined;
        },
    },

    controls: {
        FETCH_FROM_API( action ) {
            return apiFetch(
                {
                    path: action.path,
                    // path: action.path,
                    method: action.method,
                    credentials: 'same-origin',
                    mode:'same-origin',
                    // headers: {
                    //  'Content-Type': 'application/json'
                    //  },
                    data: action.data
                } );
        },
    },

    resolvers: {
        *readItem( ID, table ) {
            const tableId = getTableId(table);
            const item = yield actions.fetchFromAPI(
                {
                    path: '/extra_table/v1/item?table=' + table + '&ID=' + ID,
                    method: 'GET',
                    data: null,
                    tableId
                }
             );
            return item !== undefined ? actions.setItem( item, tableId ) : undefined;
        },
        *readItems( table ) {
            const tableId = getTableId(table);
            const allItems = yield actions.fetchFromAPI({
                    path: '/extra_table/v1/all_items?table=' + table,
                    method: 'GET',
                    data: null,
                    tableId
            });
            return allItems !== undefined ? actions.setAllItems( allItems, tableId ) : undefined;
        },
    },
} );

register( store );

1 Answer 1

0

It turned out that allItems gets populated ...eventually. So the code in the beginning of the block must be extended to respond when allItems is populated:

export default function Edit(props) {
const { attributes, setAttributes } = props;

const allItems = useSelect(select => select('REST-extra_table').readItems(LECTURERS_TABLE));
const [populated, setPopulated] = useState(false);

if (! populated && allItems.length !== 0) {
    // further code here
    setPopulated(true);
}

What I did is checking each time the block is rendered if allItems is populated, but saving this information in State, so code which I want to run after it is populated runs only once.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.