React bindings for @_linked/core.
@_linked/react takes a Linked query from @_linked/core's Schema-Parameterized Query DSL and maps the top-level query result keys to props for a React component.
This package provides:
linkedComponent(...)linkedSetComponent(...)LinkedComponentClassuseStyles(...)
npm install @_linked/react @_linked/core react react-domimport {
linkedComponent,
linkedSetComponent,
linkedShape,
} from '@_linked/react';linkedComponent(...) wraps a React component with a Linked query. You pass a query built with Shape.query(...) (which prepares query execution), not Shape.select(...) (which executes immediately). At render time, when you pass of={{id: ...}}, the wrapper applies the prepared query to that subject and injects the query result keys as props into your component.
const PersonCard = linkedComponent(
Person.query((p) => p.name),
({name, source, _refresh}) => (
<article>
<h3>{name}</h3>
<small>{source.id}</small>
<button onClick={() => _refresh()}>Reload</button>
</article>
),
);
// External API: pass `of` as a node reference (`{id: string}`), Shape, or QResult.
<PersonCard of={{id: 'https://example.org/p1'}} />;Props received by the wrapped component:
- Query result props: all top-level keys from the query result become direct props (for example
name). source: the resolved shape instance for the inputofsubject._refresh(updatedProps?): rerun the query (_refresh()) or patch local query-result props before rerender (_refresh({...})).- Custom props: any additional props you pass to the linked component are forwarded as normal.
_refresh is injected into wrapped linkedComponent(...) render functions.
_refresh()reruns the query and rerenders when results return._refresh(updatedProps)mergesupdatedPropsinto current query result state and rerenders immediately (without fetching first).updatedPropsis for query result keys only (for examplename,activefrom your query), not regular additional props passed by parents.
Example use case: optimistic UI after a mutation.
const PersonCard = linkedComponent(
Person.query((p) => [p.name, p.active]),
({id, name, active, _refresh, title}) => (
<div>
<h4>{title}</h4>
<span>{name}</span>
<button
onClick={async () => {
// Patch query-result keys immediately (name/active/id/etc.)
_refresh({active: !active}); // optimistic local query-result update
await saveActiveFlag(id, !active); // your write call
_refresh(); // optional: sync with store response
// Not for parent custom props like `title`; those come from parent rerender.
}}
>
Toggle active
</button>
</div>
),
);Use linkedSetComponent(...) when you want to render a list of sources.
const NameList = linkedSetComponent(
Person.query((p) => p.name),
({linkedData}) => (
<ul>
{(linkedData || []).map((person) => (
<li key={person.id}>{person.name}</li>
))}
</ul>
),
);const personQuery = Person.query((p) => [p.name, p.hobby]);
const NameList = linkedSetComponent({persons: personQuery}, ({persons}) => (
<ul>
{persons.map((person) => (
<li key={person.id}>{person.name}</li>
))}
</ul>
));Both formats are supported. For linked-set wrappers, the external API is also of (optional). Internally this becomes sources for the wrapped component.
When LinkedStorage is initialized and data is not already preloaded in of:
- First render: returns a loading element.
- Query resolves: component rerenders with mapped query result props.
- Source changes (
ofchanges): prior query result is cleared and query runs again.
Loading fallback is currently fixed to:
<div class="ld-loader" role="status" aria-label="Loading"></div>There is no API prop to replace this element today. You can style it via CSS class .ld-loader.
When linkedSetComponent(...) has a limit (explicit query limit or default limit), wrapped props include:
query.nextPage()query.previousPage()query.setPage(pageIndex)query.setLimit(limit)
There is no public setOffset(...) in the React query controller; use setPage, nextPage, or previousPage.
Example:
import React from 'react';
const PeopleList = linkedSetComponent(
Person.query((p) => [p.name]).limit(5),
({linkedData = [], query}) => {
const [page, setPage] = React.useState(0);
return (
<section>
<ul>
{linkedData.map((person) => (
<li key={person.id}>{person.name}</li>
))}
</ul>
<div>
<button
onClick={() => {
query?.previousPage();
setPage((p) => Math.max(0, p - 1));
}}
>
Previous
</button>
<span>Page {page + 1}</span>
<button
onClick={() => {
query?.nextPage();
setPage((p) => p + 1);
}}
>
Next
</button>
<label>
Page size
<select
defaultValue="5"
onChange={(e) => {
const nextLimit = Number(e.target.value);
query?.setLimit(nextLimit);
query?.setPage(0);
setPage(0);
}}
>
<option value="5">5</option>
<option value="10">10</option>
<option value="25">25</option>
</select>
</label>
</div>
</section>
);
},
);- This package depends on
@_linked/corequery APIs andpreloadFor(...)/BoundComponentbehavior from core. @_linked/reactitself does not provide RDF storage; use a store package and set a default store inLinkedStorage(for example@_linked/rdf-mem-store).
For local in-memory setup, register @_linked/rdf-mem-store as the default store:
import {LinkedStorage} from '@_linked/core';
import {InMemoryStore} from '@_linked/rdf-mem-store';
LinkedStorage.setDefaultStore(new InMemoryStore());- Add
setOffsettolinkedSetComponentquery controller. - Make loader configurable and/or switch to passing a loading-state prop.
npm run build
npm testSee CHANGELOG.md.