Pull Component Data from App Config

Geocortex Web has a powerful app configuration model which can be used to easily change the behavior of an application without modifying custom code. Using app config to power a components behavior increases its reusability and customizability.

By the end of this article, you'll have the knowledge to build a component that displays relevant news items at the top of your map. These news items will be populated from config, along with a value that tells the news component whether or not to be visible by default.

Prerequisites

Starting Point

We are going to add configuration to a custom component that displays news items. This component currently is following bad practices and does not treat the model as the source of truth for its data. We are going to move the newsItems list to a configurable property on the model, and add a new configurable property, hideOnStartup.

note

This example uses Geocortex Web layout components

src/components/NewsFeed/NewsFeed.tsx
import React, { useState } from "react";
import { LayoutElement } from "@vertigis/web/components";
import List from "@vertigis/web/ui/List";
import ListItem from "@vertigis/web/ui/ListItem";
import TitleBar from "@vertigis/web/ui/TitleBar";
import Button from "@vertigis/web/ui/Button";
import DialogActions from "@vertigis/web/ui/DialogActions";
import "./NewsFeed.css";
export default function NewsFeed(props) {
const [hidden, setHidden] = useState(false);
const newsItems: string[] = [
"New fire hydrant installed at Main and 5th.",
"Pipe burst at 4th and Broadview",
"Fire hydrant reported as needs maintenance by citizen.",
];
return (
<LayoutElement {...props}>
<List className="news-item-list">
<DialogActions>
<TitleBar text="Recent News"></TitleBar>
{hidden && (
<Button onClick={() => setHidden(false)}>
Show News
</Button>
)}
{!hidden && (
<Button onClick={() => setHidden(true)}>
Hide News
</Button>
)}
</DialogActions>
{!hidden &&
newsItems.map((news, idx) => (
<ListItem key={idx}>{news}</ListItem>
))}
</List>
</LayoutElement>
);
}

Define the Configurable Properties

First, we need to create a NewsFeedModelProperties interface which we use to inform the NewsFeedModel about which properties it should populate from configuration.

import {
ComponentModelBase,
serializable,
ComponentModelProperties,
PropertyDefs,
} from "@vertigis/web/models";
import Collection from "esri/core/Collection";
interface NewsFeedModelProperties extends ComponentModelProperties {
newsItems?: Collection<string>;
hideOnStartup?: boolean;
}
@serializable
export default class NewsFeedModel extends ComponentModelBase<
NewsFeedModelProperties
> {
/**
* Array of items to display in the news feed
*/
newsItems: Collection<string> = new Collection<string>();
/**
* Whether or not the news ticker is initially hidden
*/
hideOnStartup: boolean;
}

Participate in the Configuration

Next, we have to inform Geocortex Web about how to serialize and deserialize these properties between the app config and the model, as well as provide default values. We do this by implementing the _getSerializableProperties method.

src/components/NewsFeed/NewsFeedModel.ts
import {
ComponentModelBase,
serializable,
ComponentModelProperties,
PropertyDefs,
} from "@vertigis/web/models";
import Collection from "esri/core/Collection";
interface NewsFeedModelProperties extends ComponentModelProperties {
newsItems?: string[];
hideOnStartup?: boolean;
}
@serializable
export default class NewsFeedModel extends ComponentModelBase<
NewsFeedModelProperties
> {
/**
* Array of items to display in the news feed
*/
newsItems: Collection<string> = new Collection<string>();
/**
* Whether or not the news ticker is hidden
*/
hideOnStartup: boolean;
protected _getSerializableProperties(): PropertyDefs<
NewsFeedModelProperties
> {
const props = super._getSerializableProperties();
return {
...props,
newsItems: {
serializeModes: ["initial"],
default: ["No news."],
serialize: () => this.newsItems.toArray(),
deserialize: (newsItems) => {
newsItems.forEach((newsItem) =>
this.newsItems.add(newsItem)
);
},
},
hideOnStartup: {
serializeModes: ["initial"],
default: false,
},
};
}
}

Consume the Configuration in the Component

Finally, we need to update the NewsFeed component to treat the model as its single source of truth for data. First, we update the props passed into the component to include the relevant model.

export interface NewsFeedProps extends LayoutElementProperties<NewsFeedModel> {}
export default function NewsFeed(props: NewsFeedProps) {
...
}

The model will initially populated with values from configuration or defaults. The component can use props.model values to set the initial state, but we also want to update the model and re-render on model changes. Since the data state is contained within the model, we can't use the useState React pattern.

To respond to model changes, we can do the following.

Upon user interaction that affects state,

  1. The component updates the model values.
  2. The component listens for changes on the model values and re-renders with the useWatchAndRerender function.
note

Learn more about the helper React Hook functions like useWatchAndRerender.

export default function NewsFeed(props: NewsFeedProps) {
const { model } = props;
useWatchAndRerender(model, "newsItems");
...
}

Complete Example

Following is a complete example where news items are configured in the app.json, populated into the NewsFeedModel and finally consumed and presented by the NewsFeed component.

src/components/NewsFeed/NewsFeed.tsx
import React, { useState } from "react";
import {
LayoutElement,
LayoutElementProperties,
} from "@vertigis/web/components";
import { useWatchAndRerender } from "@vertigis/web/ui";
import List from "@vertigis/web/ui/List";
import ListItem from "@vertigis/web/ui/ListItem";
import TitleBar from "@vertigis/web/ui/TitleBar";
import Button from "@vertigis/web/ui/Button";
import DialogActions from "@vertigis/web/ui/DialogActions";
import "./NewsFeed.css";
import { NewsFeedModel } from ".";
export interface NewsFeedProps extends LayoutElementProperties<NewsFeedModel> {}
export default function NewsFeed(props: NewsFeedProps) {
const { model } = props;
const { hidden, setHidden } = useState(model.hideOnStartup);
/**
* The use watch function handles observing a property on the model,
* re-rendering on change, and cleaning up the subscription handle on unmount.
* This helper function allows you to use the model as your component state.
*/
useWatchAndRerender(model, "newsItems");
return (
<LayoutElement {...props}>
<List className="news-item-list">
<DialogActions>
<TitleBar text="Recent News"></TitleBar>
{model.hidden && (
<Button onClick={() => (model.hidden = false)}>
Show News
</Button>
)}
{!model.hidden && (
<Button onClick={() => (model.hidden = true)}>
Hide News
</Button>
)}
</DialogActions>
{!model.hidden &&
model.newsItems
.map((news, idx) => (
<ListItem key={idx}>{news}</ListItem>
))
.toArray()}
</List>
</LayoutElement>
);
}

Next Steps

Check out the Component Reference

Take a deep dive into components in the Geocortex Web SDK