note: the examples in this post are no longer live, and the data access patterns no longer recommended. for updates on Solid development, check out my work at Understory
If you're following along, you know that @solid/react comes with some useful components for displaying information stored in a Solid pod. With just a little more work, we'll see how easy it is to write data to your pod and get started publishing data to the Semantic Web.
First, you'll need to get a Pod. I've created mine on the Inrupt pod server because I think they'll do a good job - Inrupt is the company started by Sir Tim Berners-Lee and others to push Solid out of the research phase and into the market so they have lots of expertise and skin in the game. You can create your Inrupt Pod here.
Once you have a Pod, you can log in and authorize this website to read and write data to it:
import React from 'react';
import { LoggedOut, LoggedIn, AuthButton } from '@solid/react';
export default () => (
<>
<LoggedOut>
<p>You are not logged in.</p>
</LoggedOut>
<LoggedIn>
<p>Congratulations, you're logged in!</p>
</LoggedIn>
<AuthButton popup="/popup.html" login="log in for magic!" logout="log me outta here"/>
</>
)
Now that you're logged in,
@solid/query-ldflex
will
alias user
to your WebID to make it easy to display information
about yourself:
import React from 'react';
import { LoginButton, LoggedIn, LoggedOut, Value, Link } from '@solid/react';
export default () => (
<>
<LoggedOut>
<LoginButton popup="popup.html">log in to see your info</LoginButton>
</LoggedOut>
<LoggedIn>
<p>welcome back, <Value src="user.name"/></p>
<p>your WebID is <Value src="user"/></p>
<ul>
<li><Link href="user">your Pod profile</Link></li>
<li><Link href="user.inbox">your Pod inbox</Link></li>
</ul>
</LoggedIn>
</>
)
You're also now able to write to your pod - here's a simple component that lets you "like" this page:
import React from 'react';
import { Like } from '@solid/react';
export default () => (
<Like/>
)
If you reload this page you'll see the state was saved. You can also easily follow users:
import React from 'react';
import { Follow } from '@solid/react';
export default () => (
<Follow object="https://tvachon.inrupt.net/profile/card#me">Travis</Follow>
)
For now this doesn't do much, but it's stored in a well known location so that Solid application developers can use this information to make their apps more social.
reading is essential
The components provided by @solid/react
are convenient for simple
use cases, but pretty soon you'll want to write custom components that
read and write data to your Solid Pod. Fortunately, @solid/react
provides some handy React
hooks that make this easy:
import React from 'react';
import { useLDflexValue } from '@solid/react';
export default () => {
const name = useLDflexValue('user.name');
return (<marquee>{name && name.toString()}</marquee>);
}
Notice that name
is not a string - it's a special JavaScript
Proxy
object that LDflex uses to make it feel like the whole Semantic Web is
at your fingertips.
Of course there's also a hook for lists:
import React from 'react';
import { useLDflexList } from '@solid/react';
export default () => {
const friends = useLDflexList('[https://ruben.verborgh.org/profile/#me].friends.firstName');
return (
<ul style={{maxHeight: "5em", overflow: "scroll"}}>
{friends.map((friend, i) => (
<li key={i}>{friend.toString()}</li>
))}
</ul>
);
}
And one that gives you more data about the status of the request:
import React from 'react';
import { useLDflex } from '@solid/react';
export default () => {
const [name, pending, error] = useLDflex('user.name', false);
return (
<div>
<p>{name && name.toString()}</p>
<p>{pending ? "loading" : "finished loading"}</p>
<p>errors? {error && error.toString()}</p>
</div>
);
}
write whatever you want
It would be nice if you could use the objects returned by useLDflex
hooks to mutate the item at the given path, but in February 2020
you'll need to get a handle on the corresponding item using
@solid/query-ldflex
:
import React, {useState, useEffect} from 'react';
import { useLDflexValue } from '@solid/react';
import data from '@solid/query-ldflex';
export default () => {
const savedName = useLDflexValue('user.name') || "";
const [name, setName] = useState(savedName);
useEffect(() => {savedName && setName(savedName)}, [savedName])
const saveName = async () => {
await data.user.name.set(name);
}
return (
<>
<marquee>{savedName && savedName.toString()}</marquee>
<input type="text" value={name} onChange={e => setName(e.target.value)}/>
<button onClick={saveName}>set name</button>
</>
);
}
If you enter a new value for your name and hit submit, next time you
reload the page you'll see the updated value. Note the asynchronous
saveName
function that discards its result - this is because
data.user.name.set(name)
returns a Promise
, and as currently
implemented won't submit to the server until the then
method of the
Promise
is called. Using async/await
feels slightly more intuitive
than the equivalent Promise.then
syntax, but also a little more
magical.
Note that with this implementation you won't see values update in
realtime. We could use setName
to update the name
manully, but
then savedName
would still be out of sync with the actual saved
value of name
, which isn't ideal. Fortunately, @solid/react
provides a LiveUpdate
component that can be wrapped around any
components that use useLDflex
hooks to make them automatically
update whenever the corresponding server state changes. LiveUpdate
relies on the WebSockets based PubSub API built into
Solid.
import React from 'react';
import { LiveUpdate } from '@solid/react';
import SetName from './SetName';
export default () => (
<LiveUpdate><SetName/></LiveUpdate>
)
make lists, do crimes
You can, of course, also write lists of things. Maybe you want to give
yourself a cool list of aliases so people know about all the cool
things you do under different names. The
foaf:nick
property is
perfect for this:
import React, {useState} from 'react';
import { LiveUpdate, useLDflexList } from '@solid/react';
import data from '@solid/query-ldflex';
const Nicks = () => {
const savedNicks = useLDflexList('user.nick');
const [newNick, setNewNick] = useState("");
const addNick = async () => {
await data.user.nick.add(newNick);
setNewNick("");
}
const deleteNick = async (nick) => {
await data.user.nick.delete(nick);
}
return (
<>
<h3>cool nicknames:</h3>
{savedNicks.map((nick, i) => (
<div key={i}>
<span>{nick.toString()}</span>
<button onClick={() => deleteNick(nick)}>delete</button>
</div>
))}
<input type="text" value={newNick} onChange={e => setNewNick(e.target.value)}/>
<button onClick={addNick}>add nickname</button>
</>
);
}
export default () => (
<LiveUpdate><Nicks/></LiveUpdate>
)
I've included an example of how values can be deleted as well so as to
avoid polluting your Pod too much. In February 2020, add
, set
and
delete
are not well
documented but they
likely will be soon.
LDflex is expressive, query-oriented, and fairly intuitive, but as you may have noticed it's still a little rough around the edges. Lots of enhancements are planned, however, and I'm excited to follow along and contribute in the coming months.
In future posts I'll work through some alternatives to LDflex and talk about what I'm hoping to see emerge in the coming years. If you'd like to hear more, follow me on GitHub, Mastodon, or Twitter. If you're in the Bay Area, join me at the inaugural meetup of the Bay Area Solid Interest Club on February 20th, 2020 in downtown San Francisco.