Building a feature guard component in React
This is a sample simple component.
At Sqore we want to release features fast, test it on some users, and either rollback (disable that feature) or roll it out to all users. To do this we use FeatureFlippers.

A feature flipper is a just a name as a string that is either present or not to the user. Our backend is built around a ruby gem called flipper. But it doesn’t matter. In redux all we store is an array of strings, for example:
[ ‘new-button-design’, ‘login-button-placement-test-1’ ]So to know if a certain feature is enabled we can check if that array contains a certain string.
return array.includes(feature_name)Easy right?
To reduce duplication we build a component for this, FeatureGuard.
You wrap you UI in a FeatureGuard and that either renders or not. Simple and effective. The only input needed is the feature name, and since it is connected to the redux store it can fetch all data it needs from there.
<FeatureGuard feature=”feature.name.1">
<div>
<h3>Create an account</h3>
<Link to=”/login”>
Login
</Link>
</div>
<FeatureGuard>Component
The component is simple. It renders children if the flag is enabled, and on mount it calls ensureLoaded action.
class FeatureGuard extends React.Component {
componentDidMount() {
this.props.ensureLoaded()
}
render() {
if (this.props.enabled) return this.props.children
return null
}
}ensureLoaded fetches the list of features if not already fetched.
const mapDispatchToProps = dispatch => {
return {
ensureLoaded: () => dispatch(ensureFeaturesLoaded())
}
}You will have to ensure that it does not fetch every time by keeping a loaded/loading state in the reducer.
enabled is calculated from the state. It is just a boolean we calculate.
const mapStateToProps = (state, props) => {
return {
enabled: isEnabled(state, props.feature)
}
}State
To make all this work we need a reducer, actions and selectors.
The first thing that happens when the component is mounted is that we dispatch ensureFeaturesLoaded().
function ensureFeaturesLoaded() {
return (dispatch, getState) => {
const state = getState()
const shouldNotFetch = isLoaded(state) || isLoading(state) if (shouldNotFetch) return
dispatch({ type: FEATURES_FETCH_REQUEST }) fetchFeatures()
.then((response) => {
dispatch(
FeaturesFetched({ enabledFeatures: response.features })
)
})
}
}
Some logic to make sure we do not fetch the list of features multiple times.
Time for the reducer!
When we start fetching we dispatch FEATURES_FETCH_REQUEST.
case FEATURES_FETCH_REQUEST: {
return {
…state,
status: ‘LOADING’,
}
}That stops the next guarded component from also fetching the list.
When we get the results back:
return {
…state,
features: payload.enabledFeatures,
status: ‘LOADED’,
}Selectors
export const isEnabled = (state, feature) => {
return state.features.enabledFeatures.includes(feature)
}export const isLoaded = state => {
return state.features.status === ‘LOADED’
}export const isLoading = state => {
return state.features.status === ‘LOADING’
}
Tests!
Testing connected components can be difficult. However, you can fake your way around it.
Instead of exporting only a connected component, export two!
export { FeatureGuard }
export default connect(
mapStateToProps,
mapDispatchToProps
)(FeatureGuard)Our connected component will provide a enabled and a ensureLoaded function.
So, assuming mapStateToProps and mapDispatchToProps works, we can test our named export.
In this test we import the named component
import { FeatureGuard } from ‘./FeatureGuard’but in our application we would import the default component
import FeatureGuard from ‘./FeatureGuard’It will now be easy to test the different cases for our dumb component.
First things first, we need to make sure that the component fetches the feature list.
describe(‘lifecycle’, () => {
it(‘tries to load feature list’, () => {
const spy = sinon.spy() const component = mount(
<FeatureGuard feature=”my.feature” ensureLoaded={ spy }>
<span>Content</span>
</FeatureGuard>
) expect(spy.calledOnce).to.eq(true)
})
})
By using `mount` from `enzyme` we can test the lifecycle methods of our class. We use sinon to spy on the action. We do not care if it actually does anything. As long it is called this component is working perfectly.
We should write other tests those thunk actions.
Continuing on testing this component we realize it does only two other things. It either renders the children or not.
So let’s test that.
describe(‘shows/hides content’, () => {
it(‘hides components by default’, () => {
const wrapper = shallow(
<FeatureGuard feature=”unknown”>
<span>Secret content</span>
</FeatureGuard>
) expect(wrapper.type()).to.eq(null)
}) it(‘renders children when enabled ‘, () => {
const wrapper = shallow(
<FeatureGuard feature=”enbaled.feature” enabled>
<span>Secret content</span>
</FeatureGuard>
) expect(wrapper.type()).to.eq(‘span’)
})
})
We test the component twice, once with enabled and one without.
That’s it.
The component is tested and working. You should also ensure you test the async action. (Might be a later article)
Conclusion
A simple little feature guard component, but it is very useful. Both as an example of how to write components and tests, but also in production systems.
