TL;DR
In this article I am going to show you how to activate specific features for specific users using feature flags in a ReactJS app.
Even though there are multiple services which would allow you to manage your feature flags, we are going to use SwitchFeat, a service that I am building open-source and in public.
Why is this useful?
- Gradually release features to subsets of users.
- Decouple deployments from releases.
- Safe guarding new feature with kill-switches.
- and many more...
Can you help?
I am trying to increase the visibility of my repo, could you help me by adding a star to the project? It would help me so much and it would give a big boost to publish more articles weekly! 🤩
https://github.com/switchfeat-com/switchfeat
Let's get started! 🔥
First thing first, create a React app in Typescript
npx create-react-app my-react-app --template typescript
Let's install some common dependancies
npm i --save @types/node @types/react @types/react-dom uuidv4
Running these two commands already give you a working React app which we can start modifying as desired.
This is the default App.tsx
which is going to hold our homepage:
const App : React.FC = () => {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.tsx</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
}
export default App;
Which looks like this:
To manage styles a bit more easily, we will use TailwindCSS, which allows to specify inline classes without going to touch css files.
I know this stuff Andy! Give me something new!
Let's protect our component ⛔
Now things become a bit more interesting. We are going to build a new component, SuperCoolCard
, which is going to hold some functionality which we want to gate and make available only to a specific segment of users.
Let's say that we want to show this card to users with an email address ending with @switchfeat.com
.
For the purpose of this article, the component is going to be super simple:
export const SuperCoolCard : React.FC = () => {
return (
<div className="rounded-lg shadow
bg-orange-500 p-10 mt-10">
<div>Super cool feature </div>
<button className="rounded-md bg-indigo-600
px-3.5 py-2.5 font-semibold text-white
shadow-sm hover:bg-indigo-500
text-lg mt-4">Let's start</button>
</div>
);
}
In order to gate the access to this component to a specific user segment, we can use the SwitchFeat API as follows:
const [showNewCard, setShowNewCard] = useState<boolean>(false);
const [userContext, setUserContext] =
useState<{}>({ username: 'a@switchfeat.com' });
useEffect(() =>
const formData = new FormData();
formData.append('flagKey', "new-card");
formData.append('flagContext', JSON.stringify(userContext));
formData.append('correlationId', uuidv4());
const evaluateFlag = () => {
fetch(`http://localhost:4000/api/sdk/flag/`, {
method: "POST",
headers: {
Accept: "application/json",
"Access-Control-Allow-Credentials": "true",
"Access-Control-Allow-Origin": "true"
},
body: formData
}).then(resp => {
return resp.json();
}).then(respJson => {
setShowNewCard(respJson.data.match);
}).catch(error => { console.log(error); });
};
evaluateFlag();
}, []);
First of all, we add a state to the page which will hold the information regarding if the card component should be shown or not.
This is a React construct which forces a re-render of the component if such state changes, to show the latest data.
The useEffect()
call is a side effect which, in this case, gets executed at first page load (or page refreshes). It is responsible of calling the SwitchFeat API via a POST Fetch request, to get the status of the requested flag based on the provided context.
In this example, the user context provided is: {email:'a@switchfeat.com'}
.
For the purpose of this article, I put the user context in the state of the component, but this should really depend on the current user data in a real scenario.
Last step is going to beadding the reference to the new component in the page behind the "protected" state:
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.tsx</code> and save to reload.
</p>
<a
className="App-link" href="https://reactjs.org"
target="_blank" rel="noopener noreferrer">Learn React
</a>
{showNewCard && <SuperCoolCard />}
</header>
</div>
);
What's going to happen now, is that when there is a state change in the value of showNewCard
, the whole component re-renders and the SuperCoolCard
component will either show up or not based on the API response from SwitchFeat. Cool right!?
SwitchFeat is under active development and I already planned to release an SDK for NodeJS, together with other languages over time. So the interaction with the API will be simplified even further.
Behind the scenes 😎
The SwitchFeat API, upon receiving the request, runs the following checks:
- Checks if the requested
flagKey
points to an existing flag. - Checks if the requested flag has any rule.
- Evaluates all available rules and checks if the provided context (in this case
{email:'a@switchfeat.com'}
), matches at least one segment condition. - Returns the match result or an error.
The final results 🚀
Here it is how the page looks like by default having the flag disabled.
Instead when the flag is enabled and there is a rule matching the context, our beautiful card can shine on the homepage!
What's even more important is that even if the flag stays enabled, but the context of the API call changes to something which is not matching any of the flag rules, the card will be removed from the DOM for those users.
So for example, providing a context like: {email:'a@google.com'}
, the result is going to be:
Well done! 🙌
You can now sit and relax knowing that any feature you want to silently deploy and release at your own time, are safely protected by your feature flags!
So.. Can you help? 😉
I hope this article was somehow interesting. If you could give a star to my repo would really make my day!
Top comments (0)