Klor.

React Native

The same package as the web. Two options need wiring up, because Klor deliberately does not depend on react-native.

Why there are two options

@klor/react has no React Native dependency, not AsyncStorage, not MMKV, not react-native itself. That is what lets one package serve both platforms without a native module or a second install. The cost is that you pass in the two things only your app knows how to provide: where to cache, and how to tell when the app comes back to the foreground.

Setup

klor.ts
import AsyncStorage from '@react-native-async-storage/async-storage'
import { AppState } from 'react-native'
import { createKlorClient } from '@klor/react'

export const klor = createKlorClient({
apiKey: 'klor_pub_…',

// Any object with getItem/setItem. AsyncStorage and MMKV both fit as-is.
storage: AsyncStorage,

// Config is refreshed whenever the app returns to the foreground, which is
// when a user is most likely to see something stale.
subscribeToForeground: (refresh) => {
const subscription = AppState.addEventListener('change', (state) => {
if (state === 'active') refresh()
})
return () => subscription.remove()
},
})

Without storage, Klor falls back to memory, so a cold start with no network serves your fallbacks instead of the last known config. On mobile that is the case you most want covered.

Context

Pass the platform and the running version so rules and update gating have something to match on.

import { Platform } from 'react-native'
import DeviceInfo from 'react-native-device-info'

<KlorProvider
client={klor}
context={{
userId: user.id,
attributes: {
platform: Platform.OS, // 'ios' | 'android'
appVersion: DeviceInfo.getVersion(),
osVersion: String(Platform.Version),
},
}}
>
<App />
</KlorProvider>

Any source for the version works: expo-constants, a generated constant, or a native module. Klor only needs a string it can parse.

Update gating

Klor ships no UI. useVersionGate returns a verdict and the copy to show; the prompt is yours, so it looks like the rest of your app.

import { useVersionGate } from '@klor/react'

function UpdateGate({ children }) {
const gate = useVersionGate()

if (gate.status === 'forced') {
return (
<Blocking
message={gate.message}
onPress={() => Linking.openURL(gate.storeUrl)}
/>
)
}

return (
<>
{gate.status === 'optional' && <UpdateBanner message={gate.message} url={gate.storeUrl} />}
{children}
</>
)
}

Platform and version come from the provider context by default. Pass them explicitly if you need to.

Offline behaviour

With storage wired up, the last snapshot survives a restart. A launch with no connection serves the config from the last successful sync, and a failed refresh never discards what is already in hand. useKlor().isStale tells you when that is happening.