Remove AsyncStorage from rn-tester and fix InternalSettings Example

Summary:
Changelog:
[**General**][**Removed**] - Removed AsyncStorage usage from RNTester

As part of the "Lean Core" efforts (see https://github.com/react-native-community/discussions-and-proposals/issues/6) to remove outdated and/or unused components (status: https://gist.github.com/Simek/88a9f1a014a47c37f4fce3738864d2e1), this diff removes usage of the deprecated AsyncStorage API from RNTester.

RNTester is intended as a reference to showcase various components and APIs. The implications of the replacement of AsyncStorage in RNTester with in-memory management of state is a tradeoff of persistance to a lighter weight implementation and user predictable behavior.

1. Removed AsyncStorage from rn-tester
  - removed Navigation and bookmark persisting from reducer
  - moved JS Stalls and tracking to application state with context and reducer
2. Fixed InternalSettings Example bugs

Reviewed By: lunaleaps

Differential Revision: D35435562

fbshipit-source-id: a879787d8683a1c452e5b6b75a9e01f3ceadfe5d
This commit is contained in:
J.T. Yim 2022-04-19 15:34:40 -07:00 коммит произвёл Facebook GitHub Bot
Родитель 361b9a808c
Коммит f4c4f446e4
11 изменённых файлов: 323 добавлений и 322 удалений

Просмотреть файл

@ -23,29 +23,29 @@ import RNTesterNavBar, {navBarHeight} from './components/RNTesterNavbar';
import RNTesterList from './utils/RNTesterList';
import {
Screens,
initialState,
initialNavState,
getExamplesListWithBookmarksAndRecentlyUsed,
getInitialStateFromAsyncStorage,
} from './utils/testerStateUtils';
import {useAsyncStorageReducer} from './utils/useAsyncStorageReducer';
import {RNTesterReducer, RNTesterActionsType} from './utils/RNTesterReducer';
import {
RNTesterNavReducer,
RNTesterNavActionsType,
} from './utils/RNTesterNavReducer';
import {RNTesterThemeContext, themes} from './components/RNTesterTheme';
import RNTTitleBar from './components/RNTTitleBar';
import {RNTesterEmptyBookmarksState} from './components/RNTesterEmptyBookmarksState';
import {RNTesterJsStallsProvider} from './utils/RNTesterJsStallsContext';
const APP_STATE_KEY = 'RNTesterAppState.v3';
// RNTester App currently uses AsyncStorage from react-native for storing navigation state
// RNTester App currently uses in memory storage for storing navigation state
// and bookmark items.
// TODO: Vendor AsyncStorage or create our own.
// TODO: Identify/implement solution for async device storage.
LogBox.ignoreLogs([/AsyncStorage has been extracted from react-native/]);
const RNTesterApp = (): React.Node => {
const [state, dispatch] = useAsyncStorageReducer(
RNTesterReducer,
initialState,
APP_STATE_KEY,
const [state, dispatch] = React.useReducer<Function, Object>(
RNTesterNavReducer,
initialNavState,
);
const colorScheme = useColorScheme();
const {
@ -57,17 +57,6 @@ const RNTesterApp = (): React.Node => {
recentlyUsed,
} = state;
React.useEffect(() => {
getInitialStateFromAsyncStorage(APP_STATE_KEY).then(
initialStateFromStorage => {
dispatch({
type: RNTesterActionsType.INIT_FROM_STORAGE,
data: initialStateFromStorage,
});
},
);
}, [dispatch]);
const examplesList = React.useMemo(
() =>
getExamplesListWithBookmarksAndRecentlyUsed({bookmarks, recentlyUsed}),
@ -76,7 +65,7 @@ const RNTesterApp = (): React.Node => {
const handleBackPress = React.useCallback(() => {
if (activeModuleKey != null) {
dispatch({type: RNTesterActionsType.BACK_BUTTON_PRESS});
dispatch({type: RNTesterNavActionsType.BACK_BUTTON_PRESS});
}
}, [dispatch, activeModuleKey]);
@ -103,7 +92,7 @@ const RNTesterApp = (): React.Node => {
const handleModuleCardPress = React.useCallback(
({exampleType, key, title}) => {
dispatch({
type: RNTesterActionsType.MODULE_CARD_PRESS,
type: RNTesterNavActionsType.MODULE_CARD_PRESS,
data: {exampleType, key, title},
});
},
@ -113,7 +102,7 @@ const RNTesterApp = (): React.Node => {
const handleModuleExampleCardPress = React.useCallback(
exampleName => {
dispatch({
type: RNTesterActionsType.EXAMPLE_CARD_PRESS,
type: RNTesterNavActionsType.EXAMPLE_CARD_PRESS,
data: {key: exampleName},
});
},
@ -123,7 +112,7 @@ const RNTesterApp = (): React.Node => {
const toggleBookmark = React.useCallback(
({exampleType, key}) => {
dispatch({
type: RNTesterActionsType.BOOKMARK_PRESS,
type: RNTesterNavActionsType.BOOKMARK_PRESS,
data: {exampleType, key},
});
},
@ -133,7 +122,7 @@ const RNTesterApp = (): React.Node => {
const handleNavBarPress = React.useCallback(
args => {
dispatch({
type: RNTesterActionsType.NAVBAR_PRESS,
type: RNTesterNavActionsType.NAVBAR_PRESS,
data: {screen: args.screen},
});
},
@ -170,40 +159,42 @@ const RNTesterApp = (): React.Node => {
return (
<RNTesterThemeContext.Provider value={theme}>
<RNTTitleBar
title={title}
theme={theme}
onBack={activeModule ? handleBackPress : null}
documentationURL={activeModule?.documentationURL}
/>
<View
style={StyleSheet.compose(styles.container, {
backgroundColor: theme.GroupedBackgroundColor,
})}>
{activeModule != null ? (
<RNTesterModuleContainer
module={activeModule}
example={activeModuleExample}
onExampleCardPress={handleModuleExampleCardPress}
/>
) : screen === Screens.BOOKMARKS &&
examplesList.bookmarks.length === 0 ? (
<RNTesterEmptyBookmarksState />
) : (
<RNTesterModuleList
sections={activeExampleList}
toggleBookmark={toggleBookmark}
handleModuleCardPress={handleModuleCardPress}
/>
)}
</View>
<View style={styles.bottomNavbar}>
<RNTesterNavBar
screen={screen || Screens.COMPONENTS}
isExamplePageOpen={!!activeModule}
handleNavBarPress={handleNavBarPress}
<RNTesterJsStallsProvider>
<RNTTitleBar
title={title}
theme={theme}
onBack={activeModule ? handleBackPress : null}
documentationURL={activeModule?.documentationURL}
/>
</View>
<View
style={StyleSheet.compose(styles.container, {
backgroundColor: theme.GroupedBackgroundColor,
})}>
{activeModule != null ? (
<RNTesterModuleContainer
module={activeModule}
example={activeModuleExample}
onExampleCardPress={handleModuleExampleCardPress}
/>
) : screen === Screens.BOOKMARKS &&
examplesList.bookmarks.length === 0 ? (
<RNTesterEmptyBookmarksState />
) : (
<RNTesterModuleList
sections={activeExampleList}
toggleBookmark={toggleBookmark}
handleModuleCardPress={handleModuleCardPress}
/>
)}
</View>
<View style={styles.bottomNavbar}>
<RNTesterNavBar
screen={screen || Screens.COMPONENTS}
isExamplePageOpen={!!activeModule}
handleNavBarPress={handleNavBarPress}
/>
</View>
</RNTesterJsStallsProvider>
</RNTesterThemeContext.Provider>
);
};

Просмотреть файл

@ -8,38 +8,10 @@
* @flow strict-local
*/
'use strict';
const RNTesterStatePersister = require('../utils/RNTesterStatePersister');
const React = require('react');
const {StyleSheet, Switch, Text, View} = require('react-native');
class RNTesterSettingSwitchRow extends React.Component<
$FlowFixMeProps,
$FlowFixMeState,
> {
UNSAFE_componentWillReceiveProps(newProps: $FlowFixMeProps) {
const {onEnable, onDisable, persister} = this.props;
if (newProps.persister.state !== persister.state) {
newProps.persister.state ? onEnable() : onDisable();
}
}
render(): React.Node {
const {label, persister} = this.props;
return (
<View style={styles.row}>
<Text>{label}</Text>
<Switch
value={persister.state}
onValueChange={value => {
persister.setState(() => value);
}}
/>
</View>
);
}
}
const styles = StyleSheet.create({
row: {
padding: 10,
@ -47,15 +19,22 @@ const styles = StyleSheet.create({
justifyContent: 'space-between',
},
});
/* $FlowFixMe[cannot-reassign-export] (>=0.85.0 site=react_native_fb) This
* comment suppresses an error found when Flow v0.85 was deployed. To see the
* error, delete this comment and run Flow. */
// $FlowFixMe[cannot-reassign]
RNTesterSettingSwitchRow = RNTesterStatePersister.createContainer(
RNTesterSettingSwitchRow,
{
cacheKeySuffix: ({label}) => 'Switch:' + label,
getInitialState: ({initialValue}) => initialValue,
},
);
module.exports = RNTesterSettingSwitchRow;
export const RNTesterSettingSwitchRow = ({
label,
onEnable,
onDisable,
active,
}: $FlowFixMeProps): React.Node => {
return (
<View style={styles.row}>
<Text>{label}</Text>
<Switch
value={active}
onValueChange={() => {
active ? onDisable() : onEnable();
}}
/>
</View>
);
};

Просмотреть файл

@ -158,74 +158,45 @@ class LoopExample extends React.Component<{...}, $FlowFixMeState> {
}
}
const RNTesterSettingSwitchRow = require('../../components/RNTesterSettingSwitchRow');
class InternalSettings extends React.Component<
{...},
{
busyTime: number | string,
filteredStall: number,
...
},
> {
_stallInterval: ?number;
render() {
return (
<View>
<RNTesterSettingSwitchRow
initialValue={false}
label="Force JS Stalls"
onEnable={() => {
/* $FlowFixMe[incompatible-type] (>=0.63.0 site=react_native_fb)
* This comment suppresses an error found when Flow v0.63 was
* deployed. To see the error delete this comment and run Flow. */
this._stallInterval = setInterval(() => {
const start = Date.now();
console.warn('burn CPU');
while (Date.now() - start < 100) {}
}, 300);
}}
onDisable={() => {
/* $FlowFixMe[incompatible-call] (>=0.63.0 site=react_native_fb)
* This comment suppresses an error found when Flow v0.63 was
* deployed. To see the error delete this comment and run Flow. */
clearInterval(this._stallInterval || 0);
}}
/>
<RNTesterSettingSwitchRow
initialValue={false}
label="Track JS Stalls"
onEnable={() => {
require('react-native/Libraries/Interaction/JSEventLoopWatchdog').install(
{
thresholdMS: 25,
},
);
this.setState({busyTime: '<none>'});
require('react-native/Libraries/Interaction/JSEventLoopWatchdog').addHandler(
{
onStall: ({busyTime}) =>
this.setState(state => ({
busyTime,
filteredStall:
(state.filteredStall || 0) * 0.97 + busyTime * 0.03,
})),
},
);
}}
onDisable={() => {
console.warn('Cannot disable yet....');
}}
/>
{this.state && (
<Text>
{`JS Stall filtered: ${Math.round(this.state.filteredStall)}, `}
{`last: ${this.state.busyTime}`}
</Text>
)}
</View>
);
}
}
const RNTesterSettingSwitchRow =
require('../../components/RNTesterSettingSwitchRow').RNTesterSettingSwitchRow;
const RNTesterJsStallsContext = require('../../utils/RNTesterJsStallsContext');
const InternalSettings = () => {
const {
state,
onDisableForceJsStalls,
onEnableForceJsStalls,
onEnableJsStallsTracking,
onDisableJsStallsTracking,
} = React.useContext(RNTesterJsStallsContext.RNTesterJsStallsContext);
const {stallInterval, busyTime, filteredStall, tracking} = state;
return (
<View>
<RNTesterSettingSwitchRow
initialValue={false}
label="Force JS Stalls"
active={stallInterval !== null}
onEnable={onEnableForceJsStalls}
onDisable={onDisableForceJsStalls}
/>
<RNTesterSettingSwitchRow
initialValue={false}
label="Track JS Stalls"
active={tracking}
onEnable={onEnableJsStallsTracking}
onDisable={onDisableJsStallsTracking}
/>
{tracking && (
<Text>
{`JS Stall filtered: ${Math.round(filteredStall)}, `}
{`last: ${busyTime !== null ? busyTime.toFixed(8) : '<none>'}`}
</Text>
)}
</View>
);
};
class EventExample extends React.Component<{...}, $FlowFixMeState> {
state = {

Просмотреть файл

@ -59,7 +59,7 @@ export type ScreenTypes = 'components' | 'apis' | 'bookmarks' | null;
export type ComponentList = null | {components: string[], apis: string[]};
export type RNTesterState = {
export type RNTesterNavState = {
activeModuleKey: null | string,
activeModuleTitle: null | string,
activeModuleExampleKey: null | string,
@ -67,3 +67,10 @@ export type RNTesterState = {
bookmarks: ComponentList,
recentlyUsed: ComponentList,
};
export type RNTesterJsStallsState = {
stallInterval: null | number,
busyTime: null | number,
filteredStall: number,
tracking: boolean,
};

Просмотреть файл

@ -0,0 +1,35 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow
*/
import React from 'react';
import type {Context, Element} from 'react';
import useJsStallsReducer from './useJsStallsReducer';
const RNTesterJsStallsContext: Context<any> = React.createContext();
const RNTesterJsStallsProvider = (props: $FlowFixMeProps): Element<any> => {
const {
state,
onEnableForceJsStalls,
onDisableForceJsStalls,
onEnableJsStallsTracking,
onDisableJsStallsTracking,
} = useJsStallsReducer();
const context = {
state,
onEnableForceJsStalls,
onDisableForceJsStalls,
onEnableJsStallsTracking,
onDisableJsStallsTracking,
};
return <RNTesterJsStallsContext.Provider {...props} value={context} />;
};
export {RNTesterJsStallsContext, RNTesterJsStallsProvider};

Просмотреть файл

@ -0,0 +1,56 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow
*/
import type {RNTesterJsStallsState} from '../types/RNTesterTypes';
export const RNTesterJsStallsActionsType = {
START_JS_STALLS: 'START_JS_STALLS',
STOP_JS_STALLS: 'STOP_JS_STALLS',
START_TRACK_JS_STALLS: 'START_TRACK_JS_STALLS',
UPDATE_TRACK_JS_STALLS: 'UPDATE_TRACK_JS_STALLS',
STOP_TRACK_JS_STALLS: 'STOP_TRACK_JS_STALLS',
};
export const RNTesterJsStallsReducer = (
state: RNTesterJsStallsState,
action: {type: string, data: RNTesterJsStallsState},
): RNTesterJsStallsState => {
const {type, data} = action;
switch (type) {
case RNTesterJsStallsActionsType.START_JS_STALLS:
case RNTesterJsStallsActionsType.STOP_JS_STALLS:
return {
...state,
stallInterval: data.stallInterval,
};
case RNTesterJsStallsActionsType.START_TRACK_JS_STALLS:
return {
...state,
tracking: true,
};
case RNTesterJsStallsActionsType.UPDATE_TRACK_JS_STALLS:
if (!state.stallInterval) {
return {
...state,
};
}
return {
...state,
busyTime: data.busyTime || state.busyTime,
filteredStall: state.filteredStall * 0.97 + (data.busyTime || 0) * 0.03,
};
case RNTesterJsStallsActionsType.STOP_TRACK_JS_STALLS:
return {
...state,
};
default:
throw new Error(`Invalid action type ${action.type}`);
}
};

Просмотреть файл

@ -8,9 +8,9 @@
* @flow
*/
import type {RNTesterState, ComponentList} from '../types/RNTesterTypes';
import type {RNTesterNavState, ComponentList} from '../types/RNTesterTypes';
export const RNTesterActionsType = {
export const RNTesterNavActionsType = {
INIT_FROM_STORAGE: 'INIT_FROM_STORAGE',
NAVBAR_PRESS: 'NAVBAR_PRESS',
BOOKMARK_PRESS: 'BOOKMARK_PRESS',
@ -67,14 +67,14 @@ const getUpdatedRecentlyUsed = ({
return updatedRecentlyUsed;
};
export const RNTesterReducer = (
state: RNTesterState,
export const RNTesterNavReducer = (
state: RNTesterNavState,
action: {type: string, data: any},
): RNTesterState => {
): RNTesterNavState => {
switch (action.type) {
case RNTesterActionsType.INIT_FROM_STORAGE:
case RNTesterNavActionsType.INIT_FROM_STORAGE:
return action.data;
case RNTesterActionsType.NAVBAR_PRESS:
case RNTesterNavActionsType.NAVBAR_PRESS:
return {
...state,
activeModuleKey: null,
@ -82,7 +82,7 @@ export const RNTesterReducer = (
activeModuleExampleKey: null,
screen: action.data.screen,
};
case RNTesterActionsType.MODULE_CARD_PRESS:
case RNTesterNavActionsType.MODULE_CARD_PRESS:
return {
...state,
activeModuleKey: action.data.key,
@ -94,12 +94,12 @@ export const RNTesterReducer = (
recentlyUsed: state.recentlyUsed,
}),
};
case RNTesterActionsType.EXAMPLE_CARD_PRESS:
case RNTesterNavActionsType.EXAMPLE_CARD_PRESS:
return {
...state,
activeModuleExampleKey: action.data.key,
};
case RNTesterActionsType.BOOKMARK_PRESS:
case RNTesterNavActionsType.BOOKMARK_PRESS:
return {
...state,
bookmarks: getUpdatedBookmarks({
@ -108,7 +108,7 @@ export const RNTesterReducer = (
bookmarks: state.bookmarks,
}),
};
case RNTesterActionsType.BACK_BUTTON_PRESS:
case RNTesterNavActionsType.BACK_BUTTON_PRESS:
// Go back to module or list
return {
...state,

Просмотреть файл

@ -1,82 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow
*/
const React = require('react');
import {AsyncStorage} from 'react-native';
export type PassProps<State> = {
state: State,
setState: (stateLamda: (state: State) => State) => void,
...
};
/**
* A simple container for persisting some state and passing it into the wrapped component as
* `props.persister.state`. Update it with `props.persister.setState`. The component is initially
* rendered using `getInitialState` in the spec and is then re-rendered with the persisted data
* once it's fetched.
*
* This is currently tied to RNTester because it's generally not good to use AsyncStorage like
* this in real apps with user data, but we could maybe pull it out for other internal settings-type
* usage.
*/
function createContainer<Props: Object, State>(
Component: React.ComponentType<Props & {persister: PassProps<State>, ...}>,
spec: {
cacheKeySuffix: (props: Props) => string,
getInitialState: (props: Props) => State,
version?: string,
...
},
): React.ComponentType<Props> {
return class ComponentWithPersistedState extends React.Component<
Props,
$FlowFixMeState,
> {
static displayName = `RNTesterStatePersister(${String(
Component.displayName ?? Component.name,
)})`;
state = {value: spec.getInitialState(this.props)};
_cacheKey = `RNTester:${spec.version || 'v1'}:${spec.cacheKeySuffix(
this.props,
)}`;
componentDidMount() {
AsyncStorage.getItem(this._cacheKey, (err, value) => {
if (!err && value) {
this.setState({value: JSON.parse(value)});
}
});
}
_passSetState = (stateLamda: (state: State) => State): void => {
this.setState(state => {
const value = stateLamda(state.value);
AsyncStorage.setItem(this._cacheKey, JSON.stringify(value));
return {value};
});
};
render(): React.Node {
return (
<Component
{...this.props}
persister={{
state: this.state.value,
setState: this._passSetState,
}}
/>
);
}
};
}
const RNTesterStatePersister = {
createContainer,
};
module.exports = RNTesterStatePersister;

Просмотреть файл

@ -8,13 +8,12 @@
* @flow
*/
import {AsyncStorage} from 'react-native';
import RNTesterList from './RNTesterList';
import type {
ExamplesList,
RNTesterState,
RNTesterNavState,
RNTesterJsStallsState,
ComponentList,
} from '../types/RNTesterTypes';
@ -24,13 +23,20 @@ export const Screens = {
BOOKMARKS: 'bookmarks',
};
export const initialState: RNTesterState = {
export const initialNavState: RNTesterNavState = {
activeModuleKey: null,
activeModuleTitle: null,
activeModuleExampleKey: null,
screen: null,
bookmarks: null,
recentlyUsed: null,
screen: Screens.COMPONENTS,
bookmarks: {components: [], apis: []},
recentlyUsed: {components: [], apis: []},
};
export const initialJsStallsState: RNTesterJsStallsState = {
stallInterval: null,
busyTime: null,
filteredStall: 0,
tracking: false,
};
const filterEmptySections = (examplesList: ExamplesList): any => {
@ -127,22 +133,3 @@ export const getExamplesListWithBookmarksAndRecentlyUsed = ({
return filterEmptySections(examplesList);
};
export const getInitialStateFromAsyncStorage = async (
storageKey: string,
): Promise<RNTesterState> => {
const initialStateString = await AsyncStorage.getItem(storageKey);
if (!initialStateString) {
return {
activeModuleKey: null,
activeModuleTitle: null,
activeModuleExampleKey: null,
screen: Screens.COMPONENTS,
bookmarks: {components: [], apis: []},
recentlyUsed: {components: [], apis: []},
};
} else {
return JSON.parse(initialStateString);
}
};

Просмотреть файл

@ -1,33 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow
*/
import * as React from 'react';
import {AsyncStorage} from 'react-native';
import type {RNTesterState} from '../types/RNTesterTypes';
export const useAsyncStorageReducer = (
reducer: Function,
initialState: RNTesterState,
storageKey: string,
): [RNTesterState, Function] => {
const [state, dispatch] = React.useReducer<Function, Object>(
reducer,
initialState,
);
React.useEffect(() => {
if (state !== initialState) {
AsyncStorage.setItem(storageKey, JSON.stringify(state));
}
}, [state, storageKey, initialState]);
return [state, dispatch];
};

Просмотреть файл

@ -0,0 +1,90 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow
*/
import * as React from 'react';
import type {RNTesterJsStallsState} from '../types/RNTesterTypes';
import {
RNTesterJsStallsReducer,
RNTesterJsStallsActionsType,
} from './RNTesterJsStallsReducer';
import {initialJsStallsState} from './testerStateUtils';
const useJsStallsReducer = (): {
state: RNTesterJsStallsState,
onEnableForceJsStalls: Function,
onDisableForceJsStalls: Function,
onEnableJsStallsTracking: Function,
onDisableJsStallsTracking: Function,
} => {
const [state, dispatch] = React.useReducer<Function, Object>(
RNTesterJsStallsReducer,
initialJsStallsState,
);
const {stallInterval} = state;
const onDisableForceJsStalls = () => {
clearInterval(stallInterval || 0);
dispatch({
type: RNTesterJsStallsActionsType.STOP_JS_STALLS,
data: {
stallInterval: null,
},
});
};
const onEnableForceJsStalls = () => {
const intervalId = setInterval(() => {
const start = Date.now();
console.warn('burn CPU');
while (Date.now() - start < 100) {}
}, 300);
dispatch({
type: RNTesterJsStallsActionsType.START_JS_STALLS,
data: {stallInterval: intervalId},
});
};
const onEnableJsStallsTracking = () => {
require('react-native/Libraries/Interaction/JSEventLoopWatchdog').install({
thresholdMS: 25,
});
dispatch({
type: RNTesterJsStallsActionsType.START_TRACK_JS_STALLS,
});
require('react-native/Libraries/Interaction/JSEventLoopWatchdog').addHandler(
{
onStall: ({busyTime}) => {
dispatch({
type: RNTesterJsStallsActionsType.UPDATE_TRACK_JS_STALLS,
data: {
busyTime,
},
});
},
},
);
};
const onDisableJsStallsTracking = () => {
console.warn('Cannot disable yet....');
};
return {
state,
onDisableForceJsStalls,
onEnableForceJsStalls,
onEnableJsStallsTracking,
onDisableJsStallsTracking,
};
};
export default useJsStallsReducer;