Feature flag (or feature toggle, feature switch…)
The idea is to be able to enable or disable features on the fly.
They introduce a certain complexity, but it does not mean you need a third-party service to use feature flag though.
Tip #1: The right tool for the right job.
// our data
["feature1", "feature2"]
// our schema
type Feature {
name: String!
}
type Query {
enabledFeatures: [Feature!]!
}
Tip #2: We expose functionality without exposing implementation details.
type Feature {
name: String!
}
type Query {
enabledFeatures: [Feature!]!
}
Tip #3:
I will be using Apollo Client as I'm familiar with the API, but it does not matter that much library you use.
const client = new ApolloClient({
uri: "https://7jxju.sse.codesandbox.io/"
});
const App = () => (
<ApolloProvider client={client}>
<div className="App">
<h1>Hello Feature Flag</h1>
</div>
</ApolloProvider>
);
export default App;
const QUERY = gql`
query {
enabledFeatures {
name
}
}
`;
function ListFeatureFlags() {
const {
loading,
error,
data
} = useQuery(QUERY);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
return (
<div>
<h2>List of features:</h2>
<ul>
{data.enabledFeatures.map(feature => (
<li key={feature.name}>
<p>{feature.name}</p>
</li>
))}
</ul>
</div>
);
}
We want to know if a certain feature is enabled for the user.
We want to use that in multiple components.
So let's create a hook. Functions that let you “hook into” React state and lifecycle features from function components. You could use HoC, render props, etc.
function useFeatureFlag(name) {
const {
loading,
error,
data
} = useQuery(QUERY);
if (!data) return { error, loading };
const enabled = data.enabledFeatures.some(feature => feature.name === name);
const feature = {
error,
enabled
};
return feature;
}
We can render a different component if a certain feature is enabled.
function Menu() {
const flagName = "feature3";
const feature = useFeatureFlag(flagName);
if (feature.enabled === undefined) {
return <p>Loading {flagName}...</p>;
}
if (feature.enabled) {
return <h2>New Menu - {flagName} is enabled 🚀</h2>;
}
return <h2>Regular Menu - {flagName} is disabled 🚫</h2>;
}
If we have only feature1 and feature2 enabled when querying feature3 we should see the disabled message.
Similarly, if we query feature2 or feature1 we should see the enabled message.
Apollo Client comes by default with an in-memory cache implementation.
We could rely on this behaviour.
The trade-off here is to slow down the overall load in favour of rendering the dependents way faster later.
function EnabledFeatures({
children
}) {
const {
loading,
error
} = useQuery(QUERY);
if (loading) {
return <p>Loading...</p>;
}
if (error) {
return <p>Error: {error}</p>;
}
return (
<React.Fragment>
{children}
</React.Fragment>
);
}
Tip #4: Start it simple.
type Feature {
name: String!
variations: [String!]!
}
function Component {
const feature = useFeature(
"feature2",
{ fetchPolicy: "network-only" }
);
// ...
}
type Query {
isFeatureEnabled(name: String!): Boolean!
}