Hi 👋
🤔 Have you ever been in a situation where you wished you could launch a feature to a handful of users and then roll it out to 100% of the users based on the feedback/analytics? Or your team just ended up developing a huge feature, but the marketing/product team says we won't launch it yet?
😖 You end up creating a separate feature branch and trying to keep that in sync with your main branch. But that doesn't end just there. A few weeks later, you want to launch that feature. Now, you'll have to trigger the deployment again. The situation is much worse with mobile apps, where complete rollout takes 2-4 days.
😭 Oh! wait? You found an issue. You want to block users from using that feature. Good luck!
👌 To save us, developers, from situations like these we have Feature Flags! Not only developers, it even helps marketing, product, and sales teams.
What are Feature Flags?
I like the LaunchDarkly's definition
A feature flag is a software development process/pattern used to enable or disable functionality remotely without deploying code. New features can be deployed without making them visible to users. Feature flags help decouple deployment from release letting you manage the full lifecycle of a feature.
Features flags can be used for:
- Running A/B tests.
- Managing Beta programs.
- Reducing multiple deployments or rollbacks.
- Providing role-based access.
- Minimizing release failures by rolling out features to smaller groups first.
Once you start using Feature Flags, there's no going back.
Adding Feature Flags in React
This implementation uses React ContextAPI. Before going ahead, make sure you understand the basics.
Let's start with an example:
Imagine you work on the 💰 Payment gateway of a huge website/app. That has recently added two new payment modes: Apple Pay and Google Pay.
You being a 10x developer, completed both the integrations pretty quickly but the Marketing team wants to hold back the launch of Google Pay for a few weeks. Apple Pay goes live tomorrow.
You wouldn't want to maintain a separate branch and re-deploy few weeks later. So, you choose to add feature flags. 😎
First, let's start with creating a Context for Feature flags.
// /contexts/FeatureFlags.js
export const FeatureFlags = React.createContext({});
Now, let's create a Provider that will wrap our React DOM tree.
// /contexts/FeatureFlags.js
export const FeatureFlags = React.createContext({});
export const FeatureFlagsProvider = ({ children }) => {
const [features, setFeatures] = React.useState({});
return (
<FeatureFlags.Provider value={{ features }}>
{children}
</FeatureFlags.Provider>
);
};
Our Context is all set up just a few more things to go. Right now, we can wrap the tree with this provider.
// index.js
// ... imports here
import App from "./App";
import { FeatureFlagsProvider } from "./contexts/FeatureFlags";
const rootElement = document.getElementById("root");
const root = createRoot(rootElement);
root.render(
<StrictMode>
<FeatureFlagsProvider>
<App />
</FeatureFlagsProvider>
</StrictMode>
);
Now, all we have to do is, get our features. I've created a dummy API using fastify. You can ignore this part.
// enabling cors for codesandbox
fastify.register(require("fastify-cors"), {
origin: /\.csb\.app$/
});
// feature flags route
fastify.get("/feature-flags", function(request, reply) {
const features = {
isGooglePayEnabled: true,
isApplePayEnabled: false
}
reply.send({ features });
});
Coming back to our context file, let's fetch the features.
// /contexts/FeatureFlags.js
import { fetchFeatures } from 'api'
export const FeatureFlags = React.createContext({});
export const FeatureFlagsProvider = ({ children }) => {
const [isLoading, setIsLoading] = React.useState(true);
const [features, setFeatures] = React.useState({});
React.useEffect(() => {
(async () => {
try {
const data = await fetchFeatures();
if (data.features) {
setFeatures(data.features);
}
} catch (err) {
console.log(err);
} finally {
setIsLoading(false);
}
})();
}, []);
return (
<FeatureFlags.Provider value={{ features }}>
{isLoading ? "Loading..." : children}
</FeatureFlags.Provider>
);
};
Just added a useEffect
and a loading state for our application.
And we're done! 🎉
The last step is to use this in our components.
// components/PaymentOptions.js
import { FeatureFlags } from "contexts/FeatureFlags";
const PaymentOptions = () => {
const { features } = React.useContext(FeatureFlags);
const handleClick = () => alert("Payment successful!");
return (
<>
<button className="btn" onClick={handleClick}>
Credit Card
</button>
{features.isApplePayEnabled && (
<button className="btn" onClick={handleClick}>
Apple Pay
</button>
)}
{features.isGooglePayEnabled && (
<button className="btn" onClick={handleClick}>
Google Pay
</button>
)}
</>
);
};
export default PaymentOptions;
🚀 Now, we can launch this app with full control over the newly created features.
👏 We can enable Google Pay whenever we want and users will see it immediately. If something goes wrong, we can disable both the payment modes.
reply.send({ isGooglePayEnabled: false, isApplePayEnabled: false});
One last thing before you leave, this implementation is the bare minimum. You can extend it to suit your team's needs. Few improvements that are on top of my mind are:
- Adding a
FeatureFlag
component, that takes a propfeature
and hides or renders the children based on that.
<FeatureFlag feature="isGooglePayEnabled">
<button onClick={handlePayment}>Google Pay</button>
</FeatureFlag>
- Adding a caching and fallback mechanism. What if your API call fails? In such a case, we can fallback to our cached version. This one is really interesting. 😉
🔗 Oh and here is the codesandbox link, if you want to play around.
That's all folks! 👋
I hope this article helped you in some way. Consider sharing it with others too.
🤙 If you'd like to chat about anything, DM me on Twitter or LinkedIn.
Top comments (1)
💡💡💡 Great