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.