Make your application easily configurable.
A simple way to provide runtime configuration for your React application, with localStorage overrides and hot-reload support
Summary
Why
Most web applications usually need to support and function within a variety of distinct environments: local, development, staging, production, on-prem, etc. This project aims to provide flexibility to React applications by making certain properties configurable at runtime, allowing the app to be customized based on a pre-determined configmap respective to the environment. This is especially powerful when combined with Kubernetes configmaps.
Here are examples of some real-world values that can be helpful when configurable at runtime:
- Primary Color
- Backend API URL
- Feature Flags
- …
How
The configuration can be set by either:
- setting a configuration property on
windowwith reasonable defaults. Consider,
window.MY_APP_CONFIG = {
primaryColor: "green",
};- or by setting a value in
localStorage. Consider,
localStorage.setItem("MY_APP_CONFIG.primaryColor", "green");The localStorage option could provide a nice delineation between environments: you could set your local environment to green, and staging to red for example, in order to never be confused about what you're looking at when developing locally and testing against a deployed development environment: if it's green, it's local.
This configuration is then easily read by the simple React component that this library exports.
Getting started
npm i react-runtime-config- Create a namespace for your config:
// components/Config.tsx
import createConfig from "react-runtime-config";
/**
* All config values that need to be set in window.
* Errors will be thrown if these values do not exist.
*/
interface MandatoryConfig {
backendUrl: string;
}
// All optional config values.
const defaultConfig = {
color: "pink",
myFeatureFlag: false,
};
export type ConfigType = MandatoryConfig & typeof defaultConfig;
/**
* Config and AdminConfig are now React components that
* you can use in your app.
*
* Config reads the config, AdminConfig provides data in order
* to visualize your config map with ease. More on this further
* down.
*/
export const { Config, AdminConfig } = createConfig<ConfigType>({
namespace: "MY_APP_CONFIG",
defaultConfig,
// Types are totally optionals since they are just metadata for the AdminConfig
types: {
myFeatureFlag: "boolean",
color: ["pink", "red", "blue"], // Enum type
backendUrl: "string",
},
});
export default Config;You can now use the created components everywhere in your application.
Usage
// components/MyComponents.tsx
import react from "React";
import Config from "./Config";
const MyComponent = () => <Config>{({ getConfig }) => <h1 style={{ color: getConfig("color") }}>My title</h1>}</Config>;The title will have a different color regarding our current environment.
The priority of config values is as follows:
localStorage.getItem("MY_APP_CONFIG.color")window.MY_APP_CONFIG.colordefaultConfig.color
Options
interface ConfigOptions {
namespace: string;
/**
* Config default values
*/
defaultConfig?: Partial<TConfig>;
/**
* Storage adapter
*
* @default window.localStorage
*/
storage?: StorageAdapter;
/**
* Permit to override any config values in storage
*
* @default true
*/
localOverride?: boolean;
/**
* Runtime types mapping (metadata for AdminConfig)
*/
types?: { [key in keyof TConfig]?: RuntimeType };
}Create an Administration Page
To allow easy management of your configuration, we provide a smart component called <AdminConfig /> that provides all the data that you need in order to assemble an awesome administration page where the configuration of your app can be referenced and managed.
Note: we are using @operational/components for this example, but a UI of config values can be assembled with any UI library, or even with plain ole HTML-tag JSX.
// pages/ConfigurationPage.tsx
import { Page, Card, Input, Button, Checkbox } from "@operational/components";
import { AdminConfig } from "./components/Config";
export default () => (
<Page title="Configuration">
<Card title="Configuration">
<AdminConfig>
{({ fields, onFieldChange, submit, reset }) => (
<>
{fields.map(field =>
field.type === "boolean" ? (
<Checkbox
key={field.path}
value={field.value}
label={field.path}
onChange={val => onFieldChange(field.path, val)}
/>
) : (
<Input
key={field.path}
value={field.value}
label={field.path}
onChange={val => onFieldChange(field.path, val)}
/>
),
)}
<Button onClick={submit}>Update config</Button>
<Button onClick={reset}>Reset config</Button>
</>
)}
</AdminConfig>
</Card>
</Page>
);You have also access to field.windowValue and field.storageValue if you want implement more advanced UX on this page.
Moar Power (if needed)
We also expose from createConfig a simple getConfig, getAllConfig and setConfig. These functions can be used standalone and do not require use of the Config component. This can be useful for accessing or mutating configuration values in component lifecycle hooks, or anywhere else outside of render.
These functions and are exactly the same as their counterparts available inside the Config component, the only thing you lose is the hot config reload.
