/** * External dependencies */ import { QUERY_STATE_STORE_KEY as storeKey } from '@woocommerce/block-data'; import { useSelect, useDispatch } from '@wordpress/data'; import { useRef, useEffect, useCallback } from '@wordpress/element'; import { useQueryStateContext } from '@woocommerce/base-context'; import { assign } from 'lodash'; /** * Internal dependencies */ import { useShallowEqual } from './use-shallow-equal'; /** * A custom hook that exposes the current query state and a setter for the query * state store for the given context. * * "Query State" is a wp.data store that keeps track of an arbitrary object of * query keys and their values. * * @param {string} [context] What context to retrieve the query state for. If not * provided, this hook will attempt to get the context * from the query state context provided by the * QueryStateContextProvider * * @return {Array} An array that has two elements. The first element is the * query state value for the given context. The second element * is a dispatcher function for setting the query state. */ export const useQueryStateByContext = ( context ) => { const queryStateContext = useQueryStateContext(); context = context || queryStateContext; const queryState = useSelect( ( select ) => { const store = select( storeKey ); return store.getValueForQueryContext( context, undefined ); }, [ context ] ); const { setValueForQueryContext } = useDispatch( storeKey ); const setQueryState = useCallback( ( value ) => { setValueForQueryContext( context, value ); }, [ context ] ); return [ queryState, setQueryState ]; }; /** * A custom hook that exposes the current query state value and a setter for the * given context and query key. * * "Query State" is a wp.data store that keeps track of an arbitrary object of * query keys and their values. * * @param {*} queryKey The specific query key to retrieve the value for. * @param {*} defaultValue Default value if query does not exist. * @param {string} [context] What context to retrieve the query state for. If * not provided will attempt to use what is provided * by query state context. * * @return {*} Whatever value is set at the query state index using the * provided context and query key. */ export const useQueryStateByKey = ( queryKey, defaultValue, context ) => { const queryStateContext = useQueryStateContext(); context = context || queryStateContext; const queryValue = useSelect( ( select ) => { const store = select( storeKey ); return store.getValueForQueryKey( context, queryKey, defaultValue ); }, [ context, queryKey ] ); const { setQueryValue } = useDispatch( storeKey ); const setQueryValueByKey = useCallback( ( value ) => { setQueryValue( context, queryKey, value ); }, [ context, queryKey ] ); return [ queryValue, setQueryValueByKey ]; }; /** * A custom hook that works similarly to useQueryStateByContext. However, this * hook allows for synchronizing with a provided queryState object. * * This hook does the following things with the provided `synchronizedQuery` * object: * * - whenever synchronizedQuery varies between renders, the queryState will be * updated to a merged object of the internal queryState and the provided * object. Note, any values from the same properties between objects will * be set from synchronizedQuery. * - if there are no changes between renders, then the existing internal * queryState is always returned. * - on initial render, the synchronizedQuery value is returned. * * Typically, this hook would be used in a scenario where there may be external * triggers for updating the query state (i.e. initial population of query * state by hydration or component attributes, or routing url changes that * affect query state). * * @param {Object} synchronizedQuery A provided query state object to * synchronize internal query state with. * @param {string} [context] What context to retrieve the query state * for. If not provided, will be pulled from * the QueryStateContextProvider in the tree. */ export const useSynchronizedQueryState = ( synchronizedQuery, context ) => { const queryStateContext = useQueryStateContext(); context = context || queryStateContext; const [ queryState, setQueryState ] = useQueryStateByContext( context ); const currentSynchronizedQuery = useShallowEqual( synchronizedQuery ); // used to ensure we allow initial synchronization to occur before // returning non-synced state. const isInitialized = useRef( false ); // update queryState anytime incoming synchronizedQuery changes useEffect( () => { setQueryState( assign( {}, queryState, currentSynchronizedQuery ) ); isInitialized.current = true; }, [ currentSynchronizedQuery ] ); return isInitialized.current ? [ queryState, setQueryState ] : [ synchronizedQuery, setQueryState ]; };