Skip to content
Thom van Kalkeren edited this page Jul 7, 2020 · 32 revisions

Reference

Welcome to the link(-redux) reference, this document should provide an overview of how to achieve most common tasks while developing your application.

Live projects

Code examples

Development resources

Further reference

TOC

  1. Introduction
  2. Preparations
  3. IRI's
  4. Accessing the store
  5. Retrieving server data
  6. Querying local data
  7. Retrieving contextual information
  8. Rendering resources
  9. Rendering properties
  10. Rendering data types
  11. Actions, middleware and state changes
  12. Inheritance and reasoning
  13. Devtools

Introduction

Link provides the functionality to build a fully-featured linked-data browser, it's also possible to just mix-and-match the functionality you need for your specific application.

Most tasks (e.g. data fetching) can be done in a number of ways, each with their own considerations. Link generally provides both imperative and declarative ways to attain the desired result. The imperative tools usually provide quick and easy results with a lot of flexibility, while the declarative tools provide a stable base for scaling and adapting to a variety of data sources. Use the latter if you don't know which one to use.

Most applications will use both to achieve the best balance between performance, readability, scalability and maintainability.

Note: Link-redux has been through a lot of changes since its inception, the name implies the use of the popular Redux framework, while this was true in the past, link-redux has dropped its dependency and provides it's own middleware which is better adopted for processing linked data.

On this documentation

We're still actively working on improving core design, so if a feature mentioned here isn't present on your machine, try checking out the @next version of the packages (@ontologies/core, link-lib, link-redux, etc).

Whenever the rdf symbol is used, it'll refer to the default export from @ontologies/core, which is a factory for creating RDF data.

Preparations

Use createStore to initialize a store to hold all data, and pass it to the RenderStoreProvider to provide your child components access;

const lrs = createStore()

ReactDOM.render(
  (
    <RenderStoreProvider value={lrs}>
      // The rest of your app
    </RenderStoreProvider>
  )
  document.getElementById('app')
)

Pick up the link devtools for an easier experience as well.

IRI's

Linked data uses URL's for resources and properties. To make life easier, we have published several of the most popular ontologies on npm ready to be used with link. We also extract documentation for you so your editor can help you build applications.

// Prepublished ontologies
import schema from '@ontologies/schema';
import rdfx from '@ontologies/rdf';
import owl from '@ontologies/owl';

console.log(schema.name) // http://schema.org/name
console.log(rdfx.label) // http://www.w3.org/1999/02/22-rdf-syntax-ns#label
console.log(owl.sameAs) // http://www.w3.org/2002/07/owl#sameAs
// You can generate custom IRIs as well
console.log(schema.ns('myLabel')) // http://schema.org/myLabel

Note: the examples use the app ontology as a placeholder for an application-specific ontology.

Literals

The second type of data in RDF is the literal, which allows for quick expression of (arbitrary) data without needing to assign an IRI to it.

Link and the @ontologies/<ontology> packages use a common factory (what is a factory?) to create linked data objects. This factory is provided via the @ontologies/core package, it's advised to check its documentation to learn how to create IRI's and literals for yourself.

Accessing the store

Retrieve the store in your component;

Hooks

Updates auto

const MyComponent = () => {
  const lrs = useLRS()
}

HOC

Updates auto

const MyComponent = (props) => {
  const lrs = props.lrs
}

const EnhancedComponent = withLRS(MyComponent)

Retrieving server data

Declarative

Fetching auto Updates auto

Mounting a Resource component will retrieve the resource passed as subject when it's not in the store yet.

<Resource subject={app.ns('person/5')} />

It's always possible to create a component which wraps an API;

const RestApiPerson = ({ id }) => (
  <Resource 
    subject={rdf.namedNode(`http://example.com/person/${id}`)} 
  />
)

// And use it in your app
<RestApiPerson id={friends[0].id} />

This is especially useful for API's which don't publish their data properly, i.e. 'RESTful API's' where an IRI has to be transformed before it can be fetched (x.com/api/v1/persons/5?type=linkedData while the IRI is just x.com/persons/5).

Store

The simplest method to fetch data from a server is via getEntity, which returns a promise which will resolve once the resource has been fetched. The promise has no value, rather your component should re-render when it finishes loading.

await lrs.getEntity(app.ns('person/5'))

Fetching yes Updates na

Use queueEntity to let the store know it should be fetched in the near future. It is considerably more performant than getEntity since it doesn't create a promise and allows the store to batch data requests more efficiently.

lrs.queueEnitity(app.ns('person/5'))

Querying local data

Hooks

These are still in development, check out the source for the most current information.

Fetching no Updates auto

Retrieve a map of properties from the current subject.

const data = useLink({
  name: schema.name,
  bio: schema.desription,
});
console.log(data.name); // "Bob" 
console.log(data.bio);  // "My description"

Fetching no Updates auto

Retrieve a map of properties from the given subject.

const subjects = [app.ns('person/5'), app.ns('person/6'), app.ns('person/7')]
const propMap = {
  name: schema.name,
  comments: {
    label: foaf.comments,
    limit: 50,
  },
};
// Optional
const opts = {
  returnType: ReturnType.Value
}
const [map1, map2, ...] = useResourceLinks(subjects, propMap, opts)

Fetching no Updates auto

Retrieve the values of a property of the current subject

const [value1, value2, ...] = useProperty(schema.name);

Fetching no Updates auto

Retrieve the values of a property of the given subject

const [value1, value2, ...] = useResourceProperty(app.ns('person/5'), schema.name);

HOC

Fetching no Updates auto

const MyComponent = (props) => {
  return (
    <div>
      <h1>{props.name.value}</h1>
      <p>{props.description.value}</p>
    </div>
  )
}

const EnhancedComponent = link({
  name: schema.name, 
  description: schema.description,
})(MyComponent)

mapDataToProps

Fetching no Updates auto

If you pass your component to register, you can skip the link HOC and use the mapDataToProps static property, which does the call for you.

const Comp = ({ name }) => (
  <div>{name.value}</div>
)
Comp.type = schema.Thing
Comp.mapDataToProps = {
  name: schema.name,
}
export default register(Comp)

Store

Fetching no Updates no

Use tryEntity to retrieve actual data from the store. An empty array is returned when no data was found.

const data = lrs.tryEntity(app('person/5'))

Fetching no Updates no

Retrieve a single value for a property of a subject from the store.

const property = lrs.getResourceProperty(app('person/5'), schema.name)

Fetching no Updates no

Retrieve all values for a property of a subject from the store.

const [prop1, prop2, ...] = lrs.getResourceProperties(app('person/5'), schema.name)

Fetching no Updates no

Resolve the values at the end of a property path, stops on dead ends.

const [prop1, prop2, ...] = lrs.dig(
    app('blog/5'),
    [schema.comments, schema.author, schema.name]
)
// prop1 = 'Alice'
// prop2 = 'Bob'
// prop2 = 'Charlie'

Fetching no Updates no

Resolve the subjects which have a matching value at the end of a property path, stops on dead ends.

const [prop1, prop2, ...] = lrs.findSubject(
    app('blog/5'), 
    [schema.comments, schema.author, schema.name], 
    'Bob'
)
// prop1 = app('person/6')
// prop2 = app('person/436')

Retrieving contextual information

Hooks

Fetching na Updates auto

const Comp () => {
    const { subject, topology } = useLinkRenderContext()

    return (
        <p>Current subject: {subject.value} in topology {topology.value}</p>
    )
}

Rendering resources

Declarative

When mounting a Resource component, the resource will be fetched and if an accompanying view can be found that will be rendered.

<Resource subject={resource} />

You can pass a view implementation as a child when you only need to fetch the resource.

<Resource subject={resource}>
  <Property label={schema.name} />
</Resource>

Rendering properties

Declarative

Fetching auto Updates depends

Mounting the Property component will render the best fitting view if present.

<Property label={schema.name} />

If the property is another resource and no view can be found, a Resource component will be mounted automatically rendering the containing resource. If it's a value (e.g. string, number, dollars, centimetres) and no view can be found the plain value will be displayed.

<Property label={schema.name} /> // Will render e.g. "Bob's great adventure"
<Property label={schema.author} /> // Will render the author mentioned in the property

In the example above, the author is a direct association on the surrounding resource. But oftentimes such associations are one-apart. When passing children to Property it will render those, but if the property is an association it will set that context first. This can be made use of to quickly render nested associations.

<Property label={schema.name} /> // Will render the name of the book
<Property label={schema.author}> // Will render its children if no property renderer for schema:author can be found
    <Property label={schema.name} /> // Will render the name of the author
    <Property label={schema.name}>
    </Property>
</Property>

If you need a (child resource) property within the context of the current view, you can use a render function to receive it without handing off control to a child component.

<Property label={app.comments}> // Some ActivityStreams Collection
    <Type /> // Will render view of the current subject, as if no children were passed here
    <Property label={as.totalItems}>
        // The render function will recieve an array of all values for the given predicate
        // We only use the first one here
        {([count]) => {
            <Link text={`Show all ${count.value} items`} />
        }}
    </Property>
</Property>

Rendering the associated resource of a property is a good default in almost any case, but rendering a value as-is might not. See the rendering data types section for more info on how to customize the rendering behaviour.

Even if a resource has a property multiple times, it will render only one view by default. If you want to render the view multiple times, pass a custom limit.

// Will render the alternate name property at most 10 times
<Property label={schema.alternateName} limit={10} />

Using limit will render the entire view multiple times. If you need more than one value for a single view, you can request that in your view.

const CommentsRenderer = ({ linkedProp }) => (
  <React.Fragment>
    {linkedProp.map((name) => <p>{name.value}</p>)}
  </React.Fragment>
);
CommentsRenderer.property = schema.Person
CommentsRenderer.property = schema.alternateName
CommentsRenderer.renderOpts = {
  limit: 10,
}

Rendering data types

RDF has some built-in primitive datatypes, including most xsd types (e.g. xsd:string, xsd:boolean, xsd:dateTime) and the langstring type from RDFS.

In addition, RDF allows you to define your own (semantic) datatypes. They are generally used for quantitative values (e.g. expressing money like dollars, distance like centimetre, or volume like gallons) but also for more complex types (e.g. markdown formatted strings, DNA codon).

Datatypes can be seen as RDF resources without a predetermined IRI, but as an IRI for a value space and its value as a point within that space.

Link supports rendering data types. When the Property component terminates at a value (no property renderer could be found), then it will look if a 'datatype renderer' exist for the value's datatype.

const DollarRenderer = ({ linkedProp }) => (
  <span>
    ${Number(linkedProp.value).toFixed(2)}
  </span>
) // E.g. "2.5" => "$2.50"

// Mark this view as a datatype renderer
DollarRenderer.type = rdfs.Literal

// Let link know which data type it can render (leave blank to act as a default).
DollarRenderer.property = rdf.namedNode("http://dbpedia.org/datatype/usDollar")

// Optional, use this renderer only for certain topology.
DollarRenderer.property = app.myTopology;

Actions, middleware and state changes

TODO

Inheritance and reasoning

Adding ontological data for view determination

If an ontology is loaded like any other resource it can be rendered, but it will not have any effect on how the engines view determination. If you want to add that information you have to use addOntologySchematics on the LinkedRenderStore. The statements added with addOntologySchematics will also be added to the main data store so they can be used by the views.

lrs.addOntologySchematics([
    rdf.quad(s, p, o,g )
 ])

The primary reason why these are separate is to prevent changing application behaviour when just browsing resources, possibly breaking the application when a server sends bad data.

Devtools

It is strongly advised to use the experimental react devtools for chrome or firefox which has support for React hooks and other newer features (yes, the stable version doesn't have that yet at the time of writing...).

We also have some devtools internally which are planned to be published to npm, hit us up for them in the mean time.