admin管理员组

文章数量:1125755

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( `/${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: /

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?

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?

Share Improve this question asked Jan 30, 2024 at 2:33 helgathevikinghelgatheviking 14.5k8 gold badges64 silver badges115 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 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().

本文标签: javascriptuseSelect() plus resolver result is serving cached data incorrectly