# Nested State Manager Example

This more complex state manager illustrates the use of nested state managers. In this case, the nested state managers are provided by the Salesforce platform. (Documentation for these built-in state managers is forthcoming.)

This example is excerpted from the `platform-state-managers` example in [State Management Examples](/docs/platform/lwc/guide/state-management-examples.md). The code is the same, but the comments are a bit more detailed.

```javascript
import { defineState } from "@lwc/state";
import smRecord from "lightning/stateManagerRecord";
import smLayout from "lightning/stateManagerLayout";

/**
 * Extracts the fields referenced by a layout.
 *
 * @param {*} layout layout definition
 * @returns fields referenced by the layout, as a string[]
 */
function extractFields(layout) {
  if (!layout) {
    return;
  }

  const fields = [];

  for (const section of layout.sections) {
    for (const row of section.layoutRows) {
      for (const item of row.layoutItems) {
        for (const component of item.layoutComponents) {
          if (component.componentType === "Field") {
            fields.push(`${layout.objectApiName}.${component.apiName}`);
          }
        }
      }
    }
  }

  return fields;
}

// Define the state manager
export default defineState(({ atom, computed, setAtom }, recordId, objectApiName) => {
    // This atom captures the current configuration of the state manager instance.
    // We wrap it in an atom to make it easier for other data to react to changes
    // in the config. It is NOT exposed as one of the properties of this state
    // manager.
    const config = atom({ recordId, objectApiName });

    // Actions to set or change the config
    const setRecordId = (recordId) =>
      setAtom(config, { recordId, objectApiName: config.value.objectApiName });
    const setObjectApiName = (objectApiName) =>
      setAtom(config, { recordId: config.value.recordId, objectApiName });

    // The following constructs implement a data waterfall that corresponds roughly
    // to what happens in a layout-driven detail panel like you see on record home.
    // (The real detail panel is FAR more complex; this is a VERY simplified version
    // of the data logic from that component.)
    //
    // 1. The `recordId` and `objectApiName` supplied as config to this state manager are
    //    used to retrieve a minimal copy of the record so that we can find its
    //    `recordTypeId`.
    // 2. The `objectApiName` and `recordTypeId` from (1) are used to retrieve the layout
    //    that should be used for the record.
    // 3. The `recordId`, `objectApiName`, and fields referenced in the layout are used
    //    to retrieve a complete copy of the record that can be displayed.

    // `initialRecord` is a nested state manager that is used to retrieve a minimal copy
    // of the record (step 1 above). `computed()` is used here so that the configuration
    // for `smRecord` will automatically be updated anytime the config changes.
    const initialRecord = smRecord(
      computed([config], ({ recordId, objectApiName } = {}) => {
        // If the state manager does not yet have a `recordId` or `objectApiName` then
        // pass an empty config to `smRecord`; the empty config will cause `smRecord` to wait.
        if (!recordId || !objectApiName) {
          return {};
        }

        // Once we have `recordId` and `objectApiName`, tell `smRecord` to get the record.
        // The `Id` field is requested here because we have to specify at least one field.
        // The data we really want (the `recordTypeId`) is included by default.
        return {
          recordId,
          fields: [`${objectApiName}.Id`],
        };
      }),
    );

    // `layout` is another nested state manager that we use to retrieve the layout. Its inputs
    // are derived from the output of `initialRecord`, so `computed()` is used to update
    // the config every time `initialRecord` changes.
    const layout = smLayout(
      computed([initialRecord], ({ data: recordData }) => {
        // If `initialRecord` has not retrieved the record yet then return an empty config
        // so `smLayout` will wait.
        if (!recordData) {
          return {};
        }

        // Once `initialRecord` has data available, use its `apiName` and `recordTypeId` to ask
        // for the Compact View layout.
        return {
          objectApiName: recordData.apiName,
          recordTypeId: recordData.recordTypeId,
          layoutType: "Compact",
          mode: "View",
        };
      }),
    );

    // `finalRecord` is used to retrieve the full set of field values that the layout needs.
    // `computed()` is used here so that the config for this second `smRecord` will be re-evaluated
    // whenever `initialRecord` or `layout` changes.
    const finalRecord = smRecord(
      computed([initialRecord, layout], ({ data: recordData }, { data: layoutData }) => {
        // Tell `smRecord` to wait if we don't have the necessary information yet.
        if (!recordData || !layoutData) {
          return {};
        }

        // Once we have all the information, ask for all the layout's fields for the
        // record.
        return {
          recordId: recordData.id,
          fields: extractFields(layoutData),
        };
      }),
    );

    // `data` is a just a more consumable form of the field values in `finalRecord`.
    // As such, we want to update it every time `finalRecord` changes.
    const data = computed([finalRecord], ({ data: recordData }) => {
      if (!recordData) {
        return;
      }

      const fieldValues = {};
      for (const [field, value] of Object.entries(recordData.fields)) {
        fieldValues[field] = value.displayValue || value.value;
      }

      return fieldValues;
    });

    // `error` is an aggregation of errors from `initialRecord`, `layout`, and `finalRecord`.
    const error = computed(
      [initialRecord, layout, finalRecord],
      ({ error: initialRecordError }, { error: layoutError }, { error: finalRecordError }) =>
        initialRecordError || layoutError || finalRecordError,
    );

    // `status` lets consumers of this state manager understand what's going on.
    const status = computed(
      [initialRecord, layout, finalRecord],
      (
        { status: initialRecordStatus },
        { status: layoutStatus },
        { status: finalRecordStatus },
      ) => {
        // We configure `initialRecord` as soon as this state manager is configured, so we
        // can assume its "unconfigured" status means that this state manager is also
        // unconfigured.
        if (initialRecordStatus === "unconfigured") {
          return "unconfigured";
        }
        // any errors => error
        else if (
          initialRecordStatus === "error" ||
          layoutStatus === "error" ||
          finalRecordStatus === "error"
        ) {
          return "error";
        }
        // everything loaded => loaded
        else if (
          initialRecordStatus === "loaded" &&
          layoutStatus === "loaded" &&
          finalRecordStatus === "loaded"
        ) {
          return "loaded";
        }
        // other status combinations mean something is still loading
        else {
          return "loading";
        }
      },
    );

    // This is the external shape that consumers of this state manager will see.
    return {
      // Data properties
      data,
      error,
      status,

      // Actions
      setObjectApiName,
      setRecordId,
    };
  };
);
```
