Dynamic configuration from the browser

In the developing a data source extension article, we described how you could create a data source and then configure it in the Studio. But, you might need to add additional dynamic configuration options in the browser when requesting a data source. For example, for pagination or a user-provided search string.

In this article, we'll show you how to do that. For our example, we'll continue using our Star Wars API, this time for characters.

Accessing query parameters in the extension

Our data source schema to return a list of all the Star Wars characters would look like this:

{
"customDataSourceType": "example/star-wars-character-search",
"name": "Star wars character search",
"category": "Content",
"icon": "source",
"schema": []
}

You can store the schema.json file wherever you like, because it's only required in the Studio and not by the code.

The parameters for the custom data source are added to the query of the page URL (for example pageSize in https://yoursite.com/page?pageSize=20). You can access these parameters as context.request.query inside the extension implementation:

import { DataSourceConfiguration, DataSourceContext, DataSourceResult } from '@frontastic/extension-types';
import axios from 'axios';
export default {
'data-sources': {
'example/star-wars-character-search': async (
config: DataSourceConfiguration,
context: DataSourceContext,
): Promise<DataSourceResult> => {
const pageSize = context.request.query.pageSize || 10;
const after = context.request.query.cursor || null;
return await axios
.post('https://swapi-graphql.netlify.app/.netlify/functions/index', {
query: `{
allPeople(first: ${pageSize}, after: ${JSON.stringify(after)}) {
totalCount
pageInfo {
hasNextPage
endCursor
}
people {
id
name
species {
name
}
}
}
}`,
})
.then(
(response): DataSourceResult => {
return {
dataSourcePayload: response.data?.data?.allPeople || {},
} as DataSourceResult;
},
)
.catch((reason) => {
return {
dataSourcePayload: {
ok: false,
error: reason.toString(),
},
} as DataSourceResult;
});
}
}
};

Setting parameters in the component

You can then use the router inside a Frontend component to change the query parameters:

import React from 'react';
import { useRouter } from 'next/router';
import Link from 'next/link';
const StarWarsCharacterSearchTastic = ({ data }) => {
const { totalCount, pageInfo, people } = data.data.dataSource;
const router = useRouter();
const { slug, ...queryWithoutSlug } = router.query;
return (
<div>
<h1 className="text-2xl mt-8 font-sans">Star Wars Characters</h1>
<p className="mt-2">{totalCount} total characters found</p>
{people.map((character) => (
<div key={character.id}>
<h2 className="text-lg mt-6 font-sans">{character.name}</h2>
{character.species !== null && (
<p className="mt-2">Species: {character.species.name}</p>
)}
</div>
))}
{pageInfo.hasNextPage && (
<div className="mt-6">
<Link
href={{
pathname: router.asPath.split('?')[0],
query: {
...queryWithoutSlug,
cursor: pageInfo.endCursor,
},
}}
>
<a className="bg-primary-500 px-4 py-2 text-white">Next Page</a>
</Link>
</div>
)}
</div>
);
};
export default StarWarsCharacterSearchTastic;

Clash of parameters

This query is shared between all the data sources available on the same page.

So, you need to ensure you use query parameter names that don't clash between the different data sources. If 2 or more data sources use the same query parameter name, or if multiple instances of the data source are used on the same page with the same query parameter name, they'll clash.

The dataSourceId can be used to distinguish different data sources since it's guaranteed to have a unique value for each data source.