---
title: "State"
framework: react
version: "2.1.2"
---

# State

Studio state allows reports to be saved and restored. Reports can be created in edit mode, saved down as state, and then reloaded in view mode.

## Saving and Restoring State

#### Saving and Restoring State

```tsx
"use client";

import type {
  AgDataEngine,
  AgDataSourcesDefinition,
  AgReportState,
  AgStudioPreDestroyedEvent,
  AgStudioStateUpdatedEvent,
} from "ag-studio";
import { AgStudioApiReadyEvent } from "ag-studio";
import type { AgStudioRef } from "ag-studio-react";
import { AgStudio } from "ag-studio-react";
import React, {
  StrictMode,
  useCallback,
  useEffect,
  useMemo,
  useRef,
  useState,
} from "react";
import { createRoot } from "react-dom/client";

const EMPTY_STATE: AgReportState = {
  pages: [
    {
      id: "page-1",
    },
  ],
  selectedPageId: "page-1",
};

const HARDCODED_STATE: AgReportState = {
  pages: [
    {
      id: "page-1",
      widgets: {
        "1": {
          type: "grid",
          dataMapping: {
            cols: [
              { id: "medals.country" },
              { id: "medals.sport" },
              { id: "medals.gold", aggregation: "sum" },
              { id: "medals.silver", aggregation: "sum" },
              { id: "medals.bronze", aggregation: "sum" },
              { id: "medals.total", aggregation: "sum" },
            ],
          },
        },
        "2": {
          type: "column-chart-grouped",
          dataMapping: {
            categoryKey: [{ id: "medals.country" }],
            valueKey: [
              { id: "medals.gold", aggregation: "sum" },
              { id: "medals.silver", aggregation: "sum" },
              { id: "medals.bronze", aggregation: "sum" },
            ],
          },
        },
      },
      widgetLayout: {
        "1": {
          xTrack: 0,
          yTrack: 0,
          xSpan: 24,
          ySpan: 16,
        },
        "2": {
          xTrack: 0,
          yTrack: 16,
          xSpan: 24,
          ySpan: 16,
        },
      },
    },
  ],
  selectedPageId: "page-1",
  panels: {
    filters: {
      collapsed: true,
    },
  },
};

const StudioExample = () => {
  const studioRef = useRef<AgStudioRef>(null);
  const containerStyle = useMemo(() => ({ width: "100%", height: "100%" }), []);
  const studioStyle = useMemo(() => ({ height: "100%", width: "100%" }), []);
  const [data, setData] = useState<AgDataSourcesDefinition | AgDataEngine>();
  const [initialState, setInitialState] =
    useState<AgReportState>(HARDCODED_STATE);
  const [savedState, setSavedState] = useState<AgReportState>(EMPTY_STATE);
  const [studioVisible, setStudioVisible] = useState(true);

  useEffect(() => {
    fetch("https://www.ag-grid.com/studio/example-assets/olympic-winners.json")
      .then((resp) => resp.json())
      .then((data: any[]) =>
        setData({
          sources: [{ id: "medals", data }],
        }),
      );
  }, []);

  const onStateUpdated = useCallback(
    ({ state }: AgStudioStateUpdatedEvent): void => {
      console.log("State updated", state);
    },
    [],
  );

  const onStudioPreDestroyed = useCallback(
    ({ state }: AgStudioPreDestroyedEvent): void => {
      console.log("Studio state on destroy", state);
      setInitialState(state);
    },
    [],
  );

  const clearState = useCallback(() => {
    studioRef.current!.api.setState(EMPTY_STATE);
  }, []);

  const saveState = useCallback(() => {
    const state = studioRef.current!.api.getState();
    console.log("Current state", state);
    setSavedState(state);
  }, []);

  const restoreState = useCallback(() => {
    studioRef.current!.api.setState(savedState);
  }, [savedState]);

  const hardcodedState = useCallback(() => {
    studioRef.current!.api.setState(HARDCODED_STATE);
  }, []);

  const recreate = useCallback(() => {
    setStudioVisible(false);
    setTimeout(() => {
      setStudioVisible(true);
    });
  }, []);

  return (
    <div style={containerStyle}>
      <div style={{ display: "flex", flexDirection: "column", height: "100%" }}>
        <div className="example-controls">
          <div className="controls-row">
            <button onClick={clearState}>Clear State</button>
            <button onClick={saveState}>Save State</button>
            <button onClick={restoreState}>Restore State</button>
            <button onClick={hardcodedState}>Load Hardcoded State</button>
            <button onClick={recreate}>
              Recreate Studio with Current State
            </button>
          </div>
        </div>

        {studioVisible && (
          <AgStudio
            ref={studioRef}
            style={studioStyle}
            className="my-studio-container"
            data={data}
            initialState={initialState}
            mode={"edit"}
            onStateUpdated={onStateUpdated}
            onStudioPreDestroyed={onStudioPreDestroyed}
          />
        )}
      </div>
    </div>
  );
};

const root = createRoot(document.getElementById("root")!);
root.render(
  <StrictMode>
    <StudioExample />
  </StrictMode>,
);
```

[Live example: Saving and Restoring State](https://www.ag-grid.com/studio/examples/state/save-restore-state/reactFunctionalTs/)

```jsx
const initialState = useMemo(() => { 
	return {
        pages: [
            {
                id: 'page-1',
                widgets: {
                    '1': {
                        type: 'grid',
                        dataMapping: {
                            cols: [
                                { id: 'medals.country' },
                            ],
                        },
                    },
                },
                widgetLayout: {
                    '1': {
                        xTrack: 0,
                        yTrack: 0,
                        xSpan: 24,
                        ySpan: 16,
                    },
                },
            },
        ],
        selectedPageId: 'page-1',
    };
}, []);

<AgStudio initialState={initialState} />
```

State is provided to Studio on initialisation via the `initialState` property.

State can be saved and restored on demand via the API methods `getState()` and `setState()`.

Any time state changes, a `stateUpdated` event is emitted with the latest state. When Studio is destroyed, the `studioPreDestroyed` event is fired, which contains the latest state at the time.

These are all demonstrated in the above example. See the [State API](#state-api) below for more details.

> **Note**
>
> State is immutable. Studio uses reference equality to detect which parts of the state have changed, and updates them accordingly. If updating state and providing it back to Studio, ensure that a shallow copy is made to the depth of the changes.

## Changing Pages

#### Changing Pages

```tsx
"use client";

import React, {
  useCallback,
  useMemo,
  useRef,
  useState,
  StrictMode,
} from "react";
import { createRoot } from "react-dom/client";
import { AgStudio, AgStudioRef } from "ag-studio-react";
import {
  AgDataEngine,
  AgDataSourcesDefinition,
  AgReportState,
  AgStudioApi,
  AgStudioApiReadyEvent,
  AgStudioMode,
  AgStudioProperties,
} from "ag-studio";

const StudioExample = () => {
  const studioRef = useRef<AgStudioRef>(null);
  const containerStyle = useMemo(() => ({ width: "100%", height: "100%" }), []);
  const studioStyle = useMemo(() => ({ height: "100%", width: "100%" }), []);
  const [data, setData] = useState<AgDataSourcesDefinition | AgDataEngine>();
  const initialState = useMemo<AgReportState>(() => {
    return {
      pages: [
        {
          id: "page-1",
          widgets: {
            "1": {
              type: "grid",
              dataMapping: {
                cols: [
                  { id: "medals.country" },
                  { id: "medals.sport" },
                  { id: "medals.gold", aggregation: "sum" },
                  { id: "medals.silver", aggregation: "sum" },
                  { id: "medals.bronze", aggregation: "sum" },
                  { id: "medals.total", aggregation: "sum" },
                ],
              },
            },
          },
          widgetLayout: {
            "1": {
              xTrack: 0,
              yTrack: 0,
              xSpan: 24,
              ySpan: 24,
            },
          },
        },
        {
          id: "page-2",
          widgets: {
            "1": {
              type: "column-chart-grouped",
              dataMapping: {
                categoryKey: [{ id: "medals.country" }],
                valueKey: [
                  { id: "medals.gold", aggregation: "sum" },
                  { id: "medals.silver", aggregation: "sum" },
                  { id: "medals.bronze", aggregation: "sum" },
                ],
              },
            },
          },
          widgetLayout: {
            "1": {
              xTrack: 0,
              yTrack: 0,
              xSpan: 24,
              ySpan: 24,
            },
          },
        },
      ],
      selectedPageId: "page-1",
      panels: {
        filters: {
          collapsed: true,
        },
      },
    };
  }, []);

  const onApiReady = useCallback((params: AgStudioApiReadyEvent) => {
    fetch("https://www.ag-grid.com/studio/example-assets/olympic-winners.json")
      .then((resp) => resp.json())
      .then((data: any[]) =>
        setData({
          sources: [{ id: "medals", data }],
        }),
      );
  }, []);

  const updatePage = useCallback((pageId: string) => {
    const state = studioRef.current!.api.getState();
    studioRef.current!.api.setState({
      ...state,
      selectedPageId: pageId,
    });
  }, []);

  return (
    <div style={containerStyle}>
      <div style={{ display: "flex", flexDirection: "column", height: "100%" }}>
        <div className="example-controls">
          <div className="controls-row">
            <button onClick={() => updatePage("page-1")}>Page 1</button>
            <button onClick={() => updatePage("page-2")}>Page 2</button>
          </div>
        </div>

        <AgStudio
          ref={studioRef}
          style={studioStyle}
          className="my-studio-container"
          data={data}
          initialState={initialState}
          mode={"edit"}
          onApiReady={onApiReady}
        />
      </div>
    </div>
  );
};

const root = createRoot(document.getElementById("root")!);
root.render(
  <StrictMode>
    <StudioExample />
  </StrictMode>,
);
```

[Live example: Changing Pages](https://www.ag-grid.com/studio/examples/state/changing-pages/reactFunctionalTs/)

Reports support multiple pages. These are all defined in state, with the currently displayed page set via the top-level `selectedPageId` state property. The page can be changed by getting the latest state from Studio and setting back a copy with the `selectedPageId` updated.

```
const state = api.getState();
api.setState({
    ...state,
    selectedPageId: 'page-2',
});
```

## Invalid State

When state is invalid, Studio will do a "best-effort" attempt to load the state. This generally means removing invalid properties, which can leave widgets incomplete.

In Edit Mode, the UI can be used to fix the properties that were invalid, however in View Mode the user cannot make updates.

Studio will emit an `errorRaised` event with `errorType: 'state'` when there is invalid state (see [Studio Events](https://www.ag-grid.com/studio/react/studio-interface#studio-events) for how to listen to events). When in View Mode, where the report cannot be loaded properly, the event will have `fatal: true`.

It is recommended that you handle fatal error events by preventing the user from interacting with Studio. E.g. hiding Studio with your own error component, prompting the user to load a different report, switching into Edit Mode if the user has permissions, etc.

> **Note**
>
> To help avoid invalid state, we strongly recommend creating state in the UI and then retrieving it from Studio. You can then make minor tweaks if required, rather than hand-crafting the entire state object.

## State Versioning

If there are breaking changes to the shape of the state object, Studio will try to automatically upgrade any provided state based on the `version` property.

`version` is always set with the current version when retrieving state from Studio.

If `version` is not set, Studio will assume that the state is for the current version, and will not perform any migrations.

> **Note**
>
> Ensure that any saved-down state is read from Studio or has the version set. Otherwise you will have to manually update it if there are breaking changes to the shape of the state object.

## State API

### Properties

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `initialState` ([Initial](https://www.ag-grid.com/studio/react/studio-interface#initial-studio-properties)) | [`AgReportState<TRegistry>`](https://www.ag-grid.com/studio/react/studio-state#agreportstate) |  | Initial state for Studio. Only read once on initialization. Can be used in conjunction with `api.getState()` to save and restore Studio state. |

### API Methods

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `getState` | `Function` |  | Get the current state of Studio. Can be used in conjunction with the `initialState` Studio property or `api.setState()` to save and restore Studio state. |
| `setState` | `Function` |  | Set the current state of Studio. Can be used in conjunction with `api.getState()` or `onStateUpdated` to save and restore Studio state. The state is expected to be a full state object, not a partial state object. State must be updated immutably as Studio uses reference equality to determine which parts of state have changed. |

### Events

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `stateUpdated` | `AgStudioStateUpdatedEvent` |  | State has been updated. |

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `studioPreDestroyed` | `AgStudioPreDestroyedEvent` |  | Invoked immediately before Studio is destroyed. This is useful for cleanup logic that needs to run before Studio is torn down. |
