visit
You can find the code and video in the summary at the end.
You can
$ git clone [email protected]:mateuszsokola/next-feature-toggle-scaffolder.git
$ cd next-feature-toggle-scaffolder
$ npm install
$ npm run dev
Now, how can we implement the Feature Toggle mechanism here? React's Context API seems to be the easiest and most appropriate way.
<Page enabledFeatures={features} />
<PageLayout enabledFeatures={features} />
<NavigationBar enabledFeatures={features} />
<Link href="//freecodecamp.org/news/">
<Avatar enabledFeatures={features} />
</Link>
Now we can create a context for our feature toggle. We need to create a directory called context/
, and a new file inside this directory. We can call it FeatureToggleContext.ts
.
$ mkdir context
$ cd context
$ touch FeatureToggleContext.ts
// file: context/FeatureToggleContext.ts
import React from "react";
export const FeatureToggleContext = React.createContext({
// TypeScript will have hard time to determine its type,
// if we don't cast this array to an array of strings.
// Likely, we will end up with an array of never or any.
enabledFeatures: [] as string[],
});
Let's create a new file in the components/
directory called FeatureToggle.tsx
:
$ cd components
$ touch FeatureToggle.tsx
Now we can create a component that accepts two properties: children
and enabledFeatures
.
The children
is the main component of the application. If you created a React app using create-react-app
you might have noticed that the main component is called App
. Next.JS calls it MyApp
, and you can find it in the file pages/_app.tsx
.
The enabledFeatures
is the array of enabled features. We will use it later.
# file: compontents/FeatureToggle.tsx
import React from "react";
import { FeatureToggleContext } from "../context/FeatureToggleContext";
type Props = {
children: any;
enabledFeatures: string[];
}
export const FeatureToggle = ({ children, enabledFeatures }: Props) => {
return (
<FeatureToggleContext.Provider value={{ enabledFeatures }}>
{children}
</FeatureToggleContext.Provider>
)
}
The wrapped component is ready. Now we need to hook it into the main component. Let's open the file pages/_app.tsx.
We have only one toggleable feature – treasury_chart
. We need to add it to the list of enabled features. At the moment, we'll soft code this list, and pass it down directly to the provider to confirm we have access to the context. Later on, we are going to create a proper API for the feature toggle.
import { FeatureToggle } from "../components/FeatureToggle";
import "../styles/globals.css";
function MyApp({ Component, pageProps }) {
const enabledFeatures = ["treasury_chart"];
return (
<FeatureToggle enabledFeatures={enabledFeatures}>
<Component {...pageProps} />
</FeatureToggle>
);
}
export default MyApp;
Technically, the list of enabled features is available in the whole application now. But we have no interface to consume its value. Taking advantage of React hooks is the best way to expose it.
Let's create a new directory called hooks/
, and a new file called useFeatureToggle.ts
.
$ mkdir hooks
$ touch useFeatureToggle.ts
Let's think about the hook for a minute. We want to check the status of a given feature. If the feature is enabled we are going to render it, otherwise, we won't. So the hook should return a helper that checks if the requested feature is on the list of enabled features, right? Let's code it.
# file: hooks/useFeatureToggle.ts
import React, { useContext } from "react";
import { FeatureToggleContext } from "../context/FeatureToggleContext";
export const useFeatureToggle = () => {
// we need to read values defined in the FeatureToggleContext.
// In this case, we'll take only the array of enabled features.
const { enabledFeatures } = useContext(FeatureToggleContext);
const isEnabled = (featureName: string) => {
return enabledFeatures.includes(featureName);
}
// For consistency, We return an array of helpers,
// so we follow the pattern defined by the useState hook.
// It makes the code open for extensions,
// so no need to refactor the app when a new helper is added here.
return [
isEnabled,
];
}
The hook is ready. We can open the main page, and connect the Treasury chart into the Feature Toggle.
Let's open pages/index.tsx
.
We are going to use our custom hook to get access to the isEnabled
helper and use it to check if the feature is enabled.
const [isEnabled] = useFeatureToggle();
// ...
return (
/* ... /
{isEnabled("treasury_chart") && (<TreasuryChart />)}
/ ... */
);
The complete implementation looks like this:
# file: pages/index.tsx
import React from "react";
import Head from "next/head";
import { Layout } from "antd";
import { GdpChart, TreasuryChart } from "../components/Charts";
import { useFeatureToggle } from "../hooks/useFeatureToggle";
const { Header, Content } = Layout;
export default function Home() {
const [isEnabled] = useFeatureToggle();
return (
<Layout className="layout">
<Head>
<title>🚦 Feature Toggle in Next.js</title>
</Head>
<Header>
<div className="logo" />
</Header>
<Content className="content">
<GdpChart />
{isEnabled("treasury_chart") && (<TreasuryChart />)}
</Content>
</Layout>
);
}
Now, you can try out the application in your browser. Keep in mind that you will still see the second chart.
You can fool around with the feature name given to the isEnabled
helper. For example, you can make a typo, and the chart should disappear afterwards.
Let's create a .env
file in our project.
$ touch .env
Now, we can open this file and add a new variable. We can call it FEATURE_TREASURY_CHART
. We need to set its value to false
.
# file: .env
FEATURE_TREASURY_CHART=false
So we need to create a new file in the pages/api/
directory. Let's call it features.ts
.
// file: pages/api/features.ts
export default (req, res) => {
res.status(200).json([
// Your environment variables are available within the `process.env` object.
// IMPORTANT! All environment variable values are strings.
// So we cannot compare them with booleans, numbers and so on.
process.env.FEATURE_TREASURY_CHART === "true" ? "treasury_chart" : "",
])
}
[""]
The treasury_chart
wasn't added to the list of enabled features, because it is disabled. You can change the FEATURE_TREASURY_CHART
variable to true
, and restart your server to try it out.
We are good to connect the React part of the application to our brand new API. Before we do so, we'll need to install axios
.
Axios will help us to make HTTP requests in a more convenient way than fetch
. It supports error handling and types out of the box. We don't need to implement it on our own, so we can get straight to business. You might need to kill your server by hitting Control + C. Twice.
$ npm install --save axios
Let's create a new directory called services/
, and a new file called FeatureToggle.ts
.
// File: services/FeatureToggle.ts
import axios from "axios";
export const fetchFeatures = async () => {
try {
const { data } = await axios.get<string[]>("/api/features");
return data;
}
catch(e) {
console.log("Something went wrong");
}
return [] as string[];
}
OK. The feature API request is ready. We can actually trigger this function at the main component.
Open the file pages/_app.tsx
.
We need to make an API request like every other in React using the useEffect
and useState
hooks. We need to add the following snippet to the MyApp
component:
const [enabledFeatures, setFeatures] = useState<string[]>([]);
const processFeatures = async () => {
const features = await fetchFeatures();
setFeatures(features);
}
useEffect(() => {
processFeatures();
}, []);
The complete solution is here:
// file: pages/_app.tsx
import { useEffect, useState } from "react";
import { FeatureToggle } from "../components/FeatureToggleProvider";
import { fetchFeatures } from "../services/FeatureToggle";
import "../styles/globals.css";
function MyApp({ Component, pageProps }) {
const [enabledFeatures, setFeatures] = useState<string[]>([]);
const processFeatures = async () => {
const features = await fetchFeatures();
setFeatures(features);
}
useEffect(() => {
processFeatures();
}, []); // eslint-disable-line react-hooks/exhaustive-deps
return (
<FeatureToggle enabledFeatures={enabledFeatures}>
<Component {...pageProps} />
</FeatureToggle>
)
}
export default MyApp;
Hurray! We are done with coding. The final step is to deploy our app.
Now you need to select the GitHub repository you want to import. In my case, it is "next-ft-demo", but you might've called it something else. If you don't see your repository, you need to click on "Adjust GitHub App Permission" (the link below marked with the red ellipse):
We need to configure the project. Let's open "Environment Variables", add a new variable called FEATURE_TREASURY_CHART, and set its value to false. Then click on the "Add" button, and hit "Deploy":
The application is being deployed now. You should see the following screen once done. Just click on the "Go To Dashboard" button:
You can open your application by clicking the "Visit" button:
The application displays only one chart:
If you want to enable the Treasury chart you need to select the "Settings" tab, choose the "Environment variables" section, and set the FEATURE_TREASURY_CHART variable to true:
Once the deployment is completed you will see the Treasury chart on your website:
Also published on: