Skip to content

Commit

Permalink
useColorScheme hook (#26143)
Browse files Browse the repository at this point in the history
Summary:
Pull Request resolved: #26143

A new useColorScheme hook is provided as the preferred way of accessing the user's preferred color scheme (aka Dark Mode).

Changelog:

[General] [Added] - useColorScheme hook

Reviewed By: yungsters

Differential Revision: D16860954

fbshipit-source-id: 8a2b6c2624ed7cf431ab331158bc5456cde1f185
  • Loading branch information
hramos authored and facebook-github-bot committed Aug 31, 2019
1 parent 2c91733 commit 51681e8
Show file tree
Hide file tree
Showing 8 changed files with 114 additions and 59 deletions.
31 changes: 31 additions & 0 deletions Libraries/Utilities/useColorScheme.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* Copyright (c) Facebook, Inc. and its 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
*/

'use strict';

import {useMemo} from 'react';
import {useSubscription} from 'use-subscription';
import Appearance from './Appearance';
import type {ColorSchemeName} from './NativeAppearance';

export default function useColorScheme(): ?ColorSchemeName {
const subscription = useMemo(
() => ({
getCurrentValue: () => Appearance.getColorScheme(),
subscribe: callback => {
Appearance.addChangeListener(callback);
return () => Appearance.removeChangeListener(callback);
},
}),
[],
);

return useSubscription(subscription);
}
4 changes: 4 additions & 0 deletions Libraries/react-native/react-native-implementation.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ import typeof ToastAndroid from '../Components/ToastAndroid/ToastAndroid';
import typeof * as TurboModuleRegistry from '../TurboModule/TurboModuleRegistry';
import typeof TVEventHandler from '../Components/AppleTV/TVEventHandler';
import typeof UIManager from '../ReactNative/UIManager';
import typeof useColorScheme from '../Utilities/useColorScheme';
import typeof useWindowDimensions from '../Utilities/useWindowDimensions';
import typeof UTFSequence from '../UTFSequence';
import typeof Vibration from '../Vibration/Vibration';
Expand Down Expand Up @@ -393,6 +394,9 @@ module.exports = {
> {
return require('../Renderer/shims/ReactNative').unstable_batchedUpdates;
},
get useColorScheme(): useColorScheme {
return require('../Utilities/useColorScheme').default;
},
get useWindowDimensions(): useWindowDimensions {
return require('../Utilities/useWindowDimensions').default;
},
Expand Down
79 changes: 54 additions & 25 deletions RNTester/js/RNTesterApp.ios.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ const SnapshotViewIOS = require('./examples/Snapshot/SnapshotViewIOS.ios');
const URIActionMap = require('./utils/URIActionMap');

const {
Appearance,
AppRegistry,
AsyncStorage,
BackHandler,
Expand All @@ -30,6 +29,7 @@ const {
SafeAreaView,
StyleSheet,
Text,
useColorScheme,
View,
YellowBox,
} = require('react-native');
Expand All @@ -38,6 +38,7 @@ import type {RNTesterExample} from './types/RNTesterTypes';
import type {RNTesterAction} from './utils/RNTesterActions';
import type {RNTesterNavigationState} from './utils/RNTesterNavigationReducer';
import {RNTesterThemeContext, themes} from './components/RNTesterTheme';
import type {ColorSchemeName} from '../../Libraries/Utilities/NativeAppearance';

type Props = {
exampleFromAppetizeParams?: ?string,
Expand Down Expand Up @@ -86,6 +87,49 @@ const Header = ({onBack, title}: {onBack?: () => mixed, title: string}) => (
</RNTesterThemeContext.Consumer>
);

const RNTesterExampleContainerViaHook = ({
onBack,
title,
module,
}: {
onBack?: () => mixed,
title: string,
module: RNTesterExample,
}) => {
const colorScheme: ?ColorSchemeName = useColorScheme();
const theme = colorScheme === 'dark' ? themes.dark : themes.light;
return (
<RNTesterThemeContext.Provider value={theme}>
<View style={styles.exampleContainer}>
<Header onBack={onBack} title={title} />
<RNTesterExampleContainer module={module} />
</View>
</RNTesterThemeContext.Provider>
);
};

const RNTesterExampleListViaHook = ({
onNavigate,
list,
}: {
onNavigate?: () => mixed,
list: {
ComponentExamples: Array<RNTesterExample>,
APIExamples: Array<RNTesterExample>,
},
}) => {
const colorScheme: ?ColorSchemeName = useColorScheme();
const theme = colorScheme === 'dark' ? themes.dark : themes.light;
return (
<RNTesterThemeContext.Provider value={theme}>
<View style={styles.exampleContainer}>
<Header title="RNTester" />
<RNTesterExampleList onNavigate={onNavigate} list={list} />
</View>
</RNTesterThemeContext.Provider>
);
};

class RNTesterApp extends React.Component<Props, RNTesterNavigationState> {
_mounted: boolean;

Expand Down Expand Up @@ -113,14 +157,6 @@ class RNTesterApp extends React.Component<Props, RNTesterNavigationState> {
Linking.addEventListener('url', url => {
this._handleAction(URIActionMap(url));
});

Appearance.addChangeListener(prefs => {
this._handleAction(
RNTesterActions.ThemeAction(
prefs.colorScheme === 'dark' ? themes.dark : themes.light,
),
);
});
}

componentWillUnmount() {
Expand All @@ -147,32 +183,25 @@ class RNTesterApp extends React.Component<Props, RNTesterNavigationState> {
if (!this.state) {
return null;
}
const theme = this.state.theme;
if (this.state.openExample) {
const Component = RNTesterList.Modules[this.state.openExample];
if (Component && Component.external) {
return <Component onExampleExit={this._handleBack} />;
} else {
return (
<RNTesterThemeContext.Provider value={theme}>
<View style={styles.exampleContainer}>
<Header onBack={this._handleBack} title={Component.title} />
<RNTesterExampleContainer module={Component} />
</View>
</RNTesterThemeContext.Provider>
<RNTesterExampleContainerViaHook
onBack={this._handleBack}
title={Component.title}
module={Component}
/>
);
}
}
return (
<RNTesterThemeContext.Provider value={theme}>
<View style={styles.exampleContainer}>
<Header title="RNTester" />
<RNTesterExampleList
onNavigate={this._handleAction}
list={RNTesterList}
/>
</View>
</RNTesterThemeContext.Provider>
<RNTesterExampleListViaHook
onNavigate={this._handleAction}
list={RNTesterList}
/>
);
}
}
Expand Down
20 changes: 18 additions & 2 deletions RNTester/js/examples/Appearance/AppearanceExample.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,8 @@
'use strict';

import * as React from 'react';
import {Appearance, Text, View} from 'react-native';
import {Appearance, Text, useColorScheme, View} from 'react-native';
import type {AppearancePreferences} from '../../../../Libraries/Utilities/NativeAppearance';

import {RNTesterThemeContext, themes} from '../../components/RNTesterTheme';

class ColorSchemeSubscription extends React.Component<
Expand Down Expand Up @@ -80,6 +79,23 @@ const ThemedText = props => (
exports.title = 'Appearance';
exports.description = 'Light and dark user interface examples.';
exports.examples = [
{
title: 'useColorScheme hook',
render(): React.Node {
const AppearanceViaHook = () => {
const colorScheme = useColorScheme();
return (
<RNTesterThemeContext.Provider
value={colorScheme === 'dark' ? themes.dark : themes.light}>
<ThemedContainer>
<ThemedText>useColorScheme(): {colorScheme}</ThemedText>
</ThemedContainer>
</RNTesterThemeContext.Provider>
);
};
return <AppearanceViaHook />;
},
},
{
title: 'Non-component `getColorScheme` API',
render(): React.Element<any> {
Expand Down
16 changes: 1 addition & 15 deletions RNTester/js/utils/RNTesterActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,10 @@ export type RNTesterExampleAction = {
openExample: string,
};

export type RNTesterThemeAction = {
type: 'RNTesterThemeAction',
theme: RNTesterTheme,
};

export type RNTesterAction =
| RNTesterBackAction
| RNTesterListAction
| RNTesterExampleAction
| RNTesterThemeAction;
| RNTesterExampleAction;

function Back(): RNTesterBackAction {
return {
Expand All @@ -55,18 +49,10 @@ function ExampleAction(openExample: string): RNTesterExampleAction {
};
}

function ThemeAction(theme: RNTesterTheme): RNTesterThemeAction {
return {
type: 'RNTesterThemeAction',
theme,
};
}

const RNTesterActions = {
Back,
ExampleList,
ExampleAction,
ThemeAction,
};

module.exports = RNTesterActions;
17 changes: 0 additions & 17 deletions RNTester/js/utils/RNTesterNavigationReducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,10 @@

'use strict';

import {themes} from '../components/RNTesterTheme';
import type {RNTesterTheme} from '../components/RNTesterTheme';

const RNTesterList = require('./RNTesterList');
import {Appearance} from 'react-native';

export type RNTesterNavigationState = {
openExample: ?string,
theme: RNTesterTheme,
};

function RNTesterNavigationReducer(
Expand All @@ -36,8 +31,6 @@ function RNTesterNavigationReducer(
return {
// A null openExample will cause the views to display the RNTester example list
openExample: null,
theme:
Appearance.getColorScheme() === 'dark' ? themes.dark : themes.light,
};
}

Expand All @@ -48,16 +41,6 @@ function RNTesterNavigationReducer(
if (ExampleModule) {
return {
openExample: action.openExample,
theme: state.theme,
};
}
}

if (action.type === 'RNTesterThemeAction') {
if (action.colorScheme) {
return {
openExample: state.openExample,
theme: action.colorScheme === 'dark' ? themes.dark : themes.light,
};
}
}
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@
"regenerator-runtime": "^0.13.2",
"scheduler": "0.15.0",
"stacktrace-parser": "^0.1.3",
"use-subscription": "^1.0.0",
"whatwg-fetch": "^3.0.0"
},
"devDependencies": {
Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7043,6 +7043,11 @@ urix@^0.1.0:
resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72"
integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=

use-subscription@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/use-subscription/-/use-subscription-1.0.0.tgz#25ed2161f75e9f6bd8c5c4acfe6087bfebfbfef4"
integrity sha512-PkDL9KSMN01nJYfa5c8O7Qbs7lmpzirfRWhWfIQN053hbIj5s1o5L7hrTzCfCCO2FsN2bKrU7ciRxxRcinmxAA==

use@^3.1.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"
Expand Down

0 comments on commit 51681e8

Please sign in to comment.