React Native Client SDK For Harness Feature Flags
Use this README to get started with our Feature Flags (FF) Client SDK for React Native. This guide outlines the basics
of getting started with the SDK and provides a full code sample for you to try out.
This sample doesn't include configuration options, for in depth steps and configuring the SDK, see
the React Native Client SDK Reference.
Requirements
To use this SDK, make sure you’ve:
- Installed Node.js v12 or a newer version
- Installed React.js v16.7 or a newer version
To follow along with our test code sample, make sure you’ve:
npx create-expo-app my-demo-app
cd my-demo-app
npm install
Installing the SDK
The first step is to install the FF SDK as a dependency in your application. To install using npm, use:
npm install @harnessio/ff-react-native-client-sdk
Or to install with yarn, use:
yarn add @harnessio/ff-react-native-client-sdk
Code Sample
The following is a complete code example using Expo that you can use to test the harnessappdemodarkmode
Flag you
created on the Harness Platform. When you run the code it will:
- Render a loading screen
- Connect to the FF service
- Retrieve all flags
- Access a flag using the
useFeatureFlag
hook - Access several flags using the
useFeatureFlags
hook
The following code can be placed in the src/App.js
file.
import { StyleSheet, Text, View } from 'react-native'
import { StatusBar } from 'expo-status-bar'
import {
FFContextProvider,
useFeatureFlag,
useFeatureFlags
} from '@harnessio/ff-react-native-client-sdk'
export default function App() {
return (
<View style={styles.container}>
<FFContextProvider
apiKey="YOUR_API_KEY"
target={{
identifier: 'reactnativeclientsdk',
name: 'ReactNativeClientSDK'
}}
>
<SingleFeatureFlag />
<MultipleFeatureFlags />
</FFContextProvider>
<StatusBar style="auto" />
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'orange',
alignItems: 'center',
justifyContent: 'center',
minHeight: '100%'
}
})
function SingleFeatureFlag() {
const flagValue = useFeatureFlag('harnessappdemodarkmode')
return (
<Text>The value of "harnessappdemodarkmode" is {JSON.stringify(flagValue)}</Text>
)
}
function MultipleFeatureFlags() {
const flags = useFeatureFlags()
return (
<>
<Text>Here are all our flags:</Text>
<Text>{JSON.stringify(flags, null, 2)}</Text>
</>
)
}
Async mode
By default, the React Native Client SDK will block rendering of children until the initial load of Feature Flags has
completed. This ensures that children have immediate access to all Flags when they are rendered. However, in some
circumstances it may be beneficial to immediately render the application and handle display of loading on a
component-by-component basis. The React Native Client SDK's asynchronous mode allows this by passing the
optional async
prop when connecting with the FFContextProvider
.
API
FFContextProvider
The FFContextProvider
component is used to set up the React context to allow your application to access Feature Flags
using the useFeatureFlag
and useFeatureFlags
hooks
and withFeatureFlags
HOC. At minimum, it requires
the apiKey
you have set up in your Harness Feature Flags account, and the target
. You can think of a target
as a
user.
The FFContextProvider
component also accepts an options
object, a fallback
component, an array
of initialEvaluations
, an onError
handler, and can be placed in Async mode using the async
prop.
The fallback
component will be displayed while the SDK is connecting and fetching your flags. The initialEvaluations
prop allows you pass an array of evaluations to use immediately as the SDK is authenticating and fetching flags.
The onError
prop allows you to pass an event handler which will be called whenever a network error occurs.
import { Text } from 'react-native'
import { FFContextProvider } from '@harnessio/ff-react-native-client-sdk'
function MyComponent() {
return (
<FFContextProvider
async={false} // OPTIONAL: whether or not to use async mode
apiKey="YOUR_API_KEY" // your SDK API key
target={{
identifier: 'targetId', // unique ID of the Target
name: 'Target Name', // name of the Target
attributes: { // OPTIONAL: key/value pairs of attributes of the Target
customAttribute: 'this is a custom attribute',
anotherCustomAttribute: 'this is something else'
}
}}
fallback={<Text>Loading...</Text>} // OPTIONAL: component to display when the SDK is connecting
options={{ // OPTIONAL: advanced configuration options
baseUrl: 'https://url-to-access-flags.com',
eventUrl: 'https://url-for-events.com',
debug: true,
eventsSyncInterval: 60000
}}
initialEvaluations={evals} // OPTIONAL: array of evaluations to use while fetching
onError={handler} // OPTIONAL: event handler to be called on network error
>
<CompontToDisplayAfterLoad />
</FFContextProvider>
)
}
useFeatureFlag
The useFeatureFlag
hook returns a single named flag value. An optional second argument allows you to set what value
will be returned if the flag does not have a value. By default useFeatureFlag
will return undefined
if the flag
cannot be found.
N.B. when rendered in Async mode, the default value will be returned until the Flags are retrieved.
Consider using the useFeatureFlagsLoading hook to determine when the SDK has finished
loading.
import { Text } from 'react-native'
import { useFeatureFlag } from '@harnessio/ff-react-native-client-sdk'
function MyComponent() {
const myFlagValue = useFeatureFlag('flagIdentifier', 'default value')
return <Text>My flag value is: {myFlagValue}</Text>
}
useFeatureFlags
The useFeatureFlags
hook returns an object of Flag identifier/Flag value pairs. You can pass an array of Flag
identifiers or an object of Flag identifier/default value pairs. If an array is used and a Flag cannot be found, the
returned value for the flag will be undefined
. If no arguments are passed, all Flags will be returned.
N.B. when rendered in Async mode, the default value will be returned until the Flags are retrieved.
Consider using the useFeatureFlagsLoading hook to determine when the SDK has finished
loading.
import { Text } from 'react-native'
import { useFeatureFlag } from '@harnessio/ff-react-native-client-sdk'
function MyComponent() {
const myFlagValues = useFeatureFlags()
return (
<>
<Text>My flag values are:</Text>
<Text>{JSON.stringify(myFlagValues, null, 2)}</Text>
</>
)
}
Get a subset of Flags
const myFlagValues = useFeatureFlags(['flag1', 'flag2'])
Get a subset of Flags with custom default values
const myFlagValues = useFeatureFlags({
flag1: 'defaultForFlag1',
flag2: 'defaultForFlag2'
})
useFeatureFlagsLoading
The useFeatureFlagsLoading
hook returns a boolean value indicating whether the SDK is currently loading Flags from the
server.
import { Text } from 'react-native'
import {
useFeatureFlagsLoading,
useFeatureFlags
} from '@harnessio/ff-react-native-client-sdk'
function MyComponent() {
const isLoading = useFeatureFlagsLoading()
const flags = useFeatureFlags()
if (isLoading) {
return <Text>Loading...</Text>
}
return (
<>
<Text>My flag values are:</Text>
<Text>{JSON.stringify(flags, null, 2)}</Text>
</>
)
}
useFeatureFlagsClient
The React Native Client SDK internally uses the Javascript Client SDK to communicate with Harness. Sometimes it can be
useful
to be able to access the instance of the Javascript Client SDK rather than use the existing hooks or higher-order
components (HOCs). The useFeatureFlagsClient
hook returns the current Javascript Client SDK instance that the React
Native Client SDK is using. This instance will be configured, initialized and have been hooked up to the various events
the
Javascript Client SDK provides.
import { Text } from 'react-native'
import {
useFeatureFlagsClient,
useFeatureFlagsLoading
} from '@harnessio/ff-react-native-client-sdk'
function MyComponent() {
const client = useFeatureFlagsClient()
const loading = useFeatureFlagsLoading()
if (loading || !client) {
return <Text>Loading...</Text>
}
return (
<Text>
My flag value is: {client.variation('flagIdentifier', 'default value')}
</Text>
)
}
ifFeatureFlag
The ifFeatureFlag
higher-order component (HOC) wraps your component and conditionally renders only when the named flag
is enabled or matches a specific value.
import { Text } from 'react-native'
import { ifFeatureFlag } from '@harnessio/ff-react-native-client-sdk'
function MyComponent() {
return <Text>This should render if the flag is on</Text>
}
const MyConditionalComponent = ifFeatureFlag('flag1')(MyComponent)
You can then use MyConditionalComponent
as a normal component, and only render if flag1
's value is truthy.
Conditionally with a specific value
import { Text } from 'react-native'
import { ifFeatureFlag } from '@harnessio/ff-react-native-client-sdk'
function MyComponent() {
return <Text>This should render if the flag evaluates to 'ABC123'</Text>
}
const MyConditionalComponent = ifFeatureFlag('flag1', { matchValue: 'ABC123' })(
MyComponent
)
You can then use MyConditionalComponent
as a normal component, and only render if flag1
's value matches the passed
condition.
Loading fallback when in async mode
If Async mode is used, by default the component will wait for Flags to be retrieved before showing. This
behaviour can be overridden by passing an element as loadingFallback
; when loading the loadingFallback
will be
displayed until the Flags are retrieved, at which point the component will either show or hide as normal.
import { Text } from 'react-native'
import { ifFeatureFlag } from '@harnessio/ff-react-native-client-sdk'
function MyComponent() {
return <Text>This should render if the flag is on</Text>
}
const MyConditionalComponent = ifFeatureFlag('flag1', {
loadingFallback: <Text>Loading...</Text>
})(MyComponent)
withFeatureFlags
The withFeatureFlags
higher-order component (HOC) wraps your component and adds flags
and loading
as additional
props. flags
contains the evaluations for all known flags and loading
indicates whether the SDK is actively fetching
Flags.
import { Text } from 'react-native'
import { withFeatureFlags } from '@harnessio/ff-react-native-client-sdk'
function MyComponent({ flags }) {
return <Text>Flag1's value is {flags.flag1}</Text>
}
const MyComponentWithFlags = withFeatureFlags(MyComponent)
Loading in async mode
If Async mode is used, the loading
prop will indicate whether the SDK has completed loading the Flags.
When loading completes, the loading
prop will be false
and the flags
prop will contain all known Flags.
import { Text } from 'react-native'
import { withFeatureFlags } from '@harnessio/ff-react-native-client-sdk'
function MyComponent({ flags, loading }) {
if (loading) {
return <Text>Loading...</Text>
}
return <Text>Flag1's value is {flags.flag1}</Text>
}
const MyComponentWithFlags = withFeatureFlags(MyComponent)
withFeatureFlagsClient
The React Native Client SDK internally uses the Javascript Client SDK to communicate with Harness. Sometimes it can be
useful to be able to access the instance of the Javascript Client SDK rather than use the existing hooks or higher-order
components (HOCs). The withFeatureFlagsClient
HOC wraps your component and adds featureFlagsClient
as additional
prop. featureFlagsClient
is the current Javascript Client SDK instance that the React Native Client SDK is using. This
instance will be configured, initialized and have been hooked up to the various events the Javascript Client SDK
provides.
import { Text } from 'react-native'
import { withFeatureFlagsClient } from '@harnessio/ff-react-native-client-sdk'
function MyComponent({ featureFlagsClient }) {
if (featureFlagsClient) {
return (
<Text>
Flag1's value is {featureFlagsClient.variation('flag1', 'no value')}
</Text>
)
}
return <Text>The Feature Flags client is not currently available</Text>
}
const MyComponentWithClient = withFeatureFlagsClient(MyComponent)
Testing with Jest
When running tests with Jest, you may want to mock the SDK to avoid making network requests. You can do this by using
the included TestWrapper
component. This component accepts a listing of flags and their values, and will mock the SDK
to return those values. In the example below, we use Testing Library to render the component <MyComponent />
that
internally uses the useFeatureFlag
hook.
N.B. to use the TestWrapper
component, you must import it from the dist/cjs/test-utils
directory, not from the
main package.
import { render, screen } from '@testing-library/react'
import { TestWrapper } from '@harnessio/ff-react-native-client-sdk/dist/cjs/test-utils'
test('it should render the flag value', () => {
render(
<TestWrapper flags={{ flag1: 'value1', flag2: 'value2' }}>
<MyComponent />
</TestWrapper>
)
expect(screen.getByText('value1')).toBeInTheDocument()
})
Additional Reading
For further examples and config options, see
the React Native Client SDK Reference
For more information about Feature Flags, see
our Feature Flags documentation.