DEV Community

Cover image for React Query in Web Extensions
Marcin
Marcin

Posted on • Updated on • Originally published at marcin.codes

React Query in Web Extensions

React Query is a library for fetching data from API. Is performant, manages fetched data, and what is the most important is backend agnostic — you can use it with REST or GraphQL APIs. But what about where you didn’t have API at all. Did you ever wonder “Can We use react-query when I don’t have API?”.


Alt Text

Did you hear about Bookmarkly? I’m working on a bookmark manager, which helps you organize your bookmarks. ML models help you boost your productivity. It’s totally free! You can find more on https://bookmarkly.app


TL;DR

yes, we can - brooklyn 9-9 gif

But, how is that possible?

As I mention above react query is backend agnostic. It’s mean it’s not worrying about how we obtain data from the backend. We just need a key that represents our data and the Promise that after resolving fetches our data.
In this example, we are using https://github.com/mozilla/webextension-polyfill to add cross-browser support.

import browser from 'webextension-polyfill';

export async function getSites() {
  const { sites } = await browser.storage.local.get(['sites']);

  return sites || [{ id: 1, name: 'sample website' }];
}
Enter fullscreen mode Exit fullscreen mode

In react component, we can use react’s fetch-as-render experimental functionality, that’s why we don’t bother catching errors in the component.

import { getSites } from './queries';

export const Sites = () => {
  const { data } = useQuery('sites', getSites);

  return (
    <div>
     {data.map(site => (
       <span key={site.id}>{site.name}</span>
     )}
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

To catch errors we are using error boundaries. We can create a sample component like this:

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) { 
   return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    // You can also log the error to an error
    // reporting service logErrorToMyService(error, errorInfo);
  }

  render() {
    if (this.state.hasError) { 
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children; 
  }
}

export ErrorBoundary;
Enter fullscreen mode Exit fullscreen mode

Everything is composed like that:

import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { ReactQueryConfigProvider } from 'react-query';
import { Sites } from './sites';
import { ErrorBoundary } from './error-boundry';

const queryConfig = {
  suspense: true,
};

async function main(): Promise<Event> {
  return new Promise((resolve) => window.addEventListener('DOMContentLoaded', resolve));
}

function render() {
  ReactDOM.render(
    <ReactQueryConfigProvider config={queryConfig}>
      <ErrorBoundary>
        <Sites />
      </ErrorBoundary>
    </ReactQueryConfigProvider>,
    document.getElementById('popup'),
  );
}

main()
  .then(render)
  .catch(reportError);
Enter fullscreen mode Exit fullscreen mode

Summary

As you see, it’s easy to use react query in web extension. React Query has a fantastic developer experience. That’s why I’m using it in Bookmarkly.

Top comments (6)

Collapse
 
ivan_jrmc profile image
Ivan Jeremic • Edited

I think I do a similar extension, I work on it for the last 5 months, also the same stack as to, I use ReactQuery and React. One question why class components?

Collapse
 
marcin_codes profile image
Marcin

I use React Query with experimental functionality reactjs.org/docs/concurrent-mode-s.... To make it work correctly we need to set up error boundaries. They are only available with class syntax.
Although using class components itself isn't bad at all.

I'm really curious, do you mind share what are you working on?

Collapse
 
ivan_jrmc profile image
Ivan Jeremic • Edited

Very cool. Sorry for now it is closed source because I have some unique features which I want to keep closed until I publish the extension. How about yours can I try it?

Thread Thread
 
marcin_codes profile image
Marcin • Edited

Not yet, I'm really close to the release date but there is a lot of things to do before that. I will let you know when it will be ready :)

Thread Thread
 
ivan_jrmc profile image
Ivan Jeremic

Thanks, did you start with a react browser extension boilerplate or a totally custom webpack react project?

Thread Thread
 
marcin_codes profile image
Marcin

I used some "react chrome extension boilerplate" but I can't find it now. But all things were done using web webpack + react + typescript. When I went through coding I find it's useful to separate UI so it's easily replaceable.

Nonetheless, I made some PoC chrome extension using snowpack + preact and it is really cool to play with. You can find it here if you want to take a look: github.com/ZBW-dev/pullrequests-te...