Skip to content

Commit d2f7239

Browse files
authored
Update useRemoteData to be more explicit about state management (#325)
1 parent 15f1b40 commit d2f7239

File tree

7 files changed

+117
-119
lines changed

7 files changed

+117
-119
lines changed

src/blocks/remote-data-container/components/field-shortcode/FieldShortcodeSelection.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,14 +110,17 @@ interface FieldShortcodeSelectFieldProps {
110110
}
111111

112112
export function FieldShortcodeSelectField( props: FieldShortcodeSelectFieldProps ) {
113-
const { data, execute, loading } = useRemoteData( props.blockName, DISPLAY_QUERY_KEY );
113+
const { data, fetch, loading } = useRemoteData( {
114+
blockName: props.blockName,
115+
queryKey: DISPLAY_QUERY_KEY,
116+
} );
114117

115118
useEffect( () => {
116119
if ( loading || data ) {
117120
return;
118121
}
119122

120-
void execute( props.queryInput );
123+
void fetch( props.queryInput );
121124
}, [ loading, data ] );
122125

123126
if ( ! data || loading ) {

src/blocks/remote-data-container/components/pattern-selection/PatternSelection.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,16 @@ import { getBlockDataSourceType } from '@/utils/localized-block-data';
1010

1111
interface PatternSelectionProps {
1212
blockName: string;
13-
insertPatternBlocks: ( pattern: BlockPattern ) => void;
1413
onCancel: () => void;
14+
onSelectPattern: ( pattern: BlockPattern ) => void;
1515
supportedPatterns: BlockPattern[];
1616
}
1717

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

2121
function onClickPattern( pattern: BlockPattern ) {
22-
props.insertPatternBlocks( pattern );
22+
props.onSelectPattern( pattern );
2323
setShowModal( false );
2424
sendTracksEvent( 'remotedatablocks_add_block', {
2525
action: 'select_pattern',

src/blocks/remote-data-container/components/placeholders/Placeholder.tsx

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,15 @@ import { PlaceholderSingle } from '@/blocks/remote-data-container/components/pla
33

44
export interface PlaceholderProps {
55
blockConfig: BlockConfig;
6-
fetchRemoteData: ( input: RemoteDataQueryInput ) => void;
6+
onSelect: ( input: RemoteDataQueryInput ) => void;
77
}
88

99
export function Placeholder( props: PlaceholderProps ) {
1010
const { loop } = props.blockConfig;
11-
const placeholderProps = {
12-
blockConfig: props.blockConfig,
13-
onSelect: props.fetchRemoteData,
14-
};
1511

1612
if ( loop ) {
17-
return <PlaceholderLoop { ...placeholderProps } />;
13+
return <PlaceholderLoop { ...props } />;
1814
}
1915

20-
return <PlaceholderSingle { ...placeholderProps } />;
16+
return <PlaceholderSingle { ...props } />;
2117
}

src/blocks/remote-data-container/edit.tsx

Lines changed: 55 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { InspectorControls, useBlockProps } from '@wordpress/block-editor';
1+
import { BlockPattern, InspectorControls, useBlockProps } from '@wordpress/block-editor';
22
import { BlockEditProps } from '@wordpress/blocks';
33
import { Spinner } from '@wordpress/components';
4-
import { useEffect, useState } from '@wordpress/element';
4+
import { useState } from '@wordpress/element';
55

66
import { InnerBlocks } from '@/blocks/remote-data-container/components/InnerBlocks';
77
import { DataPanel } from '@/blocks/remote-data-container/components/panels/DataPanel';
@@ -32,120 +32,97 @@ export function Edit( props: BlockEditProps< RemoteDataBlockAttributes > ) {
3232
const {
3333
getInnerBlocks,
3434
getSupportedPatterns,
35+
innerBlocksPattern,
3536
insertPatternBlocks,
36-
markReadyForInsertion,
37-
resetReadyForInsertion,
38-
showPatternSelection,
37+
resetInnerBlocks,
3938
} = usePatterns( blockName, rootClientId );
40-
const { execute } = useRemoteData( blockName, DISPLAY_QUERY_KEY );
41-
const [ initialLoad, setInitialLoad ] = useState< boolean >( true );
42-
43-
function fetchRemoteData( input: RemoteDataQueryInput, insertBlocks = true ) {
44-
execute( input, true )
45-
.then( remoteData => {
46-
if ( remoteData ) {
47-
updateRemoteData(
48-
{
49-
enabledOverrides: props.attributes.remoteData?.enabledOverrides ?? [],
50-
...remoteData,
51-
},
52-
insertBlocks
53-
);
54-
}
55-
} )
56-
.catch( () => {} )
57-
.finally( () => {
58-
setInitialLoad( false );
59-
} );
39+
40+
const { data, fetch, loading, reset } = useRemoteData( {
41+
blockName,
42+
externallyManagedRemoteData: props.attributes.remoteData,
43+
externallyManagedUpdateRemoteData: updateRemoteData,
44+
queryKey: DISPLAY_QUERY_KEY,
45+
} );
46+
47+
const [ showPatternSelection, setShowPatternSelection ] = useState< boolean >( false );
48+
49+
function refreshRemoteData(): void {
50+
void fetch( props.attributes.remoteData?.queryInput ?? {} );
6051
}
6152

62-
// Update the remote data in the block attributes, which is passed via context
63-
// to children blocks. If this is the initial load of remote data, show the
64-
// pattern selection modal so that we can insert the blocks from the pattern.
65-
function updateRemoteData( remoteData: RemoteData, insertBlocks = false ) {
66-
if ( hasRemoteDataChanged( props.attributes.remoteData, remoteData ) ) {
67-
props.setAttributes( { remoteData } );
68-
}
53+
function resetPatternSelection(): void {
54+
resetInnerBlocks();
55+
setShowPatternSelection( false );
56+
}
6957

70-
if ( insertBlocks ) {
71-
markReadyForInsertion();
72-
}
58+
function resetRemoteData(): void {
59+
reset();
60+
resetPatternSelection();
7361
}
7462

75-
const hasInputVariables = Boolean(
76-
blockConfig.selectors.find( selector => selector.query_key === DISPLAY_QUERY_KEY )?.inputs
77-
?.length
78-
);
63+
function onSelectPattern( pattern: BlockPattern ): void {
64+
insertPatternBlocks( pattern );
65+
setShowPatternSelection( false );
66+
}
7967

80-
function refreshRemoteData() {
81-
if ( ! props.attributes.remoteData?.queryInput ) {
82-
if ( hasInputVariables ) {
68+
function onSelectRemoteData( queryInput: RemoteDataQueryInput ): void {
69+
void fetch( queryInput ).then( () => {
70+
if ( innerBlocksPattern ) {
71+
insertPatternBlocks( innerBlocksPattern );
8372
return;
8473
}
8574

86-
fetchRemoteData( {}, true );
87-
} else {
88-
fetchRemoteData( props.attributes.remoteData.queryInput, false );
89-
}
75+
setShowPatternSelection( true );
76+
} );
9077
}
9178

92-
function resetRemoteData() {
93-
props.setAttributes( { remoteData: undefined } );
94-
resetReadyForInsertion();
79+
function updateRemoteData( remoteData?: RemoteData ): void {
80+
if ( hasRemoteDataChanged( props.attributes.remoteData, remoteData ) ) {
81+
props.setAttributes( { remoteData } );
82+
}
9583
}
9684

97-
useEffect( () => {
98-
// Refetch remote data for initial load
99-
refreshRemoteData();
100-
}, [] );
101-
10285
// No remote data has been selected yet, show a placeholder.
103-
if ( ! props.attributes.remoteData ) {
104-
if ( ! hasInputVariables ) {
105-
return null;
106-
}
107-
86+
if ( ! data ) {
10887
return (
10988
<div { ...blockProps }>
110-
<Placeholder blockConfig={ blockConfig } fetchRemoteData={ fetchRemoteData } />
89+
<Placeholder blockConfig={ blockConfig } onSelect={ onSelectRemoteData } />
11190
</div>
11291
);
11392
}
11493

11594
if ( showPatternSelection ) {
116-
const supportedPatterns = getSupportedPatterns( props.attributes.remoteData?.results[ 0 ] );
117-
118-
if ( supportedPatterns.length ) {
119-
return (
120-
<div { ...blockProps }>
121-
<PatternSelection
122-
blockName={ blockName }
123-
insertPatternBlocks={ insertPatternBlocks }
124-
onCancel={ resetReadyForInsertion }
125-
supportedPatterns={ supportedPatterns }
126-
/>
127-
</div>
128-
);
129-
}
95+
const supportedPatterns = getSupportedPatterns( data.results[ 0 ] );
96+
97+
return (
98+
<div { ...blockProps }>
99+
<PatternSelection
100+
blockName={ blockName }
101+
onCancel={ resetPatternSelection }
102+
onSelectPattern={ onSelectPattern }
103+
supportedPatterns={ supportedPatterns }
104+
/>
105+
</div>
106+
);
130107
}
131108

132109
return (
133110
<>
134111
<InspectorControls>
135112
<OverridesPanel
136113
blockConfig={ blockConfig }
137-
remoteData={ props.attributes.remoteData }
114+
remoteData={ data }
138115
updateRemoteData={ updateRemoteData }
139116
/>
140117
<DataPanel
141118
refreshRemoteData={ refreshRemoteData }
142-
remoteData={ props.attributes.remoteData }
119+
remoteData={ data }
143120
resetRemoteData={ resetRemoteData }
144121
/>
145122
</InspectorControls>
146123

147124
<div { ...blockProps }>
148-
{ initialLoad && (
125+
{ loading && (
149126
<div className="remote-data-blocks-loading-overlay">
150127
<Spinner
151128
style={ {
@@ -158,7 +135,7 @@ export function Edit( props: BlockEditProps< RemoteDataBlockAttributes > ) {
158135
<InnerBlocks
159136
blockConfig={ blockConfig }
160137
getInnerBlocks={ getInnerBlocks }
161-
remoteData={ props.attributes.remoteData }
138+
remoteData={ data }
162139
/>
163140
</div>
164141
</>

src/blocks/remote-data-container/hooks/usePatterns.ts

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import {
66
} from '@wordpress/block-editor';
77
import { BlockInstance, cloneBlock, createBlock } from '@wordpress/blocks';
88
import { useDispatch, useSelect } from '@wordpress/data';
9-
import { useState } from '@wordpress/element';
109

1110
import {
1211
getBoundAttributeEntries,
@@ -41,7 +40,6 @@ export function usePatterns( remoteDataBlockName: string, rootClientId: string =
4140
remoteDataBlockName,
4241
[ remoteDataBlockName, rootClientId ],
4342
] );
44-
const [ showPatternSelection, setShowPatternSelection ] = useState< boolean >( false );
4543

4644
// Extract patterns with defined roles.
4745
const patternsByBlockTypes = getPatternsByBlockTypes( remoteDataBlockName );
@@ -80,9 +78,8 @@ export function usePatterns( remoteDataBlockName: string, rootClientId: string =
8078
),
8179
} ) );
8280
},
81+
innerBlocksPattern,
8382
insertPatternBlocks: ( pattern: BlockPattern ): void => {
84-
setShowPatternSelection( false );
85-
8683
// If the pattern is a synced pattern, insert it directly.
8784
if ( isSyncedPattern( pattern ) ) {
8885
const syncedPattern = createBlock( 'core/block', { ref: pattern.id } );
@@ -107,19 +104,9 @@ export function usePatterns( remoteDataBlockName: string, rootClientId: string =
107104

108105
replaceInnerBlocks( rootClientId, patternBlocks ).catch( () => {} );
109106
},
110-
markReadyForInsertion: (): void => {
111-
if ( innerBlocksPattern ) {
112-
returnValue.insertPatternBlocks( innerBlocksPattern );
113-
return;
114-
}
115-
116-
setShowPatternSelection( true );
117-
},
118-
resetReadyForInsertion: (): void => {
107+
resetInnerBlocks: (): void => {
119108
replaceInnerBlocks( rootClientId, [] ).catch( () => {} );
120-
setShowPatternSelection( false );
121109
},
122-
showPatternSelection,
123110
};
124111

125112
return returnValue;

src/blocks/remote-data-container/hooks/useRemoteData.ts

Lines changed: 48 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -32,21 +32,45 @@ async function fetchRemoteData( requestData: RemoteDataApiRequest ): Promise< Re
3232
};
3333
}
3434

35-
// This hook fetches remote data and provides state for the data and loading
36-
// status. If you do not need a separate state update for the data, you can
37-
// instruct the `execute` function to skip it.
35+
interface UseRemoteData {
36+
data?: RemoteData;
37+
fetch: ( queryInput: RemoteDataQueryInput ) => Promise< void >;
38+
loading: boolean;
39+
reset: () => void;
40+
}
41+
42+
interface UseRemoteDataInput {
43+
blockName: string;
44+
enabledOverrides?: string[];
45+
externallyManagedRemoteData?: RemoteData;
46+
externallyManagedUpdateRemoteData?: ( remoteData?: RemoteData ) => void;
47+
onSuccess?: () => void;
48+
queryKey: string;
49+
}
50+
51+
// This hook fetches remote data and manages state for the requests.
52+
//
53+
// If you have another way to manage the state of the remote data, then you must
54+
// pass in the data and a state updater function.
3855
//
3956
// Use case: You might be fetching data only to provide it to setAttributes,
4057
// which is already reactive. Or you might be chaining multiple calls and
4158
// don't need an intermediate state update / re-render.
42-
export function useRemoteData( blockName: string, queryKey: string ) {
43-
const [ data, setData ] = useState< RemoteData | null >( null );
59+
export function useRemoteData( {
60+
blockName,
61+
enabledOverrides = [],
62+
externallyManagedRemoteData,
63+
externallyManagedUpdateRemoteData,
64+
onSuccess,
65+
queryKey,
66+
}: UseRemoteDataInput ): UseRemoteData {
67+
const [ data, setData ] = useState< RemoteData >();
4468
const [ loading, setLoading ] = useState< boolean >( false );
4569

46-
async function execute(
47-
queryInput: RemoteDataQueryInput,
48-
updateDataState = true
49-
): Promise< RemoteData | null > {
70+
const resolvedData = externallyManagedRemoteData ?? data;
71+
const resolvedUpdater = externallyManagedUpdateRemoteData ?? setData;
72+
73+
async function fetch( queryInput: RemoteDataQueryInput ): Promise< void > {
5074
setLoading( true );
5175

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

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

60-
if ( updateDataState ) {
61-
setData( remoteData );
84+
if ( ! remoteData ) {
85+
resolvedUpdater( undefined );
86+
setLoading( false );
87+
return;
6288
}
6389

90+
resolvedUpdater( { enabledOverrides, ...remoteData } );
6491
setLoading( false );
92+
onSuccess?.();
93+
}
6594

66-
return remoteData;
95+
function reset(): void {
96+
resolvedUpdater( undefined );
6797
}
6898

69-
return { data, execute, loading };
99+
return {
100+
data: resolvedData,
101+
fetch,
102+
loading,
103+
reset,
104+
};
70105
}

0 commit comments

Comments
 (0)