npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@spydersoft/react-runtime-config

v3.0.4

Published

Provide a typesafe runtime configuration inside a react app

Downloads

91

Readme

React Runtime Config (FORK)

This is a fork of contiamo's react-runtime-config library. At the time of the fork creation, the library had not been formally released since April 15, 2021. This library is scoped as @spydersoft/react-runtime-config at npm.

A simple way to provide runtime configuration for your React application, with localStorage overrides and hot-reload support ⚡️!

npm build mit

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 window with 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 hook that this library exports.

Getting started

  1. npm i @spydersoft/react-runtime-config
  2. Create a namespace for your config:
// components/Config.tsx
import createConfig from "@spydersoft/react-runtime-config";

/**
 * `useConfig` and `useAdminConfig` are now React hooks that you can use in your app.
 *
 * `useConfig` provides config getter & setter, `useAdminConfig` provides data in order
 * to visualize your config map with ease. More on this further down.
 */
export const { useConfig, useAdminConfig } = createConfig({
  namespace: "MY_APP_CONFIG",
  schema: {
    color: {
      type: "string",
      enum: ["blue" as const, "green" as const, "pink" as const], // `as const` is required to have nice autocompletion
      description: "Main color of the application",
    },
    backend: {
      type: "string",
      description: "Backend url", // config without `default` need to be provided into `window.MY_APP_CONFIG`
    },
    port: {
      type: "number", // This schema can be retrieved after in `useAdminConfig().fields`
      description: "Backend port",
      min: 1,
      max: 65535,
      default: 8000, // config with `default` don't have to be set on `window.MY_APP_CONFIG`
    },
    monitoringLink: {
      type: "custom",
      description: "Link of the monitoring",
      parser: value => {
        if (typeof value === "object" && typeof value.url === "string" && typeof value.displayName === "string") {
          // The type will be inferred from the return type
          return { url: value.url as string, displayName: value.displayName as string };
        }
        // This error will be shown if the `window.MY_APP_CONFIG.monitoringLink` can't be parsed or if we `setConfig` an invalid value
        throw new Error("Monitoring link invalid!");
      },
    },
    isLive: {
      type: "boolean",
      default: false,
    },
  },
});

You can now use the created hooks everywhere in your application. Thoses hooks are totally typesafe, connected to your configuration. This means that you can easily track down all your configuration usage across your entire application and have autocompletion on the keys.

Usage

// components/MyComponents.tsx
import react from "React";
import { useConfig } from "./Config";

const MyComponent = () => {
  const { getConfig } = useConfig();

  return <h1 style={{ color: getConfig("color") }}>My title</h1>;
};

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.color
  • schema.color.default

Namespaced useConfig hook

In a large application, you may have multiple instances of useConfig from different createConfig. So far every useConfig will return a set of getConfig, setConfig and getAllConfig.

To avoid any confusion or having to manually rename every usage of useConfig in a large application, you can use the configNamespace options.

// themeConfig.ts
export const { useConfig: useThemeConfig } = createConfig({
  namespace: "theme",
  schema: {},
  configNamespace: "theme", // <- here
});

// apiConfig.ts
export const { useConfig: useApiConfig } = createConfig({
  namespace: "api",
  schema: {},
  configNamespace: "api", // <- here
});

// App.ts
import { useThemeConfig } from "./themeConfig";
import { useApiConfig } from "./apiConfig";

export const App = () => {
  // All methods are now namespaces
  // no more name conflicts :)
  const { getThemeConfig } = useThemeConfig();
  const { getApiConfig } = useApiConfig();

  return <div />;
};

Create an Administration Page

To allow easy management of your configuration, we provide a smart react hook called useAdminConfig 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 { useAdminConfig } from "./components/Config";

export default () => {
  const { fields, reset } = useAdminConfig();

  return (
    <Page title="Configuration">
      <Card title="Configuration">
        {fields.map(field =>
          field.type === "boolean" ? (
            <Checkbox key={field.key} value={field.value} label={field.key} onChange={field.set} />
          ) : (
            <Input key={field.key} value={field.value} label={field.key} onChange={field.set} />
          ),
        )}
        <Button onClick={reset}>Reset config</Button>
      </Card>
    </Page>
  );
};

You have also access to field.windowValue and field.storageValue if you want implement more advanced UX on this page.

Multiconfiguration admin page

As soon as you have more than one configuration in your project, you might want to merge all thoses configurations in one administration page. Of course, you will want a kind of ConfigSection component that take the result of any useAdminConfig() (so field, reset and namespace as props).

Spoiler alert, having this kind of component type safe can be tricky, indeed you can try use ReturnType<typeof useFirstAdminConfig> | ReturnType<typeof useSecondAdminConfig> as props but typescript will fight you (Array.map will tell you that the signature are not compatible).

Anyway, long story short, this library provide you an easy way to with this: GenericAdminFields type. This type is compatible with every configuration and will provide you a nice framework to create an amazing UX.

import { GenericAdminFields } from "@spydersoft/react-runtime-config";

export interface ConfigSectionProps {
  fields: GenericAdminFields;
  namespace: string;
  reset: () => void;
}

export const ConfigSection = ({ namespace, fields }: ConfigSectionProps) => {
  return (
    <Section title={namespace}>
      {fields.map(f => {
        if (f.type === "string" && !f.enum) {
          return <Input key={f.key} type="text" label={f.key} onChange={f.set} value={f.value} />;
        }
        if (f.type === "number") {
          return <Input key={f.key} type="number" label={f.key} onChange={f.set} value={f.value} />;
        }
        if (f.type === "boolean") {
          return <Checkbox key={f.key} label={f.key} onChange={f.set} value={f.value} />;
        }
        if (f.type === "string" && f.enum) {
          // `f.set` can take `any` but you still have runtime validation if a wrong value is provided.
          return <Select options={f.enum} value={f.value} onChange={f.set} />;
        }
        if (f.type === "custom") {
          /* Add some special handler/typeguard to retrieve the safety */
        }
      })}
    </Section>
  );
};

PS: If you have a better idea/pattern, please open an issue to tell me about it 😃

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 useConfig react hooks. This can be useful for accessing or mutating configuration values in component lifecycle hooks, or anywhere else outside of render.

These functions are exactly the same as their counterparts available inside the useConfig react hook, the only thing you lose is the hot config reload.