Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Syncing state with DB #54

Open
oliverjam opened this issue Aug 4, 2021 · 1 comment
Open

Syncing state with DB #54

oliverjam opened this issue Aug 4, 2021 · 1 comment

Comments

@oliverjam
Copy link

Following our chat yesterday I ended up attempting to write a custom hook that would work just like normal state but also sync updates with the DB in the background. It seems to work reasonably well, and is hopefully not too confusing 😅

I think using something like this would probably be easier at this stage that trying to work in a whole new library/paradigm (i.e. React Query).

Here's how you'd use it:

function App() {
  // need to pass in two fns: to load initial state and to update it later
  const [state, setState] = useRemoteState({ load, update });

  if (state.status === "loading") return <div>Initialising...</div>;
  if (state.status === "error") return <div>Something went wrong!</div>;

  const count = state.data.count;
  return (
    <main>
      <p>{count}</p>
      <button
        onClick={() =>
          // simple update:
          // setState({ count: count + 1 })

          // or pass a fn to update based on previous state (just like normal React state):
          setState((prev) => {
            return { count: prev.count + 1 };
          })
        }

        // updates are blocked while previous one is pending
        // but for good UX indicate to the user that button won't work
        disabled={state.status === "updating"}
      >
        Click me {state.status === "updating" ? "..." : "+"}
      </button>
    </main>
  );
}

function load() {
  // TODO: actually fetch data from DB
  // has to return a promise resolving with the initial data
  // e.g. supabase.table("thing").select("blah");
}

function update(changedData) {
  // TODO: update the right bit of the DB using the `changedData` object
  // just has to return a promise (resolved value isn't used)
}

I'm gonna describe exactly how it works in detail, which makes it seem super complicated 😅. But really the idea is you use it pretty much like useState({}), and just don't worry about all the DB syncing happening in the background.

Basically you give the hook two functions that return promises: load and update. It will call load and use the eventual result as the initial data (state.data).

It returns an array of two things, just like useState. The first is a state object like this:

const state = {
  status: "loading" || "updating" || "success" || "fail",
  data: null || { your: "data returned from load" },
  error: null || new Error(),
};

and the second is an updater function. This is a little more convenient than useState's, because it will shallow-merge updates. E.g. if your data is:
{ count: 0, other: "thing" } then calling setState({ count: 5 }) will result in { count: 5, other: "thing" }.

Calling setState immediately updates the local state (so the user sees the changes instantly), but also calls the update function you passed into the hook and waits for the promise to resolve. This is known as an "optimistic update".

While this is happening state.status will be "updating", so you can show an indicator to the user that data is syncing in the background. While updating the hook will prevent further state updates (to avoid e.g. setState({ count: 1 }) taking longer than setState({ count: 2}) and ending up showing 2 locally but having 1 in the DB).

@oliverjam
Copy link
Author

I put the actual hook in a gist so it's easier to read:

https://gist.github.com/oliverjam/baf0a137c171c3ce8fe78568d76d9ebb

Hopefully the comments help explain what exactly is going on 😅

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant