Our Feature Flags course is now live!
Feature FlagsNEW

Development Flags

Use this flag to hide unfinished work

Development flags help you hide unfinished work. A good scenario where you'll use this will be something like this:

  • You start using a new library for a new feature and setup a lot of config for it
  • But now the team wants to start using this library immediately
  • But you have an unfinished feature in your branch
  • Your options are to create a new branch and redo that entire setup again
  • Or hide the current unfinished work behind a development flag
  • Raise a PR and the team can maybe also use that unfinished work as a reference on how to use this library

Don't use development flags often

We don't recommend you use this kind of flag often as it adds friction to code reviews.

Install Mantine UI Library

You can run the below steps and ignore learning anything about Mantine. Just understand we are using a different UI library. Feature flags have nothing to do with the library you use at all!

So, the team decided that MUI is not the way to move forward and have agreed to use Mantine instead. The team wants you to first refresh the FeatureOne to use Mantine's Design.

Let us install it now:

pnpm add @mantine/core @mantine/hooks

Mantine needs postcss for styling, so let us install it too:

pnpm add -D postcss postcss-preset-mantine postcss-simple-vars

Also, create a postcss.config.cjs file for the configuration.

postcss.config.cjs
module.exports = {
  plugins: {
    'postcss-preset-mantine': {},
    'postcss-simple-vars': {
      variables: {
        'mantine-breakpoint-xs': '36em',
        'mantine-breakpoint-sm': '48em',
        'mantine-breakpoint-md': '62em',
        'mantine-breakpoint-lg': '75em',
        'mantine-breakpoint-xl': '88em',
      },
    },
  },
};

We then setup Mantine's Provider:

App.tsx
import AuthLayout from '@/layouts/auth-layout';
import About from '@/pages/about';
import Home from '@/pages/home';
import Login from '@/pages/login';
import Register from '@/pages/register';
import { MantineProvider } from '@mantine/core';
import '@mantine/core/styles.css';
import { Route, Routes } from 'react-router';
import AppLayout from './layouts/app-layout';
 
function App() {
  return (
    <MantineProvider>
      <Routes>
        <Route element={<AppLayout />}>
          <Route index element={<Home />} />
          <Route path="about" element={<About />} />
        </Route>
 
        <Route element={<AuthLayout />}>
          <Route path="login" element={<Login />} />
          <Route path="register" element={<Register />} />
        </Route>
      </Routes>
    </MantineProvider>
  );
}
 
export default App;

UI libraries interfere with each other

When it comes to using multiple UI libraries in the same app, things might look weird due to conflicts. These conflicts usually arise due to the default global styles each UI library applies. There are ways to handle this, but for this refactoring series, it is out of the scope.

Refreshing Feature One's Design

Great, now we have the neccessary setup for Mantine, and we can start using it in our app. As discussed, let us start to refresh the FeatureOne design. We will replace the MUI's paper with Mantine's Paper in FeatureOne to begin with.

The current FeatureOne code looks like this:

src/components/feature-one.tsx
import BuildIcon from '@mui/icons-material/Build';
import DescriptionIcon from '@mui/icons-material/Description';
import FolderIcon from '@mui/icons-material/Folder';
import {
  List,
  ListItem,
  ListItemIcon,
  ListItemText,
  Paper,
  Typography,
} from '@mui/material';
 
export default function FeatureOne() {
  return (
    <Paper elevation={3} sx={{ p: 3, borderLeft: '5px solid #f44336' }}>
      <Typography variant="h5" gutterBottom>
        Old Feature One
      </Typography>
      <Typography variant="body1" gutterBottom>
        This is the legacy version of Feature One with basic tools and layout.
      </Typography>
 
      <List>
        <ListItem>
          <ListItemIcon>
            <FolderIcon color="action" />
          </ListItemIcon>
          <ListItemText primary="Basic data list" />
        </ListItem>
        <ListItem>
          <ListItemIcon>
            <BuildIcon color="action" />
          </ListItemIcon>
          <ListItemText primary="Minimal controls" />
        </ListItem>
        <ListItem>
          <ListItemIcon>
            <DescriptionIcon color="action" />
          </ListItemIcon>
          <ListItemText primary="Static content only" />
        </ListItem>
      </List>
    </Paper>
  );
}

We start to duplicate the code to keep visual reference and start bringing in our Mantine Paper component.

src/components/feature-one.tsx
import { Paper } from '@mantine/core';
import BuildIcon from '@mui/icons-material/Build';
import DescriptionIcon from '@mui/icons-material/Description';
import FolderIcon from '@mui/icons-material/Folder';
import {
  List,
  ListItem,
  ListItemIcon,
  ListItemText,
  Paper as MUIPaper,
  Typography,
} from '@mui/material';
 
export default function FeatureOne() {
  return (
    <>
      <Paper shadow="xs" withBorder p="xl">
        <Typography variant="h5" gutterBottom>
          Old Feature One
        </Typography>
        <Typography variant="body1" gutterBottom>
          This is the legacy version of Feature One with basic tools and layout.
        </Typography>
 
        <List>
          <ListItem>
            <ListItemIcon>
              <FolderIcon color="action" />
            </ListItemIcon>
            <ListItemText primary="Basic data list" />
          </ListItem>
          <ListItem>
            <ListItemIcon>
              <BuildIcon color="action" />
            </ListItemIcon>
            <ListItemText primary="Minimal controls" />
          </ListItem>
          <ListItem>
            <ListItemIcon>
              <DescriptionIcon color="action" />
            </ListItemIcon>
            <ListItemText primary="Static content only" />
          </ListItem>
        </List>
      </Paper>
      {/* USING THIS AS VISUAL REFERENCE TILL THE FULL MIGRATION IS COMPLETE */}
      <MUIPaper elevation={3} sx={{ p: 3, borderLeft: '5px solid #f44336' }}>
        <Typography variant="h5" gutterBottom>
          Old Feature One
        </Typography>
        <Typography variant="body1" gutterBottom>
          This is the legacy version of Feature One with basic tools and layout.
        </Typography>
 
        <List>
          <ListItem>
            <ListItemIcon>
              <FolderIcon color="action" />
            </ListItemIcon>
            <ListItemText primary="Basic data list" />
          </ListItem>
          <ListItem>
            <ListItemIcon>
              <BuildIcon color="action" />
            </ListItemIcon>
            <ListItemText primary="Minimal controls" />
          </ListItem>
          <ListItem>
            <ListItemIcon>
              <DescriptionIcon color="action" />
            </ListItemIcon>
            <ListItemText primary="Static content only" />
          </ListItem>
        </List>
      </MUIPaper>
    </>
  );
}

Great, it looks like Mantine's paper works well. You decide to continue working on the other parts but now all of a sudden a high priority request has come in and the team needs to push a FeatureThree immediately due to a client request and they want it to use the new Mantine based UI.

useDevelopmentFlag and DevelopmentFlag

We tried to come up with the simplest example for this course, you'd have to understand that you might have to approach such a situation differently. There are other ways to preserve this work for example cherry picking some of your commits.

Now, all of your setup is in this branch and you don't want to redo this work just to create another branch. Again, assume you did a lot more work than what we just did above.

One quick solution that we have now is using a development flag to hide the unfinished work.

src/components/feature-one.tsx
import { Paper } from '@mantine/core';
import BuildIcon from '@mui/icons-material/Build';
import DescriptionIcon from '@mui/icons-material/Description';
import FolderIcon from '@mui/icons-material/Folder';
import {
  List,
  ListItem,
  ListItemIcon,
  ListItemText,
  Paper as MUIPaper,
  Typography,
} from '@mui/material';
 
function useDevelopmentFlag() {
  return process.env.NODE_ENV === 'development';
}
 
export default function FeatureOne() {
  const isDev = useDevelopmentFlag(); 
 
  return (
    <>
      {isDev && (
        <Paper shadow="xs" withBorder p="xl">
          <Typography variant="h5" gutterBottom>
            Old Feature One
          </Typography>
          <Typography variant="body1" gutterBottom>
            This is the legacy version of Feature One with basic tools and
            layout.
          </Typography>
 
          <List>
            <ListItem>
              <ListItemIcon>
                <FolderIcon color="action" />
              </ListItemIcon>
              <ListItemText primary="Basic data list" />
            </ListItem>
            <ListItem>
              <ListItemIcon>
                <BuildIcon color="action" />
              </ListItemIcon>
              <ListItemText primary="Minimal controls" />
            </ListItem>
            <ListItem>
              <ListItemIcon>
                <DescriptionIcon color="action" />
              </ListItemIcon>
              <ListItemText primary="Static content only" />
            </ListItem>
          </List>
        </Paper>
      )}
      {/* USING THIS AS VISUAL REFERENCE TILL THE FULL MIGRATION IS COMPLETE */}
      <MUIPaper elevation={3} sx={{ p: 3, borderLeft: '5px solid #f44336' }}>
        <Typography variant="h5" gutterBottom>
          Old Feature One
        </Typography>
        <Typography variant="body1" gutterBottom>
          This is the legacy version of Feature One with basic tools and layout.
        </Typography>
 
        <List>
          <ListItem>
            <ListItemIcon>
              <FolderIcon color="action" />
            </ListItemIcon>
            <ListItemText primary="Basic data list" />
          </ListItem>
          <ListItem>
            <ListItemIcon>
              <BuildIcon color="action" />
            </ListItemIcon>
            <ListItemText primary="Minimal controls" />
          </ListItem>
          <ListItem>
            <ListItemIcon>
              <DescriptionIcon color="action" />
            </ListItemIcon>
            <ListItemText primary="Static content only" />
          </ListItem>
        </List>
      </MUIPaper>
    </>
  );
}

That is it! This is the simplest of the flags we can implement. We can go one step ahead and create a wrapper component called DevelopmentFlag. Let us also separate out these into dedicated files.

src/components/use-development-flag
export default function useDevelopmentFlag() {
  return process.env.NODE_ENV === 'development';
}
src/components/development-flag.tsx
import useDevelopmentFlag from './use-development-flag';
 
export default function DevelopmentFlag({
  children,
}: {
  children: React.ReactNode;
}) {
  const isDev = useDevelopmentFlag();
 
  return isDev ? <>{children}</> : null;
}
src/components/feature-one.tsx
import { Paper } from '@mantine/core';
import BuildIcon from '@mui/icons-material/Build';
import DescriptionIcon from '@mui/icons-material/Description';
import FolderIcon from '@mui/icons-material/Folder';
import {
  List,
  ListItem,
  ListItemIcon,
  ListItemText,
  Paper as MUIPaper,
  Typography,
} from '@mui/material';
import DevelopmentFlag from './development-flag';
 
export default function FeatureOne() {
  return (
    <>
      <DevelopmentFlag>
        <Paper shadow="xs" withBorder p="xl">
          <Typography variant="h5" gutterBottom>
            Old Feature One
          </Typography>
          <Typography variant="body1" gutterBottom>
            This is the legacy version of Feature One with basic tools and
            layout.
          </Typography>
 
          <List>
            <ListItem>
              <ListItemIcon>
                <FolderIcon color="action" />
              </ListItemIcon>
              <ListItemText primary="Basic data list" />
            </ListItem>
            <ListItem>
              <ListItemIcon>
                <BuildIcon color="action" />
              </ListItemIcon>
              <ListItemText primary="Minimal controls" />
            </ListItem>
            <ListItem>
              <ListItemIcon>
                <DescriptionIcon color="action" />
              </ListItemIcon>
              <ListItemText primary="Static content only" />
            </ListItem>
          </List>
        </Paper>
      </DevelopmentFlag>
      {/* USING THIS AS VISUAL REFERENCE TILL THE FULL MIGRATION IS COMPLETE */}
      <MUIPaper elevation={3} sx={{ p: 3, borderLeft: '5px solid #f44336' }}>
        <Typography variant="h5" gutterBottom>
          Old Feature One
        </Typography>
        <Typography variant="body1" gutterBottom>
          This is the legacy version of Feature One with basic tools and layout.
        </Typography>
 
        <List>
          <ListItem>
            <ListItemIcon>
              <FolderIcon color="action" />
            </ListItemIcon>
            <ListItemText primary="Basic data list" />
          </ListItem>
          <ListItem>
            <ListItemIcon>
              <BuildIcon color="action" />
            </ListItemIcon>
            <ListItemText primary="Minimal controls" />
          </ListItem>
          <ListItem>
            <ListItemIcon>
              <DescriptionIcon color="action" />
            </ListItemIcon>
            <ListItemText primary="Static content only" />
          </ListItem>
        </List>
      </MUIPaper>
    </>
  );
}

Why do we have a hook and a component for development flag?

Not every thing in frontend development is UI-related. You might want to hide some unfinished work within functions where the hook value helps.

Now, this is the simplest example of a development flag. Though the usecase is quite limited, we wanted you to know you can do something like this.

Try it out

You can verify the flag working or not by either building the app with pnpm build && pnpm preview or just flip the condition to check during development. We recommend the former approach of building as it would help check for any leaks.

In the next, section we will start using the core feature flags to release our FeatureThree component and in the further sections, our entire app and the layouts will be behind a feature flag.

At this point, your code should be a good match to the branch of the repository: 1-development-flags

Last updated on

On this page