Transform nested API responses
Use resultSelector to extract a subset of the response and transformResult to reshape it with custom logic.
APIs often return wrapper objects ({ status, data: { items: [...] } }) rather than a flat array. Instead of restructuring data in every consumer, let the DataSource do the work. resultSelector plucks a nested path (and supports inline .filter() / .map() calls), while transformResult takes a function for heavier processing like lookups or computed fields.
<App>
<!--
{
people:
[
{ id: 1, name: 'Alice', active: true, group: 'A' },
{ id: 2, name: 'Bob', active: false, group: 'B' },
{ id: 3, name: 'Carol', active: true, group: 'A' },
{ id: 4, name: 'Dave', active: true, group: 'B' }
]
}
-->
<!-- Use resultSelector to select the items array -->
<DataSource
id="allPeople"
url="/api/people"
resultSelector="people"
/>
<!-- Use resultSelector to filter the users array -->
<DataSource
id="activePeople"
url="/api/people"
resultSelector="people.filter(p => p.active)"
/>
<!-- Use transformResult -->
<script>
function transformPeople (data) {
console.log(data);
const items = data.people;
const itemMap = {
A: 'Austin',
B: 'Boston'
};
return items.map(item => ({
...item,
city: itemMap[item.group]
}));
};
</script>
<DataSource
id="transformedPeople"
url="/api/people"
transformResult="{transformPeople}"
/>
<Text>All people:</Text>
<List data="{allPeople}">
<Text>{$item.name} ({$item.group})</Text>
</List>
<Text>Active people:</Text>
<List data="{activePeople}">
<Text>{$item.name} ({$item.group})</Text>
</List>
<Text>Transformed people:</Text>
<List data="{transformedPeople}">
<Text>{$item.name} ({$item.city})</Text>
</List>
</App><App>
<!--
{
people:
[
{ id: 1, name: 'Alice', active: true, group: 'A' },
{ id: 2, name: 'Bob', active: false, group: 'B' },
{ id: 3, name: 'Carol', active: true, group: 'A' },
{ id: 4, name: 'Dave', active: true, group: 'B' }
]
}
-->
<!-- Use resultSelector to select the items array -->
<DataSource
id="allPeople"
url="/api/people"
resultSelector="people"
/>
<!-- Use resultSelector to filter the users array -->
<DataSource
id="activePeople"
url="/api/people"
resultSelector="people.filter(p => p.active)"
/>
<!-- Use transformResult -->
<script>
function transformPeople (data) {
console.log(data);
const items = data.people;
const itemMap = {
A: 'Austin',
B: 'Boston'
};
return items.map(item => ({
...item,
city: itemMap[item.group]
}));
};
</script>
<DataSource
id="transformedPeople"
url="/api/people"
transformResult="{transformPeople}"
/>
<Text>All people:</Text>
<List data="{allPeople}">
<Text>{$item.name} ({$item.group})</Text>
</List>
<Text>Active people:</Text>
<List data="{activePeople}">
<Text>{$item.name} ({$item.group})</Text>
</List>
<Text>Transformed people:</Text>
<List data="{transformedPeople}">
<Text>{$item.name} ({$item.city})</Text>
</List>
</App>Key points
resultSelector plucks a path from the response: Set it to a dot-separated key path like "people" and the DataSource's value will be that nested value instead of the full response object.
resultSelector supports inline expressions: You can chain .filter(), .map(), or .find() directly — e.g., resultSelector="people.filter(p => p.active)". The expression runs on every fetch result.
transformResult takes a function for complex reshaping: Assign a function reference — transformResult="{myTransform}" — that receives the raw response and returns the final value. Use this when you need lookups, computed fields, or multi-step transformations.
Both can be combined: resultSelector runs first (extracting a subset), then transformResult receives that subset. However, for most cases one or the other is sufficient.
See also
- Use mock data during development — test your selectors with inline data
- Show a skeleton while data loads — display a placeholder during the fetch
- Poll an API at regular intervals — combine transformations with automatic polling