TinyBase logoTinyBase β

Using React Hooks

There are reactive hooks in the ui-react module for accessing every part of a Store, as well as more advanced things like the Metrics and Indexes objects.

By reactive hooks, we mean that the hook not only fetches part of the Store, but that it also registers a listener that will then cause a component to re-render if the underlying value changes. Therefore, it's easy to describe a user interface in terms of raw data in a Store, and know that it will stay updated when the data changes.

To start with a simple example, we use the useCell hook in a component called App to get the value of a Cell and render it in a <span> element. When the Cell is updated, so is the HTML.

import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useCell} from 'tinybase/ui-react';

const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const App = () => <span>{useCell('pets', 'fido', 'color', store)}</span>;

const app = document.createElement('div');
const root = createRoot(app);
root.render(<App />);
console.log(app.innerHTML);
// -> '<span>brown</span>'

store.setCell('pets', 'fido', 'color', 'walnut');
console.log(app.innerHTML);
// -> '<span>walnut</span>'

There are hooks that correspond to each of the Store getter methods:

And for tabular data:

They have the same return types. For example, the useTable hook returns an object:

import {useTable} from 'tinybase/ui-react';

const App2 = () => <span>{JSON.stringify(useTable('pets', store))}</span>;
root.render(<App2 />);
console.log(app.innerHTML);
// -> '<span>{"fido":{"color":"walnut"}}</span>'

store.setCell('pets', 'fido', 'species', 'dog');
console.log(app.innerHTML);
// -> '<span>{"fido":{"color":"walnut","species":"dog"}}</span>'

When the component is unmounted, the listener will be automatically removed. This means you can use these hooks without having to worry too much about the lifecycle of how your component interacts with the Store.

Using Hooks To Set Data

In an interactive application, you don't just want to read data. You also want to be able to set it in response to user's actions. For this purpose, there is a group of hooks that return callbacks for setting data based on events.

Let's start with a simple example, the useSetCellCallback hook. The Cell to be updated needs to be identified by the Table, Row, and Cell Id parameters. The fourth parameter to the hook is a parameterized callback (that will be memoized based on the dependencies in the fifth parameter). The responsibility of that function is to return the value that will be used to update the Cell.

It's probably easier to understand with an example:

import {useSetCellCallback} from 'tinybase/ui-react';

const App3 = () => {
  const handleClick = useSetCellCallback(
    'pets',
    'fido',
    'sold',
    (event) => event.bubbles,
    [],
    store,
  );
  return (
    <span>
      Sold: {useCell('pets', 'fido', 'sold', store) ? 'yes' : 'no'}
      <br />
      <button onClick={handleClick}>Sell</button>
    </span>
  );
};
root.render(<App3 />);
console.log(app.innerHTML);
// -> '<span>Sold: no<br><button>Sell</button></span>'

const button = app.querySelector('button');
// User clicks the <button> element:
// -> button MouseEvent('click', {bubbles: true})

console.log(store.getTables());
// -> {pets: {fido: {color: 'walnut', species: 'dog', sold: true}}}
console.log(app.innerHTML);
// -> '<span>Sold: yes<br><button>Sell</button></span>'

In the real-world, a more valid case for using the event parameter might be to handle the content of a text input to write into the Store. See the Todo demo for a working example of doing that with the useAddRowCallback hook to add new todos.

State Hooks: useState-Style Convenience

For common scenarios where you need both read and write access to data, TinyBase provides a family of "state hooks" that follow React's useState pattern. These hooks return a tuple containing the current value and a setter function:

import {useCellState} from 'tinybase/ui-react';

const ColorSetter = () => {
  const [color, setColor] = useCellState('pets', 'fido', 'color', store);
  return (
    <div>
      <div>Color: {color}</div>
      <button onClick={() => setColor('black')}>Change Color</button>
    </div>
  );
};

These state hooks combine the functionality of getter hooks (like the useRow hook) with setter callback hooks (like the useSetRowCallback hook), reducing boilerplate and providing a familiar API for React developers.

The following state hooks are available:

These hooks are particularly useful when building forms, editors, or any UI that requires bidirectional data binding. They handle all the memoization and re-rendering logic for you, just like the standard hooks.

Other Hook Types

The hooks to read and write Store data (described above) will be the ones you most commonly use. For completeness, there are three other broad groups of hooks. Firstly, there are those that create callbacks to delete data (such as the useDelRowCallback hook), which should be self-explanatory.

Then there are hooks that are used to create objects (including Store objects, but also Metrics, and Indexes objects, and so on). These are essentially convenient aliases for memoization so that object creation can be performed inside a component without fear of creating a new instance per render:

import {useCreateStore} from 'tinybase/ui-react';

const App5 = () => {
  const store = useCreateStore(() => {
    console.log('Store created');
    return createStore().setTables({pets: {fido: {species: 'dog'}}});
  });
  return <span>{store.getCell('pets', 'fido', 'species')}</span>;
};

root.render(<App5 />);
// -> 'Store created'

root.render(<App5 />);
// No second Store creation

There is also a final group of hooks that add listeners (such as the useCellListener hook). Since the regular hooks (like the useCell hook) already register listeners to track changes, you won't often need to use these unless you need to establish a listener in a component that has some other side-effect, such as mutating data to enforce a schema, for example.

Summary

The hooks available in the ui-react module make it easy to connect your user interface to TinyBase Store data. It also contains some convenient components that you can use to build your user interface more declaratively. For that, let's proceed to the Using React Components guide.