Skip to content

Commit

Permalink
Update useRemoteData to be more explicit about state management (#325)
Browse files Browse the repository at this point in the history
  • Loading branch information
chriszarate authored Feb 3, 2025
1 parent 15f1b40 commit d2f7239
Show file tree
Hide file tree
Showing 7 changed files with 117 additions and 119 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -110,14 +110,17 @@ interface FieldShortcodeSelectFieldProps {
}

export function FieldShortcodeSelectField( props: FieldShortcodeSelectFieldProps ) {
const { data, execute, loading } = useRemoteData( props.blockName, DISPLAY_QUERY_KEY );
const { data, fetch, loading } = useRemoteData( {
blockName: props.blockName,
queryKey: DISPLAY_QUERY_KEY,
} );

useEffect( () => {
if ( loading || data ) {
return;
}

void execute( props.queryInput );
void fetch( props.queryInput );
}, [ loading, data ] );

if ( ! data || loading ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,16 @@ import { getBlockDataSourceType } from '@/utils/localized-block-data';

interface PatternSelectionProps {
blockName: string;
insertPatternBlocks: ( pattern: BlockPattern ) => void;
onCancel: () => void;
onSelectPattern: ( pattern: BlockPattern ) => void;
supportedPatterns: BlockPattern[];
}

export function PatternSelection( props: PatternSelectionProps ) {
const [ showModal, setShowModal ] = useState< boolean >( false );

function onClickPattern( pattern: BlockPattern ) {
props.insertPatternBlocks( pattern );
props.onSelectPattern( pattern );
setShowModal( false );
sendTracksEvent( 'remotedatablocks_add_block', {
action: 'select_pattern',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,15 @@ import { PlaceholderSingle } from '@/blocks/remote-data-container/components/pla

export interface PlaceholderProps {
blockConfig: BlockConfig;
fetchRemoteData: ( input: RemoteDataQueryInput ) => void;
onSelect: ( input: RemoteDataQueryInput ) => void;
}

export function Placeholder( props: PlaceholderProps ) {
const { loop } = props.blockConfig;
const placeholderProps = {
blockConfig: props.blockConfig,
onSelect: props.fetchRemoteData,
};

if ( loop ) {
return <PlaceholderLoop { ...placeholderProps } />;
return <PlaceholderLoop { ...props } />;
}

return <PlaceholderSingle { ...placeholderProps } />;
return <PlaceholderSingle { ...props } />;
}
133 changes: 55 additions & 78 deletions src/blocks/remote-data-container/edit.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { InspectorControls, useBlockProps } from '@wordpress/block-editor';
import { BlockPattern, InspectorControls, useBlockProps } from '@wordpress/block-editor';
import { BlockEditProps } from '@wordpress/blocks';
import { Spinner } from '@wordpress/components';
import { useEffect, useState } from '@wordpress/element';
import { useState } from '@wordpress/element';

import { InnerBlocks } from '@/blocks/remote-data-container/components/InnerBlocks';
import { DataPanel } from '@/blocks/remote-data-container/components/panels/DataPanel';
Expand Down Expand Up @@ -32,120 +32,97 @@ export function Edit( props: BlockEditProps< RemoteDataBlockAttributes > ) {
const {
getInnerBlocks,
getSupportedPatterns,
innerBlocksPattern,
insertPatternBlocks,
markReadyForInsertion,
resetReadyForInsertion,
showPatternSelection,
resetInnerBlocks,
} = usePatterns( blockName, rootClientId );
const { execute } = useRemoteData( blockName, DISPLAY_QUERY_KEY );
const [ initialLoad, setInitialLoad ] = useState< boolean >( true );

function fetchRemoteData( input: RemoteDataQueryInput, insertBlocks = true ) {
execute( input, true )
.then( remoteData => {
if ( remoteData ) {
updateRemoteData(
{
enabledOverrides: props.attributes.remoteData?.enabledOverrides ?? [],
...remoteData,
},
insertBlocks
);
}
} )
.catch( () => {} )
.finally( () => {
setInitialLoad( false );
} );

const { data, fetch, loading, reset } = useRemoteData( {
blockName,
externallyManagedRemoteData: props.attributes.remoteData,
externallyManagedUpdateRemoteData: updateRemoteData,
queryKey: DISPLAY_QUERY_KEY,
} );

const [ showPatternSelection, setShowPatternSelection ] = useState< boolean >( false );

function refreshRemoteData(): void {
void fetch( props.attributes.remoteData?.queryInput ?? {} );
}

// Update the remote data in the block attributes, which is passed via context
// to children blocks. If this is the initial load of remote data, show the
// pattern selection modal so that we can insert the blocks from the pattern.
function updateRemoteData( remoteData: RemoteData, insertBlocks = false ) {
if ( hasRemoteDataChanged( props.attributes.remoteData, remoteData ) ) {
props.setAttributes( { remoteData } );
}
function resetPatternSelection(): void {
resetInnerBlocks();
setShowPatternSelection( false );
}

if ( insertBlocks ) {
markReadyForInsertion();
}
function resetRemoteData(): void {
reset();
resetPatternSelection();
}

const hasInputVariables = Boolean(
blockConfig.selectors.find( selector => selector.query_key === DISPLAY_QUERY_KEY )?.inputs
?.length
);
function onSelectPattern( pattern: BlockPattern ): void {
insertPatternBlocks( pattern );
setShowPatternSelection( false );
}

function refreshRemoteData() {
if ( ! props.attributes.remoteData?.queryInput ) {
if ( hasInputVariables ) {
function onSelectRemoteData( queryInput: RemoteDataQueryInput ): void {
void fetch( queryInput ).then( () => {
if ( innerBlocksPattern ) {
insertPatternBlocks( innerBlocksPattern );
return;
}

fetchRemoteData( {}, true );
} else {
fetchRemoteData( props.attributes.remoteData.queryInput, false );
}
setShowPatternSelection( true );
} );
}

function resetRemoteData() {
props.setAttributes( { remoteData: undefined } );
resetReadyForInsertion();
function updateRemoteData( remoteData?: RemoteData ): void {
if ( hasRemoteDataChanged( props.attributes.remoteData, remoteData ) ) {
props.setAttributes( { remoteData } );
}
}

useEffect( () => {
// Refetch remote data for initial load
refreshRemoteData();
}, [] );

// No remote data has been selected yet, show a placeholder.
if ( ! props.attributes.remoteData ) {
if ( ! hasInputVariables ) {
return null;
}

if ( ! data ) {
return (
<div { ...blockProps }>
<Placeholder blockConfig={ blockConfig } fetchRemoteData={ fetchRemoteData } />
<Placeholder blockConfig={ blockConfig } onSelect={ onSelectRemoteData } />
</div>
);
}

if ( showPatternSelection ) {
const supportedPatterns = getSupportedPatterns( props.attributes.remoteData?.results[ 0 ] );

if ( supportedPatterns.length ) {
return (
<div { ...blockProps }>
<PatternSelection
blockName={ blockName }
insertPatternBlocks={ insertPatternBlocks }
onCancel={ resetReadyForInsertion }
supportedPatterns={ supportedPatterns }
/>
</div>
);
}
const supportedPatterns = getSupportedPatterns( data.results[ 0 ] );

return (
<div { ...blockProps }>
<PatternSelection
blockName={ blockName }
onCancel={ resetPatternSelection }
onSelectPattern={ onSelectPattern }
supportedPatterns={ supportedPatterns }
/>
</div>
);
}

return (
<>
<InspectorControls>
<OverridesPanel
blockConfig={ blockConfig }
remoteData={ props.attributes.remoteData }
remoteData={ data }
updateRemoteData={ updateRemoteData }
/>
<DataPanel
refreshRemoteData={ refreshRemoteData }
remoteData={ props.attributes.remoteData }
remoteData={ data }
resetRemoteData={ resetRemoteData }
/>
</InspectorControls>

<div { ...blockProps }>
{ initialLoad && (
{ loading && (
<div className="remote-data-blocks-loading-overlay">
<Spinner
style={ {
Expand All @@ -158,7 +135,7 @@ export function Edit( props: BlockEditProps< RemoteDataBlockAttributes > ) {
<InnerBlocks
blockConfig={ blockConfig }
getInnerBlocks={ getInnerBlocks }
remoteData={ props.attributes.remoteData }
remoteData={ data }
/>
</div>
</>
Expand Down
17 changes: 2 additions & 15 deletions src/blocks/remote-data-container/hooks/usePatterns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
} from '@wordpress/block-editor';
import { BlockInstance, cloneBlock, createBlock } from '@wordpress/blocks';
import { useDispatch, useSelect } from '@wordpress/data';
import { useState } from '@wordpress/element';

import {
getBoundAttributeEntries,
Expand Down Expand Up @@ -41,7 +40,6 @@ export function usePatterns( remoteDataBlockName: string, rootClientId: string =
remoteDataBlockName,
[ remoteDataBlockName, rootClientId ],
] );
const [ showPatternSelection, setShowPatternSelection ] = useState< boolean >( false );

// Extract patterns with defined roles.
const patternsByBlockTypes = getPatternsByBlockTypes( remoteDataBlockName );
Expand Down Expand Up @@ -80,9 +78,8 @@ export function usePatterns( remoteDataBlockName: string, rootClientId: string =
),
} ) );
},
innerBlocksPattern,
insertPatternBlocks: ( pattern: BlockPattern ): void => {
setShowPatternSelection( false );

// If the pattern is a synced pattern, insert it directly.
if ( isSyncedPattern( pattern ) ) {
const syncedPattern = createBlock( 'core/block', { ref: pattern.id } );
Expand All @@ -107,19 +104,9 @@ export function usePatterns( remoteDataBlockName: string, rootClientId: string =

replaceInnerBlocks( rootClientId, patternBlocks ).catch( () => {} );
},
markReadyForInsertion: (): void => {
if ( innerBlocksPattern ) {
returnValue.insertPatternBlocks( innerBlocksPattern );
return;
}

setShowPatternSelection( true );
},
resetReadyForInsertion: (): void => {
resetInnerBlocks: (): void => {
replaceInnerBlocks( rootClientId, [] ).catch( () => {} );
setShowPatternSelection( false );
},
showPatternSelection,
};

return returnValue;
Expand Down
61 changes: 48 additions & 13 deletions src/blocks/remote-data-container/hooks/useRemoteData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,21 +32,45 @@ async function fetchRemoteData( requestData: RemoteDataApiRequest ): Promise< Re
};
}

// This hook fetches remote data and provides state for the data and loading
// status. If you do not need a separate state update for the data, you can
// instruct the `execute` function to skip it.
interface UseRemoteData {
data?: RemoteData;
fetch: ( queryInput: RemoteDataQueryInput ) => Promise< void >;
loading: boolean;
reset: () => void;
}

interface UseRemoteDataInput {
blockName: string;
enabledOverrides?: string[];
externallyManagedRemoteData?: RemoteData;
externallyManagedUpdateRemoteData?: ( remoteData?: RemoteData ) => void;
onSuccess?: () => void;
queryKey: string;
}

// This hook fetches remote data and manages state for the requests.
//
// If you have another way to manage the state of the remote data, then you must
// pass in the data and a state updater function.
//
// Use case: You might be fetching data only to provide it to setAttributes,
// which is already reactive. Or you might be chaining multiple calls and
// don't need an intermediate state update / re-render.
export function useRemoteData( blockName: string, queryKey: string ) {
const [ data, setData ] = useState< RemoteData | null >( null );
export function useRemoteData( {
blockName,
enabledOverrides = [],
externallyManagedRemoteData,
externallyManagedUpdateRemoteData,
onSuccess,
queryKey,
}: UseRemoteDataInput ): UseRemoteData {
const [ data, setData ] = useState< RemoteData >();
const [ loading, setLoading ] = useState< boolean >( false );

async function execute(
queryInput: RemoteDataQueryInput,
updateDataState = true
): Promise< RemoteData | null > {
const resolvedData = externallyManagedRemoteData ?? data;
const resolvedUpdater = externallyManagedUpdateRemoteData ?? setData;

async function fetch( queryInput: RemoteDataQueryInput ): Promise< void > {
setLoading( true );

const requestData: RemoteDataApiRequest = {
Expand All @@ -57,14 +81,25 @@ export function useRemoteData( blockName: string, queryKey: string ) {

const remoteData = await fetchRemoteData( requestData ).catch( () => null );

if ( updateDataState ) {
setData( remoteData );
if ( ! remoteData ) {
resolvedUpdater( undefined );
setLoading( false );
return;
}

resolvedUpdater( { enabledOverrides, ...remoteData } );
setLoading( false );
onSuccess?.();
}

return remoteData;
function reset(): void {
resolvedUpdater( undefined );
}

return { data, execute, loading };
return {
data: resolvedData,
fetch,
loading,
reset,
};
}
Loading

0 comments on commit d2f7239

Please sign in to comment.