Skip to main content

Interact with the application state

This page shows how to interact with the application state. In particular, you will learn how to:

note

The snippets in this page work with the Atoti UI starter. If you have not installed it yet, then read Setup.

note

Atoti uses Redux for state management. A basic understanding of it is preferable in order to understand what follows.

Consuming and updating the state is possible using two hooks: useSelector and useDispatch from react-redux. It works just like when you use Redux to manage the state of any web application. The only difference is that you do not have to create and provide a store, as Atoti UI does it for you.

See more details and examples below.

Consume state attributes

To retrieve attributes from the state, use useSelector.

For instance, the following snippet implements a higher order component which depends on isPresenting. If it is true, then it just displays the application, but if it is false, then it also displays a custom horizontal application bar to the left of it.

import React, { ComponentType } from "react";
import { useSelector } from "react-redux";
import { getIsPresenting } from "@activeviam/activeui-sdk";

/**
* HOC to be used around Atoti, in order to display a custom left bar (except in presentation mode).
*/
export const withLeftBar = (WrappedApplication: ComponentType) => () => {
const isPresenting = useSelector(getIsPresenting);
if (isPresenting) {
return <WrappedApplication />;
}

return (
<div style={{ height: "100%", display: "flex" }}>
<div style={{ width: 200 }}>Custom left bar</div>
<div style={{ flexGrow: 1, position: "relative" }}>
<WrappedApplication />
</div>
</div>
);
};

It can then be registered as follows, in your index.tsx:

import { withLeftBar } from "./withLeftBar";

const extension: ExtensionModule = {
activate: async (configuration) => {
configuration.higherOrderComponents.push(withLeftBar);
},
};

Like getIsPresenting in the example above, more selectors are exported from @activeviam/activeui-sdk and allow you to retrieve specific pieces of state:

Trigger state updates

To update the state, use useDispatch.

For instance, the following snippet implements an application menu item which adds a new empty page to the dashboard each time the user clicks it.

import { MenuItem, MenuItemProps } from "antd";
import React, { FC } from "react";
import { useDispatch, useSelector } from "react-redux";
import {
ApplicationMenuItem,
DashboardPageState,
addPage,
getDashboardState,
pluginWidgetPivotTable,
} from "@activeviam/activeui-sdk";

/** State of the added page */
const addedPage: DashboardPageState = {
content: { "0": pluginWidgetPivotTable.initialState },
layout: {
children: [
{
leafKey: "0",
size: 1,
},
],
direction: "row",
},
name: "New page",
};

/** Menu item component */
const AddPageMenuItemComponent: FC<MenuItemProps> = (
props,
) => {
const dashboardState = useSelector(getDashboardState);
const dispatch = useDispatch();
const handleClicked: MenuItemProps["onClick"] = (param) => {
if (dashboardState) {
// Update the state in order to add a new page to the dashboard.
dispatch({
type: "dashboardUpdated",
dashboardState: addPage(dashboardState, { page: addedPage }),
});
}
props.onClick?.(param);
};
return (
<MenuItem {...props} onClick={handleClicked}>
Add a page
</MenuItem>
);
};

/** Menu item that can be wired into the Atoti UI configuration */
export const addPageAppMenuItem: ApplicationMenuItem = {
component: AddPageMenuItemComponent,
};

This menu item can be added into the Edit menu as follows, in your index.tsx:

import { ApplicationMenu } from "@activeviam/activeui-sdk";

const extension: ExtensionModule = {
activate: async (configuration) => {
const editApplicationMenu = configuration
.leftApplicationMenu[1] as ApplicationMenu;
editApplicationMenu.children.push(addPageAppMenuItem);
},
};

For more information about the actions you can dispatch in order to update the state, see Action.

Advanced use cases

For more advanced use cases such as extending the state or changing the initial state, you can use a tool called a store enhancer.

Store enhancers allow to hook into the application's Redux store creation. They can be passed through Configuration, in your index.tsx:

import { PreloadedState, Reducer, StoreEnhancer } from "redux";

const myStoreEnhancer: StoreEnhancer = (createStore) => (
reducer: Reducer<any, any>,
preloadedState?: PreloadedState<any>,
) => {
// ... This store enhancer does nothing ...
// ... You will see more examples below ...
return createStore(reducer, preloadedState);
};

const extension: ExtensionModule = {
activate: async (configuration) => {
configuration.storeEnhancers.push(myStoreEnhancer);
},
};

Extend the state

What if you need to share state (that is not part of the core Atoti UI state) between several components?

With the right store enhancer, your own state can live along the core state, within the Atoti UI store.

For instance, the following store enhancer introduces a boolean attribute called foo along with an action to toggle it. It allows your components to use useSelector and useDispatch in order to consume and update this slice of state.

import { PreloadedState, Reducer, StoreEnhancer } from "redux";

const fooReducer: Reducer = (state = true, action) => {
return action.type === "toggleFoo" ? !state : state;
};

/** Store enhancer adding a slice of state called `foo` into Atoti */
const myStoreEnhancer: StoreEnhancer = (
createStore,
) => (reducer: Reducer<any, any>, preloadedState?: PreloadedState<any>) => {
const enhancedReducer: Reducer<any, any> = (state, action) => {
const nextState = reducer(state, action);
const nextFoo = fooReducer(state?.foo, action);
return { ...nextState, foo: nextFoo };
};
return createStore(enhancedReducer, preloadedState);
};

Change the initial state

You can configure the initial state by overriding the Redux preloaded state. For instance, the following store enhancer makes Atoti start up in presentation mode:

/** Store enhancer allowing to start in presentation mode */
const myStoreEnhancer: StoreEnhancer = (createStore) => (
reducer: Reducer<any, any>,
preloadedState?: PreloadedState<any>,
) => {
return createStore(reducer, preloadedState);
const overriddenPreloadedState = {
...preloadedState,
isPresenting: true,
};

return createStore(reducer, overriddenPreloadedState);
};
warning

Don't forget to forward preloadedState, as illustrated in the snippet above. Forgetting it can break other store enhancers, possibly registered by other Atoti UI extensions.