Derived State

In some scenarios, components need to perform further calculations on the State in Model before they can be used in the components. This logic can be directly written in the component or implemented through derived states in Model. Derived states are defined under the computed field in the Model. Depending on the dependencies of the Model and the return type, there are three ways to define derived states.

Only Depend on the Model's Own State

The derived state only depends on the current Model's State, which is passed as the first parameter to the derived state's definition function.

For example, the todo application has items and filter in its State, and filter is used to filter the todo items displayed on the current page. Therefore, we define a visibleTodos derived state that can be directly used in the component. The sample code is as follows:

/**
 *  Assuming the structure of the todo item is as follows:
{
    id: string;          // id
    text: string;        // todo
    completed: boolean;
}
**/

const todoModel = model('todo').define({
  state: {
    items: [],
    filter: 'ALL', // ALL: show all;COMPLETED: show completed;ACTIVE: show active
  },
  computed: {
    visibleTodos: state => {
      switch (state.filter) {
        case 'ALL':
          return state.items;
        case 'COMPLETED':
          return todos.filter(t => t.completed);
        case 'ACTIVE':
          return todos.filter(t => !t.completed);
        default:
          return [];
      }
    },
  },
});

Derived state will eventually be merged with the Model's State, so the derived state can be accessed through the Model's State object. For example, the visibleTodos can be used in the component as follows:

function Todo() {
  const [state, actions] = useModel(todoModel);

  return (
    <div>
      <div>
        {state.visibleTodos.map(item => (
          <div key={item.id}>{item.text}</div>
        ))}
      </div>
    </div>
  );
}

Dependent State from Other Models

In addition to depending on the current model's state, derived states may also depend on the state of other models. In this case, the definition format for the derived state is:

[stateKey]: [...depModels, (selfState, ...depModels) => computedState]

The following example demonstrates how the derived state combinedValue of barModel depends on the state of fooModel.

const fooModel = model('foo').define({
  state: {
    value: 'foo',
  },
});

const barModel = model('bar').define({
  state: {
    value: 'foo',
  },
  computed: {
    combinedValue: [
      fooModel,
      (state, fooState) => state.value + fooState.value,
    ],
  },
});

Derived State with Function Type

Derived states can also return a function. In this case, the definition format for the derived state is:

[stateKey]: (state) => (...args) => computedState    // Only relies on its own state
[stateKey]: [...depModels, (selfState, ...depModels) => (...args) => computedState]  // Relies on the state of other models

Assuming the filter state is not stored in the state of the todo app, but is instead used directly in the component, visibleTodos can be a function type value. This function receives the filter parameter when used in the component, as shown below:

const todoModel = model('todo').define({
  state: {
    items: [],
  },
  computed: {
    visibleTodos: state => filter => {
      switch (filter) {
        case 'ALL':
          return state.items;
        case 'COMPLETED':
          return todos.filter(t => t.completed);
        case 'ACTIVE':
          return todos.filter(t => !t.completed);
        default:
          return [];
      }
    },
  },
});

function Todo(props) {
  // use props pass filter
  const { filter } = props;
  const [state, actions] = useModel(todoModel);

  // get final todos
  const todos = state.visibleTodos(filter);

  return (
    <div>
      <div>
        {todos.map(item => (
          <div key={item.id}>{item.text}</div>
        ))}
      </div>
    </div>
  );
}
Additional Reference