Skip to content

Commit 51681e8

Browse files
hramosfacebook-github-bot
authored andcommitted
useColorScheme hook (#26143)
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
1 parent 2c91733 commit 51681e8

File tree

8 files changed

+114
-59
lines changed

8 files changed

+114
-59
lines changed

Libraries/Utilities/useColorScheme.js

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @format
8+
* @flow
9+
*/
10+
11+
'use strict';
12+
13+
import {useMemo} from 'react';
14+
import {useSubscription} from 'use-subscription';
15+
import Appearance from './Appearance';
16+
import type {ColorSchemeName} from './NativeAppearance';
17+
18+
export default function useColorScheme(): ?ColorSchemeName {
19+
const subscription = useMemo(
20+
() => ({
21+
getCurrentValue: () => Appearance.getColorScheme(),
22+
subscribe: callback => {
23+
Appearance.addChangeListener(callback);
24+
return () => Appearance.removeChangeListener(callback);
25+
},
26+
}),
27+
[],
28+
);
29+
30+
return useSubscription(subscription);
31+
}

Libraries/react-native/react-native-implementation.js

+4
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ import typeof ToastAndroid from '../Components/ToastAndroid/ToastAndroid';
8282
import typeof * as TurboModuleRegistry from '../TurboModule/TurboModuleRegistry';
8383
import typeof TVEventHandler from '../Components/AppleTV/TVEventHandler';
8484
import typeof UIManager from '../ReactNative/UIManager';
85+
import typeof useColorScheme from '../Utilities/useColorScheme';
8586
import typeof useWindowDimensions from '../Utilities/useWindowDimensions';
8687
import typeof UTFSequence from '../UTFSequence';
8788
import typeof Vibration from '../Vibration/Vibration';
@@ -393,6 +394,9 @@ module.exports = {
393394
> {
394395
return require('../Renderer/shims/ReactNative').unstable_batchedUpdates;
395396
},
397+
get useColorScheme(): useColorScheme {
398+
return require('../Utilities/useColorScheme').default;
399+
},
396400
get useWindowDimensions(): useWindowDimensions {
397401
return require('../Utilities/useWindowDimensions').default;
398402
},

RNTester/js/RNTesterApp.ios.js

+54-25
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ const SnapshotViewIOS = require('./examples/Snapshot/SnapshotViewIOS.ios');
2020
const URIActionMap = require('./utils/URIActionMap');
2121

2222
const {
23-
Appearance,
2423
AppRegistry,
2524
AsyncStorage,
2625
BackHandler,
@@ -30,6 +29,7 @@ const {
3029
SafeAreaView,
3130
StyleSheet,
3231
Text,
32+
useColorScheme,
3333
View,
3434
YellowBox,
3535
} = require('react-native');
@@ -38,6 +38,7 @@ import type {RNTesterExample} from './types/RNTesterTypes';
3838
import type {RNTesterAction} from './utils/RNTesterActions';
3939
import type {RNTesterNavigationState} from './utils/RNTesterNavigationReducer';
4040
import {RNTesterThemeContext, themes} from './components/RNTesterTheme';
41+
import type {ColorSchemeName} from '../../Libraries/Utilities/NativeAppearance';
4142

4243
type Props = {
4344
exampleFromAppetizeParams?: ?string,
@@ -86,6 +87,49 @@ const Header = ({onBack, title}: {onBack?: () => mixed, title: string}) => (
8687
</RNTesterThemeContext.Consumer>
8788
);
8889

90+
const RNTesterExampleContainerViaHook = ({
91+
onBack,
92+
title,
93+
module,
94+
}: {
95+
onBack?: () => mixed,
96+
title: string,
97+
module: RNTesterExample,
98+
}) => {
99+
const colorScheme: ?ColorSchemeName = useColorScheme();
100+
const theme = colorScheme === 'dark' ? themes.dark : themes.light;
101+
return (
102+
<RNTesterThemeContext.Provider value={theme}>
103+
<View style={styles.exampleContainer}>
104+
<Header onBack={onBack} title={title} />
105+
<RNTesterExampleContainer module={module} />
106+
</View>
107+
</RNTesterThemeContext.Provider>
108+
);
109+
};
110+
111+
const RNTesterExampleListViaHook = ({
112+
onNavigate,
113+
list,
114+
}: {
115+
onNavigate?: () => mixed,
116+
list: {
117+
ComponentExamples: Array<RNTesterExample>,
118+
APIExamples: Array<RNTesterExample>,
119+
},
120+
}) => {
121+
const colorScheme: ?ColorSchemeName = useColorScheme();
122+
const theme = colorScheme === 'dark' ? themes.dark : themes.light;
123+
return (
124+
<RNTesterThemeContext.Provider value={theme}>
125+
<View style={styles.exampleContainer}>
126+
<Header title="RNTester" />
127+
<RNTesterExampleList onNavigate={onNavigate} list={list} />
128+
</View>
129+
</RNTesterThemeContext.Provider>
130+
);
131+
};
132+
89133
class RNTesterApp extends React.Component<Props, RNTesterNavigationState> {
90134
_mounted: boolean;
91135

@@ -113,14 +157,6 @@ class RNTesterApp extends React.Component<Props, RNTesterNavigationState> {
113157
Linking.addEventListener('url', url => {
114158
this._handleAction(URIActionMap(url));
115159
});
116-
117-
Appearance.addChangeListener(prefs => {
118-
this._handleAction(
119-
RNTesterActions.ThemeAction(
120-
prefs.colorScheme === 'dark' ? themes.dark : themes.light,
121-
),
122-
);
123-
});
124160
}
125161

126162
componentWillUnmount() {
@@ -147,32 +183,25 @@ class RNTesterApp extends React.Component<Props, RNTesterNavigationState> {
147183
if (!this.state) {
148184
return null;
149185
}
150-
const theme = this.state.theme;
151186
if (this.state.openExample) {
152187
const Component = RNTesterList.Modules[this.state.openExample];
153188
if (Component && Component.external) {
154189
return <Component onExampleExit={this._handleBack} />;
155190
} else {
156191
return (
157-
<RNTesterThemeContext.Provider value={theme}>
158-
<View style={styles.exampleContainer}>
159-
<Header onBack={this._handleBack} title={Component.title} />
160-
<RNTesterExampleContainer module={Component} />
161-
</View>
162-
</RNTesterThemeContext.Provider>
192+
<RNTesterExampleContainerViaHook
193+
onBack={this._handleBack}
194+
title={Component.title}
195+
module={Component}
196+
/>
163197
);
164198
}
165199
}
166200
return (
167-
<RNTesterThemeContext.Provider value={theme}>
168-
<View style={styles.exampleContainer}>
169-
<Header title="RNTester" />
170-
<RNTesterExampleList
171-
onNavigate={this._handleAction}
172-
list={RNTesterList}
173-
/>
174-
</View>
175-
</RNTesterThemeContext.Provider>
201+
<RNTesterExampleListViaHook
202+
onNavigate={this._handleAction}
203+
list={RNTesterList}
204+
/>
176205
);
177206
}
178207
}

RNTester/js/examples/Appearance/AppearanceExample.js

+18-2
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,8 @@
1111
'use strict';
1212

1313
import * as React from 'react';
14-
import {Appearance, Text, View} from 'react-native';
14+
import {Appearance, Text, useColorScheme, View} from 'react-native';
1515
import type {AppearancePreferences} from '../../../../Libraries/Utilities/NativeAppearance';
16-
1716
import {RNTesterThemeContext, themes} from '../../components/RNTesterTheme';
1817

1918
class ColorSchemeSubscription extends React.Component<
@@ -80,6 +79,23 @@ const ThemedText = props => (
8079
exports.title = 'Appearance';
8180
exports.description = 'Light and dark user interface examples.';
8281
exports.examples = [
82+
{
83+
title: 'useColorScheme hook',
84+
render(): React.Node {
85+
const AppearanceViaHook = () => {
86+
const colorScheme = useColorScheme();
87+
return (
88+
<RNTesterThemeContext.Provider
89+
value={colorScheme === 'dark' ? themes.dark : themes.light}>
90+
<ThemedContainer>
91+
<ThemedText>useColorScheme(): {colorScheme}</ThemedText>
92+
</ThemedContainer>
93+
</RNTesterThemeContext.Provider>
94+
);
95+
};
96+
return <AppearanceViaHook />;
97+
},
98+
},
8399
{
84100
title: 'Non-component `getColorScheme` API',
85101
render(): React.Element<any> {

RNTester/js/utils/RNTesterActions.js

+1-15
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,10 @@ export type RNTesterExampleAction = {
2525
openExample: string,
2626
};
2727

28-
export type RNTesterThemeAction = {
29-
type: 'RNTesterThemeAction',
30-
theme: RNTesterTheme,
31-
};
32-
3328
export type RNTesterAction =
3429
| RNTesterBackAction
3530
| RNTesterListAction
36-
| RNTesterExampleAction
37-
| RNTesterThemeAction;
31+
| RNTesterExampleAction;
3832

3933
function Back(): RNTesterBackAction {
4034
return {
@@ -55,18 +49,10 @@ function ExampleAction(openExample: string): RNTesterExampleAction {
5549
};
5650
}
5751

58-
function ThemeAction(theme: RNTesterTheme): RNTesterThemeAction {
59-
return {
60-
type: 'RNTesterThemeAction',
61-
theme,
62-
};
63-
}
64-
6552
const RNTesterActions = {
6653
Back,
6754
ExampleList,
6855
ExampleAction,
69-
ThemeAction,
7056
};
7157

7258
module.exports = RNTesterActions;

RNTester/js/utils/RNTesterNavigationReducer.js

-17
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,10 @@
1010

1111
'use strict';
1212

13-
import {themes} from '../components/RNTesterTheme';
14-
import type {RNTesterTheme} from '../components/RNTesterTheme';
15-
1613
const RNTesterList = require('./RNTesterList');
17-
import {Appearance} from 'react-native';
1814

1915
export type RNTesterNavigationState = {
2016
openExample: ?string,
21-
theme: RNTesterTheme,
2217
};
2318

2419
function RNTesterNavigationReducer(
@@ -36,8 +31,6 @@ function RNTesterNavigationReducer(
3631
return {
3732
// A null openExample will cause the views to display the RNTester example list
3833
openExample: null,
39-
theme:
40-
Appearance.getColorScheme() === 'dark' ? themes.dark : themes.light,
4134
};
4235
}
4336

@@ -48,16 +41,6 @@ function RNTesterNavigationReducer(
4841
if (ExampleModule) {
4942
return {
5043
openExample: action.openExample,
51-
theme: state.theme,
52-
};
53-
}
54-
}
55-
56-
if (action.type === 'RNTesterThemeAction') {
57-
if (action.colorScheme) {
58-
return {
59-
openExample: state.openExample,
60-
theme: action.colorScheme === 'dark' ? themes.dark : themes.light,
6144
};
6245
}
6346
}

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@
112112
"regenerator-runtime": "^0.13.2",
113113
"scheduler": "0.15.0",
114114
"stacktrace-parser": "^0.1.3",
115+
"use-subscription": "^1.0.0",
115116
"whatwg-fetch": "^3.0.0"
116117
},
117118
"devDependencies": {

yarn.lock

+5
Original file line numberDiff line numberDiff line change
@@ -7043,6 +7043,11 @@ urix@^0.1.0:
70437043
resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72"
70447044
integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=
70457045

7046+
use-subscription@^1.0.0:
7047+
version "1.0.0"
7048+
resolved "https://registry.yarnpkg.com/use-subscription/-/use-subscription-1.0.0.tgz#25ed2161f75e9f6bd8c5c4acfe6087bfebfbfef4"
7049+
integrity sha512-PkDL9KSMN01nJYfa5c8O7Qbs7lmpzirfRWhWfIQN053hbIj5s1o5L7hrTzCfCCO2FsN2bKrU7ciRxxRcinmxAA==
7050+
70467051
use@^3.1.0:
70477052
version "3.1.1"
70487053
resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"

0 commit comments

Comments
 (0)