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/hooksMantine needs postcss for styling, so let us install it too:
pnpm add -D postcss postcss-preset-mantine postcss-simple-varsAlso, create a postcss.config.cjs file for the configuration.
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:
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:
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.
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.
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.
export default function useDevelopmentFlag() {
return process.env.NODE_ENV === 'development';
}import useDevelopmentFlag from './use-development-flag';
export default function DevelopmentFlag({
children,
}: {
children: React.ReactNode;
}) {
const isDev = useDevelopmentFlag();
return isDev ? <>{children}</> : null;
}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