1771 Technologies Logo

Introduction

Graphite Grid Reactivity

Modern front-end libraries like React rely on reactive programming. Graphite Grid leverages this model to manage its state. By integrating with React's standard rendering system, Graphite Grid can share these reactive primitives with other components in your web application.

Reacting to Grid Changes

To set up Graphite Grid, you must use the useGraphiteGrid function, which returns a state object reflecting the current grid state. This state object is a stable reference and is safe to use in effects and as a dependency in hooks like useEffect. It provides a real-time view of the data within the grid.

The state object includes an api property that refers to the Graphite Grid API attached to it. Components responding to specific grid state changes can use api.useApiSlice to re-render.

import { useGraphiteGrid, GraphiteGridDom } from "@1771technologies/graphite-grid-react";
 
function MyGrid() {
  const rowData = [
    // Row data here
  ];
 
  const columnDefinitions = [
    // Column definition here
  ];
 
  // Our grid component
  export function MyGrid() {
    const state = useGraphiteGrid({
      initial: {
        columnDefinitions,
        rowDataSource: {
          kind: "client",
          data: rowData,
        },
      },
    });
 
    return <GraphiteGridDom state={state} />;
  }
}

While the example above is contained within a single component, Graphite Grid supports the typical React approach of sharing state by moving it to a parent component and then passing it down as props. Therefore, the state graph of an application that includes Graphite Grid would appear as follows:

useApiSlice

Passing the Graphite Grid state object to different components will not trigger a render. The state reference is stable and does not change. Instead, the useApiSlice function can listen to state changes reactively.

Displayed Columns:

  • Alpha
  • Beta
  • Zeta
import type { GraphiteGridState } from "@1771technologies/graphite-grid-react";
import { GraphiteGridDom, useGraphiteGrid } from "@1771technologies/graphite-grid-react";
 
const columnSetA = [{ id: "Alpha" }, { id: "Beta" }, { id: "Zeta" }];
const columnSetB = [{ id: "Bravo" }, { id: "Echo" }, { id: "Charlie" }];
 
export function GridReactivity() {
  const state = useGraphiteGrid({
    initial: {
      columnDefinitions: columnSetA,
      rowDataSource: {
        kind: "client",
        data: [{ Alpha: 1, Beta: 2, Zeta: 3, Bravo: 4, Echo: 5, Charlie: 6 }],
      },
    },
  });
 
  return (
    <div className="flex flex-col p-2 gap-4">
      <div>
        <button
          variant="success"
          onClick={() =>
            state.api.setColumnDefinitionsExn(
              state.api.getColumnDefinitions()[0] === columnSetA[0] ? columnSetB : columnSetA,
            )
          }
        >
          Swap Random Column
        </button>
      </div>
      <GridChild state={state} />
      <ChildUsingState state={state} />
    </div>
  );
}
 
function GridChild<T>(props: { state: GraphiteGridState<T> }) {
  return (
    <div height={82}>
      <GraphiteGridDom state={props.state} />
    </div>
  );
}
 
function ChildUsingState<T>({ state }: { state: GraphiteGridState<T> }) {
  const api = state.api;
  const columns = api.useApiSlice((s) => s.getColumnDefinitions());
 
  return (
    <div className="flex flex-col gap-1">
      <h4 className="uppercase font-bold">Displayed Columns: </h4>
      <ul className="my-0">
        {columns.map((c) => (
          <li key={c.id}>{c.id}</li>
        ))}
      </ul>
    </div>
  );
}
  1. Start by creating the Graphite Grid state object
  2. Add an onclick event handler to update the current column definitions of the grid to a new set of column definitions
  3. Share the grid state with any components that need to use the state
  4. Use useApiSlice to select the state that will re-render when it changes declaratively

Danger

useApiSlice is a hook and, as such, must follow the rules of hooks for React.

useApiSlice expects a selector function as its first argument. The value returned from this selector should be stable. Hence, the following will not work:

api.useApiSlice((s) => [...s.getColumnDefinitions()]);

Every time the selector is run, a new array is called, resulting in re-render and potentially an infinite loop. As a second argument to useApiSlice, you may pass an equalityFunc where more fine-grained equality is required.