Feature Flags in React with Flagged

When you are working in a big product, with multiple teams adding features and doing continuos deployment it's common to need a way to hide certain unfinished or unpolished parts of a UI from the users in production.

Or maybe you want to only show certain features to some users, maybe only to paid users or employees of a company. Even it's possible that you want to hide parts of the UI based on the role, e.g. only show admin features to admin users.

This is where enters Feature Flags, a technique to hide or show features based on a flag, which is basically a boolean which tells the application if the feature is enabled or not.

Let's see how we could show or hide React components based on those flags, for doing this we are going to use a package called Flagged, which is a super tiny library to use this technique in React based applications.

Hide Admin Only Components

Let's start with the simplest one, hide components intended to be available only to admin users. Let's say we have a wiki application, this application shows to the user the content along a button to edit it, but those edits should be moderated and if you are an admin it will show another button to see pending edits.

import React from "react";
import EditButton from "./edit-button";
import ModerateButton from "./moderate-button";

function ContentPage({ content }) {
  return (
    <section>
      <header>
        <EditButton />
        <ModerateButton />
      </header>
      <article>{content}</article>
    </section>
  );
}

export default ContentPage;

Something like that should work, right? But what happens when an non-admin users access a page rendered by this component? It will see two buttons, the edit and the moderate, and if the user tries to click the moderate button it will probably got a rejection when trying to access the moderation page which is probably a protected page already.

This is not ideal from the user perspective, we shouldn't show a button the user can't use, to solve this let's use flagged.

import React from "react";
import { FlagsProvider } from "flagged";

import ContentPage from "./content-page";

function App({ user }) {
  return (
    <FlagsProvider features={{ moderate: user.role === "admin" }}>
      <ContentPage />
    </FlagsProvider>
  );
}

export default App;

This will make the flag moderate enabled if the user has the role admin and disabled in other cases.

Now we need to check the flag status in our component, in our case since we want to hide ModerateButton completely if the user is not admin we could use the withFeature high order component flagged gives us.

import React from "react";
import { withFeature } from "flagged";
import Button from "./button";

function ModerateButton() {
  return <Button href="moderate">Moderate</Button>;
}

export default withFeature("moderate")(ModerateButton);

Now our component will only render if the flag moderate is true, if it's false then withFeature will return null and avoid rendering at all.

This is useful in the case we want to render or not a component without a fallback in case the feature is disabled.

Paid Only Feature with Fallback

Let's say now we want to only let paid users be able to edit content in our wiki, while free users will only be able to read the content, we could use an approach similar to before and hide the edit button completely from free users, however in this case it could be better to let the free users know this edit feature exists and they need to pay to use it, this way users may be tempted to pay us.

Let's start by adding a new flag.

import React from "react";
import { FlagsProvider } from "flagged";

import ContentPage from "./content-page";

function App({ user }) {
  const features = {
    moderate: user.role === "admin",
    paid: user.role === "admin" || user.hasActiveSubscription
  };

  return (
    <FlagsProvider features={features}>
      <ContentPage />
    </FlagsProvider>
  );
}

export default App;

This will enable the paid features if the user is either an admin or has an active subscription.

Now let's use the Feature component flagged gives use to provide an alternative to the Edit button in case the user is not a paid one.

import React from "react";
import { Feature } from "flagged";
import EditButton from "./edit-button";
import FakeEditButton from "./fake-edit-button";
import ModerateButton from "./moderate-button";

function ContentPage({ content }) {
  return (
    <section>
      <header>
        <Feature name="paid">
          {isPaid => (isPaid ? <EditButton /> : <FakeEditButton />)}
        </Feature>
        <ModerateButton />
      </header>
      <article>{content}</article>
    </section>
  );
}

export default ContentPage;

This Feature component will let use know if the feature paid is enabled so we could show two different components based on that. Our FakeEditButton could simulate the EditButton and show a modal to convince the user to become a paid one in order to use it.

We could also use the Feature component to replace the withFeature high order component.

import React from "react";
import { Feature } from "flagged";
import EditButton from "./edit-button";
import FakeEditButton from "./fake-edit-button";
import ModerateButton from "./moderate-button";

function ContentPage({ content }) {
  return (
    <section>
      <header>
        <Feature name="paid">
          {isPaid => (isPaid ? <EditButton /> : <FakeEditButton />)}
        </Feature>
        <Feature name="moderate">
          <ModerateButton />
        </Feature>
      </header>
      <article>{content}</article>
    </section>
  );
}

export default ContentPage;

This way we could ditch the withFeature HOC, the only possible issue here is not our ContentPage needs to know if ModerateButton should be behind a flag or not, in the HOC approach it was the ModerateButton the only one aware of the flag.

Run Effects Based on a Flag

We saw how to use the high order component and the render prop API Flagged gives us, both of those use internally the custom hook useFeature to detect if the feature is enabled or not. This custom hook could also help use create custom logic based on a flag.

Let say now want to track when a free user access a page, but we don't want to track paid users, since they are paying we promising them anonymity in our application.

Let's create a custom hook useTracking which will use our useFeature to check if it should or not track the user.

import React from "react";
import { pageview } from "../services/analytics";
import { useFeature } from "flagged";

function useTracking() {
  const isPaid = useFeature("paid");

  React.useEffect(() => {
    if (isPaid) return;

    pageview(window.location.pathname);
  }, [isPaid]);
}

export default useTracking;

Now let's use it in our ContentPage component.

import React from "react";
import { Feature } from "flagged";
import EditButton from "./edit-button";
import FakeEditButton from "./fake-edit-button";
import ModerateButton from "./moderate-button";
import useTracking from "../hooks/use-tracking";

function ContentPage({ content }) {
  useTracking();

  return (
    <section>
      <header>
        <Feature name="paid">
          {isPaid => (isPaid ? <EditButton /> : <FakeEditButton />)}
        </Feature>
        <ModerateButton />
      </header>
      <article>{content}</article>
    </section>
  );
}

export default ContentPage;

That's all, our tracking hook will only work for non-paid users.

Hooks as a Low Level Primitive

We could also use the useFeature hook to replace the render prop component in ContentPage.

import React from "react";
import { useFeature } from "flagged";
import EditButton from "./edit-button";
import FakeEditButton from "./fake-edit-button";
import ModerateButton from "./moderate-button";
import useTracking from "../hooks/use-tracking";

function ContentPage({ content }) {
  const isPaid = useFeature("paid");
  useTracking();

  return (
    <section>
      <header>
        {isPaid ? <EditButton /> : <FakeEditButton />}
        <ModerateButton />
      </header>
      <article>{content}</article>
    </section>
  );
}

export default ContentPage;

Even the ModerateButton could be hidden using the useFeature hook.

import React from "react";
import { useFeature } from "flagged";
import EditButton from "./edit-button";
import FakeEditButton from "./fake-edit-button";
import ModerateButton from "./moderate-button";
import useTracking from "../hooks/use-tracking";

function ContentPage({ content }) {
  const isPaid = useFeature("paid");
  const isModerator = useFeature("moderate");
  useTracking();

  return (
    <section>
      <header>
        {isPaid ? <EditButton /> : <FakeEditButton />}
        {isModerator && <ModerateButton />}
      </header>
      <article>{content}</article>
    </section>
  );
}

export default ContentPage;

This will render ModerateButton only if isModerator is true.

Final Words

As you could see above there are multiple cases in which feature flags flags is useful and with Flagged there are multiple approaches you could take to detect if a flag is enabled and render a component or run an effect.

Are you using feature flags in your project? Do you know another example where it could be useful? Do you have any question about how Flagged or Feature Flags works? Send me tweet to share your thoughts.