PluginProbe
ElasticPress / 4.5.0
ElasticPress v4.5.0
5.3.5 5.3.4 3.6.5 3.6.6 4.0.0 4.0.1 4.1.0 4.2.0 4.2.1 4.2.2 4.3.0 4.3.1 4.4.0 4.4.1 4.5.0 4.5.1 4.5.2 4.6.0 4.6.1 4.7.0 4.7.1 4.7.2 5.0.0 5.0.1 5.0.2 All 108 releases
elasticpress / assets / js / api-search / index.js

index.js in ElasticPress 4.5.0, at assets/js/api-search/index.js

359 lines 7.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * WordPress dependencies.
3 */
4 import {
5 createContext,
6 useCallback,
7 useContext,
8 useEffect,
9 useMemo,
10 useReducer,
11 useRef,
12 WPElement,
13 } from '@wordpress/element';
14
15 /**
16 * Internal dependencies.
17 */
18 import { useFetchResults } from './src/hooks';
19 import reducer from './src/reducer';
20 import {
21 getArgsFromUrlParams,
22 getDefaultArgsFromSchema,
23 getUrlParamsFromArgs,
24 getUrlWithParams,
25 } from './src/utilities';
26
27 /**
28 * Instant Results context.
29 */
30 const Context = createContext();
31
32 /**
33 * Instant Results provider component.
34 *
35 * @param {object} props Component props.
36 * @param {string} props.apiEndpoint API endpoint.
37 * @param {string} props.apiHost API Host.
38 * @param {object} props.argsSchema Schema describing supported args.
39 * @param {string} props.authorization Authorization header.
40 * @param {string} props.requestIdBase Base of Requests IDs.
41 * @param {WPElement} props.children Component children.
42 * @param {string} props.paramPrefix Prefix used to set and parse URL parameters.
43 * @param {Function} props.onAuthError Function to run when request authentication fails.
44 * @returns {WPElement} Component.
45 */
46 export const ApiSearchProvider = ({
47 apiEndpoint,
48 apiHost,
49 authorization,
50 requestIdBase,
51 argsSchema,
52 children,
53 paramPrefix,
54 onAuthError,
55 }) => {
56 /**
57 * Any default args from the URL.
58 */
59 const defaultArgsFromUrl = useMemo(() => {
60 if (!paramPrefix) {
61 return {};
62 }
63
64 return getArgsFromUrlParams(argsSchema, paramPrefix);
65 }, [argsSchema, paramPrefix]);
66
67 /**
68 * All default args including defaults from the schema.
69 */
70 const defaultArgs = useMemo(() => {
71 const defaultArgsFromSchema = getDefaultArgsFromSchema(argsSchema);
72
73 return {
74 ...defaultArgsFromSchema,
75 ...defaultArgsFromUrl,
76 };
77 }, [argsSchema, defaultArgsFromUrl]);
78
79 /**
80 * Whether the provider is "on" by default.
81 */
82 const defaultIsOn = useMemo(() => {
83 return Object.keys(defaultArgsFromUrl).length > 0;
84 }, [defaultArgsFromUrl]);
85
86 /**
87 * Set up fetch method.
88 */
89 const fetchResults = useFetchResults(
90 apiHost,
91 apiEndpoint,
92 authorization,
93 onAuthError,
94 requestIdBase,
95 );
96
97 /**
98 * Set up the reducer.
99 */
100 const [state, dispatch] = useReducer(reducer, {
101 aggregations: {},
102 args: defaultArgs,
103 argsSchema,
104 isLoading: false,
105 isOn: defaultIsOn,
106 isPoppingState: false,
107 searchResults: [],
108 searchedTerm: '',
109 totalResults: 0,
110 });
111
112 /**
113 * Create state ref.
114 *
115 * Helps to avoid dependency hell.
116 */
117 const stateRef = useRef(state);
118
119 stateRef.current = state;
120
121 /**
122 * Clear facet contraints.
123 *
124 * @returns {void}
125 */
126 const clearConstraints = useCallback(() => {
127 dispatch({ type: 'CLEAR_CONSTRAINTS' });
128 }, []);
129
130 /**
131 * Clear search resu;ts.
132 *
133 * @returns {void}
134 */
135 const clearResults = useCallback(() => {
136 dispatch({ type: 'CLEAR_RESULTS' });
137 }, []);
138
139 /**
140 * Update the search query args, triggering a search.
141 *
142 * @param {object} args Search args.
143 * @returns {void}
144 */
145 const search = useCallback((args) => {
146 dispatch({ type: 'SEARCH', args });
147 }, []);
148
149 /**
150 * Update the search term, triggering a search and resetting facet
151 * constraints.
152 *
153 * @param {string} searchTerm Search term.
154 * @returns {void}
155 */
156 const searchFor = (searchTerm) => {
157 dispatch({ type: 'SEARCH_FOR', searchTerm });
158 };
159
160 /**
161 * Set loading state.
162 *
163 * @param {boolean} isLoading Is loading?
164 * @returns {void}
165 */
166 const setIsLoading = (isLoading) => {
167 dispatch({ type: 'SET_IS_LOADING', isLoading });
168 };
169
170 /**
171 * Set search results based on an Elasticsearch response.
172 *
173 * @param {object} response Elasticsearch response.
174 * @returns {void}
175 */
176 const setResults = (response) => {
177 dispatch({ type: 'SET_RESULTS', response });
178 };
179
180 /**
181 * Go to the next page of search results.
182 *
183 * @returns {void}
184 */
185 const nextPage = () => {
186 dispatch({ type: 'NEXT_PAGE' });
187 };
188
189 /**
190 * Go to the previous page of search results.
191 *
192 * @returns {void}
193 */
194 const previousPage = () => {
195 dispatch({ type: 'PREVIOUS_PAGE' });
196 };
197
198 /**
199 * Set search args from popped history state.
200 *
201 * @param {object} args Search args.
202 */
203 const popState = (args) => {
204 dispatch({ type: 'POP_STATE', args });
205 };
206
207 /**
208 * Turn off the provider.
209 *
210 * @returns {void}
211 */
212 const turnOff = () => {
213 dispatch({ type: 'TURN_OFF' });
214 };
215
216 /**
217 * Push search args to browser history.
218 *
219 * @returns {void}
220 */
221 const pushState = useCallback(() => {
222 if (typeof paramPrefix === 'undefined') {
223 return;
224 }
225
226 const { args, isOn } = stateRef.current;
227 const state = { args, isOn };
228
229 if (window.history.state) {
230 if (isOn) {
231 const params = getUrlParamsFromArgs(args, argsSchema, paramPrefix);
232 const url = getUrlWithParams(paramPrefix, params);
233
234 window.history.pushState(state, document.title, url);
235 } else {
236 const url = getUrlWithParams(paramPrefix);
237
238 window.history.pushState(state, document.title, url);
239 }
240 } else {
241 window.history.replaceState(state, document.title, window.location.href);
242 }
243 }, [argsSchema, paramPrefix]);
244
245 /**
246 * Handle popstate event.
247 *
248 * @param {Event} event popstate event.
249 */
250 const onPopState = useCallback(
251 (event) => {
252 if (typeof paramPrefix === 'undefined') {
253 return;
254 }
255
256 const hasState = event.state && Object.keys(event.state).length > 0;
257
258 if (hasState) {
259 popState(event.state);
260 }
261 },
262 [paramPrefix],
263 );
264
265 /**
266 * Handle initialization.
267 *
268 * @returns {Function} A cleanup function.
269 */
270 const handleInit = useCallback(() => {
271 window.addEventListener('popstate', onPopState);
272
273 return () => {
274 window.removeEventListener('popstate', onPopState);
275 };
276 }, [onPopState]);
277
278 /**
279 * Handle a change to search args.
280 *
281 * @returns {void}
282 */
283 const handleSearch = useCallback(async () => {
284 const { args, isOn, isPoppingState } = stateRef.current;
285
286 if (!isPoppingState) {
287 pushState();
288 }
289
290 if (!isOn) {
291 return;
292 }
293
294 const urlParams = getUrlParamsFromArgs(args, argsSchema);
295
296 setIsLoading(true);
297
298 const response = await fetchResults(urlParams);
299
300 if (!response) {
301 return;
302 }
303
304 setResults(response);
305 setIsLoading(false);
306 }, [argsSchema, fetchResults, pushState]);
307
308 /**
309 * Effects.
310 */
311 useEffect(handleInit, [handleInit]);
312 useEffect(handleSearch, [
313 handleSearch,
314 state.args,
315 state.args.orderby,
316 state.args.order,
317 state.args.offset,
318 state.args.search,
319 ]);
320
321 /**
322 * Provide state to context.
323 */
324 const { aggregations, args, isLoading, isOn, searchResults, searchTerm, totalResults } =
325 stateRef.current;
326
327 // eslint-disable-next-line react/jsx-no-constructed-context-values
328 const contextValue = {
329 aggregations,
330 args,
331 clearConstraints,
332 clearResults,
333 getUrlParamsFromArgs,
334 getUrlWithParams,
335 isLoading,
336 isOn,
337 searchResults,
338 searchTerm,
339 search,
340 searchFor,
341 setResults,
342 nextPage,
343 previousPage,
344 totalResults,
345 turnOff,
346 };
347
348 return <Context.Provider value={contextValue}>{children}</Context.Provider>;
349 };
350
351 /**
352 * Use the API Search context.
353 *
354 * @returns {object} API Search Context.
355 */
356 export const useApiSearch = () => {
357 return useContext(Context);
358 };
359