DEV Community

Cover image for Solid's Resource is great!
Daniel Macák
Daniel Macák

Posted on

Solid's Resource is great!

When you say Solid, Signal usually comes to mind, but there are many more great APIs there. One of them is Resource:

const fetchPosts = () => 
  fetch(`https://jsonplaceholder.typicode.com/posts`)
    .then(r => r.json());
};

const App = () => {
  const [posts] = createResource(fetchPosts);

  return <span>Number of posts: {posts()?.length}</span>;
}
Enter fullscreen mode Exit fullscreen mode

As you can see, Resource basically does 2 things:

  • Manages an async operation, like a fetch
  • Binds the result of that operation to Solid's reactive model

Therefore, it makes an async operation appear synchronous. See? I haven't used await anywhere in the App component.

You can also connect the Resource to a Signal. Every time the latter changes, Resource performs the async operation again.

const fetchPost = async (id) => (
    await fetch(`https://jsonplaceholder.typicode.com/posts/${id}/`)
  ).json();

const App = () => {
  const [postId, setPostId] = createSignal();
  const [post] = createResource(postId, fetchPost);

  function onOpenPost(id) {
    setPostId(id);
  }
Enter fullscreen mode Exit fullscreen mode

Pretty neat. However, sometimes you need a bit more control over the encapsulated data, and that's where refetch and mutate come in.

const App = () => {
  const [postId, setPostId] = createSignal();

  const [post, { mutate }] = createResource(postId, fetchPost);
  const [posts, { refetch }] = createResource(fetchPosts);

  async function onCreatePost() {
    const newPost = await createPostInDB();
    refetch();
  }

  async function updatePost(updatedPost) {
    await updatePostInDB(updatedPost);
    mutate(oldPost => ({ ...oldPost, ...updatedPost });
  }
Enter fullscreen mode Exit fullscreen mode

As their names tell, refetch makes the Resource, in our case, call the backend API again. This can be handy anytime it's more consistent to get fresh new data from the backend rather than modify it in place on the frontend.
mutate on the other hand lets you modify the data in place without calling backend API. The one thing I'd love and I am missing at the moment would be to have the same capabilities to mutate the Resource as with Solid Stores as that would make the mutations much easier to perform.

To wrap the nice features up, there are loading and error properties on the Signal produced by Resource which make it super easy to track the fetch's status:

const App = () => {
  const [postId, setPostId] = createSignal();
  const [post] = createResource(postId, fetchPost);

  return <span>{post.loading && 'Loading...'}</span>
Enter fullscreen mode Exit fullscreen mode

There are more interesting Resource options and properties like state which you can find in the docs.

Alternatives

When I was listing all those advantages of Solid's Resource, I bet many of you thought Tanstack Query. And they are indeed similar in many ways, although the latter is much more powerful. That's why it's so nice there is an official distribution for Solid as well!

yarn add @tanstack/solid-query
Enter fullscreen mode Exit fullscreen mode

I'd say use the built-in Resource as long as you can and only when you need to manage your queries in a more complex fashion switch to Solid Query.

There was some talk of Solid + MobX in the past but to me, in case of async resources, I still prefer Solid Resource since it encapsulates both the fetch and the data in one easy to manipulate object.

Architecture

That's all nice, you might say, but having those Resources in components is not great, I'd like to extract them outside to make them more testable and maintainable. Well, there is nothing stopping you form doing exactly that!

I like to put them outside components like this:
post-resource.ts

const [postId, setPostId] = createSignal<number>();
const postResource = createResource(postId, api.fetchPost);

export const getPost = (id: number) => {
  setPostId(id);
  return postResource[0];
};

export const updatePost = async (
  postPatch: Partial<Post>
) => {
  const success = await api.updatePost(postPatch);

  if (success) {
    const { mutate } = postResource[1];
    mutate(p => ({ ...p!, ...postPatch }));
  }
}
Enter fullscreen mode Exit fullscreen mode

And then in the component I just use those exposed functions:

const PostDetail = () => {
  const postId= +params.id;
  const post = getPost(postId);
  ...
}
Enter fullscreen mode Exit fullscreen mode

That means you can build an entire frontend architecture around the Resources! No need to sync API calls with Redux or other complicated solutions, you can just use the Resource and expose as much or little as you want.

One benefit of extracting the Resources from components is that they can then be easily tested on their own. The only thing you should be careful about is not to forget createRoot. It sets up reactive context inside which updates to reactive state are detected. This context normally gets set up when calling render, but we are not rendering anything when testing just Resources. Therefore, you should wrap the part that creates Resources in createRoot.

test('resource fetches from api', async () => {
  const { postResource } = setup();
  ...
})

function setup() {
  return createRoot(() => initResources(testApi));
}
Enter fullscreen mode Exit fullscreen mode

If you are wondering what the setup function is about, check out my article 5 steps to better unit/integration testing.

That's it folks

How do you like Solid Resource so far, and do you use it yourself? Which other architecture do you use in your Solid apps? I'd love to know!

Top comments (0)