1

Well ok, it's probably not incorrect that the data is cached, but it's not what I want to happen. But I am stuck on how to correctly set up my custom data store the way I need.

I'm trying to set up a wp.data custom data store to track a few things in my front-end, react, rendered app. It's integrating with some legacy WordPress that has a <select> html element and I need to refresh the data store when the select value changes.

I'm getting stuck on 2 points and I suspect they are related.

1. If the select's value is 1, the product with ID = 1 is correctly fetched from the API. Same, if you switch the select to value 2. But then if you switch back to value/product ID = 1, the product in the data store is not updated... and I am fairly certain now that it's because the resolver's resolution (with that specific ID) was cached.

Here's my app's functional component:

import React from "react";
import { useState } from '@wordpress/element';
import { __ } from "@wordpress/i18n";
import { useSelect } from "@wordpress/data";

import { PRODUCTS_STORE_KEY } from "./data";

function App() {

  const [ productId, setProductId ] = useState( 0 );

  const handleChange = (e) => {
    const productId = parseInt(e.target.value, 10) || e.target.value;
    setProductId(productId);
  };

    // Get product from the store.
    const { product } = useSelect(
        ( select ) => {
            return {
                product: select( PRODUCTS_STORE_KEY ).getProduct( productId ),
            };
        }, [ productId ]
    );

  console.debug('product', product);

  return (
    <div className="App">
      
      <div>
        <label>{__("Choose a product", "your-text-domain")}</label>
        <select onChange={handleChange}>
          <option value="0">{__("None", "your-text-domain")}</option>
          <option value="1">{__("Product 1", "your-text-domain")}</option>
          <option value="2">{__("Product 2", "your-text-domain")}</option>
          <option value="3">{__("Product 3", "your-text-domain")}</option>
          <option value="4">{__("Product 4", "your-text-domain")}</option>
          <option value="5">{__("Product 5", "your-text-domain")}</option>
        </select>
      </div>

      {product && (
        <div>
          <h2>{product.title}</h2>
          <p>{product.description}</p>
          <p>Price: {product.price}</p>
        </div>
      )}
    </div>
  );
}

export default App;

and here's my resolver:

/**
 * Fetch a product object from the Store API.
 *
 * @param {number} productId Id of the product to retrieve.
 */
export function getProduct( productId ) {
    return async ( { dispatch } ) => {
        try {

            let product = null;

            if ( productId ) {

                const response = await fetch( `https://dummyjson.com/products/${productId}`);

                product = await response.json();

                dispatch.hydrateProduct( product );

                return product;

            } 
    
        } catch ( error ) {
            // @todo: Handle an error here eventually.
            console.error( error );
        }
    };
}

So (somehow) I could invalidate the cache using wp.data.dispatch( ‘data/products’ ).invalidateResolution( ‘getProduct’ ); . I am not at all clear on how/where to add this to my code, but also part

2. is that I don't want to invalidate the cache because I don't want to have to fetch the result from the API another time. If I have fetched product 1's data once, I should stash it somewhere and serve it locally the next time the select's value is equal to 1.

It should be clearer in the minimum, viable, example I have created here: https://nwy3j4.csb.app/

As an MVE, using a custom data store is clearly overkill, but I will eventually need to track some custom data.

So, is there a different way to set up my resolver? or do I need to restructure my store?

1 Answer 1

1

Thanks for MVE, @helgatheviking! It was beneficial.

When wp.data caches the resolvers, it also considers arguments. Here's what happens when following the reproduction steps for the bug:

  1. Selecting the first product resolves data, creates a cache for getProduct(1), and sets a value for the product state.
  2. Selecting second does the same (cache: getProduct(2)) and sets a new value for the product state.
  3. When switching back to the first product, wp.data knows the resolution was successful, so it calls the selector. The selector returns the latest product state for Product #2 set in step 2.

Solution: Use product ID as key when storing them in state, so selectors return correct data. Here's the updated fork - https://codesandbox.io/p/sandbox/mve-product-store-example-cached-resolve-value-forked-8lqv3r?file=%2Fsrc%2Fdata%2Findex.js.

P.S. You can check cached resolvers by running - select( storeName ).getCachedResolvers().

5
  • I wish I could upvote this more than once! I have been struggling with this for a week and even getting the MVE up took an entire day. This does resolve my issue with the cached data! Thank you so much @mamaduka! Commented Jan 30 at 13:36
  • Is there any way to modify the data store so that there's a selector that always returns the currently selected product? For example something like getCurrentProduct() that doesn't need a parameter? Commented Jan 30 at 14:08
  • 1
    The pattern from the solution works best for similar cases; it's like querying products by ID in a database. You can also set the selected product ID and use it for fetching. I have updated my fork with example
    – Mamaduka
    Commented Jan 30 at 15:27
  • one more question if you'll indulge me... If actions now support async thunks then how do you decide whether you can use an async action to fetch data or the resolver? Is it more or less the same? Commented Jan 31 at 15:04
  • Posting your reply from Twitter for posterity: If you are fetching data from the server (GET requests), - use resolver/selector. If you are modifying data on the server (POST, DELETE, and so on), use actions. Commented Jan 31 at 15:10

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.