travis vachon

writing the web with solid

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"/>
  </>
)
a lazy component will slouch into place here at runtime

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>
  </>
)
a lazy component will slouch into place here at runtime

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/>
)
a lazy component will slouch into place here at runtime

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>
)
a lazy component will slouch into place here at runtime

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>);
}
a lazy component will slouch into place here at runtime

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>
  );
}
a lazy component will slouch into place here at runtime

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>
  );
}
a lazy component will slouch into place here at runtime

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>
    </>
  );
}
a lazy component will slouch into place here at runtime

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>
)
a lazy component will slouch into place here at runtime

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>
)
a lazy component will slouch into place here at runtime

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.