Calling an action

If you want to trigger an operation on a backend system or want to fetch data asynchronously after initial rendering, you call an action extension (see the developing an action extension article for more information).

For this example, we'll use the following Frontend component to implement searching a backend data set using an action:

import React from 'react';
const CharacterSearchTastic: React.FC = () => {
return (
<>
<form
onSubmit={(event) => {
event.preventDefault();
}}
>
<label>
Search:
<input type="text" name="search" />
</label>
<input type="submit" value="Search" />
</form>
<ul></ul>
</>
);
};
export default CharacterSearchTastic;
{
"tasticType": "example/character-search",
"name": "Character search",
"category": "Example",
"description": "A frontend component showing actions and session handling",
"schema": []
}

The Frontend component doesn't provide any configuration to the Studio and (so far) only renders a rudimentary search form and an empty list.

Perform the fetch call

An action extension is a server function in the API hub which can be invoked by a URL in the following format: https://<frontastic-host>/frontastic/action/<namespace>/<action>(also see the action API section).

To access it, you should always use the fetchApiHub() function:

import { fetchApiHub } from 'frontastic';
export const characterSearch = async (search: string) => {
return await fetchApiHub(
'/action/star-wars/character?search=' + encodeURIComponent(search)
);
};

The fetchApiHub() function not only resolves the correct API hub host for you but also ensures to maintain the session. For this reason, you should always use the fetchApiHub() function for communication with the API hub.

By convention, you should store the file to encapsulate the fetchApiHub() call in packages/<project>/frontend/frontastic/actions/.

Sending custom headers

To send custom headers to your actions, you must prefix the header name with coFE-. Otherwise, the API hub won't pass the header to the action extension handler. For example, to send a custom header called Custom-Configuration, you'll rename it to coFE-Custom-Configuration, as shown in the code below.

import { fetchApiHub } from 'frontastic';
export const characterSearch = async (search: string) => {
return await fetchApiHub(
'/action/star-wars/character?search=' + encodeURIComponent(search),
{
method: 'POST',
headers: { 'coFE-Custom-Configuration': 'header-value-as-string' },
}
);
};

Register fetcher as a hook

The fetch function is wrapped into a higher-order component to receive the base URL to query. This way, it can be used together with the contenxt provider component, where you should register your fetchers in the manner of ReactJS hooks:

// …
import { characterSearch } from '../actions/doc-character-search';
// …
export const FrontasticProvider: React.FC> = ({ children }) => {
return (
<SWRConfig value={{ fetcher: fetchApiHub }}>
<FrontasticContext.Provider
value={{
// ...
useStarWars: {
characterSearch: characterSearch,
},
}}
>
{children}
</FrontasticContext.Provider>
</SWRConfig>
);
};
// …
export const useStarWars = () => {
const context = React.useContext(FrontasticContext);
if (!context) throw new Error('Expected to be wrapped in FrontasticProvider');
return context.useStarWars;
};

The code above needs to be added to the file packages/<project>/frontend/frontastic/lib/provider.tsx.

There are 2 steps:

  1. Register the characterSearch() function on the context provider
  2. If not done yet, expose a fetcher function to make use of the functions in the corresponding hook

In this example, a new hook (useStarWars) is exposed for which a new provider property is used.

The way fetchers are registered in the lib/ folder will change soon.

Execute the hook to fetch search results

Based on this infrastructure code, you can now complete the Frontend component and use the useState() hook to maintain the search results locally:

import React, { useState } from 'react';
import { useStarWars } from 'frontastic';
const CharacterSearchTastic: React.FC = () => {
const { characterSearch } = useStarWars();
const [searchResults, setSearchResults] = useState([]);
return (
<>
<form
onSubmit={(event) => {
event.preventDefault();
characterSearch(event.target[0].value).then((result) => {
setSearchResults(result.results || []);
});
}}
>
<label>
Search:
<input type="text" name="search" />
</label>
<input type="submit" value="Search" />
</form>
<ul>
{searchResults.map((character) => {
return <li key={character.url}>{character.name}</li>;
})}
</ul>
</>
);
};
export default CharacterSearchTastic;