Skip to content

Commit 01f1780

Browse files
jamesreggiofacebook-github-bot
authored andcommitted
Expose static methods to manipulate the StatusBar stack imperatively (facebook#21206)
Summary: This PR exposes three static methods (`pushStackEntry`, `popStackEntry`, and `replaceStackEntry`) on StatusBar that enable imperative manipulation of the StatusBar style within the stack established by mounted StatusBar components. Motivation: ---------- The StatusBar **component** provides a sensible API for manipulating that StatusBar style: every time a StatusBar component is mounted, its props are pushed onto a stack, and the props from the most recently mounted component are applied. However, there are some scenarios where you may need to manipulate the StatusBar style from imperative code — particularly when invoking imperative third-party APIs that cause UI to appear. (For example, a user feedback utility or bug reporter that launches a full-screen modal.) In modern iOS development, `UIViewControllerBasedStatusBarAppearance` is typically set to `YES`, which allows the third-party UIViewController to specify its preferred status bar style. However, as has been discussed at length in facebook#11710, React Native has disabled this setting, which means that either the app's code or the third-party's React Native wrapper needs to manually manipulate React Native's StatusBar API to achieve the desired outcome. The existing imperative StatusBar APIs are not a good fit for these needs because they simply overwrite the existing StatusBar styles, and provide no means of reverting StatusBar style changes when the third-party UI is dismissed. To improve upon this situation, this PR makes it possible to call `StatusBar.pushStackEntry` before launching the third-party UI, wait for the UI to dismiss, and then call `StatusBar.popStackEntry` (supplying the token returned from the push call). I've featured the new stack-based imperative methods in the documentation, but stopped short of explicitly deprecating the older imperative methods — though I can think of no reason not to deprecate them. Feedback is welcome on this point. Release Notes: -------------- [GENERAL] [ENHANCEMENT] [StatusBar] - Add static methods to manipulate StatusBar stack imperatively Pull Request resolved: facebook#21206 Differential Revision: D9945247 Pulled By: cpojer fbshipit-source-id: ec118268cff5b47e87be81d0b9e1728ecc3a9b02
1 parent 638d672 commit 01f1780

File tree

1 file changed

+79
-20
lines changed

1 file changed

+79
-20
lines changed

Libraries/Components/StatusBar/StatusBar.js

Lines changed: 79 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -182,11 +182,34 @@ function createStackEntry(props: any): any {
182182
*
183183
* ### Imperative API
184184
*
185-
* For cases where using a component is not ideal, there is also an imperative
186-
* API exposed as static functions on the component. It is however not recommended
187-
* to use the static API and the component for the same prop because any value
188-
* set by the static API will get overriden by the one set by the component in
189-
* the next render.
185+
* For cases where using a component is not ideal, there are static methods
186+
* to manipulate the `StatusBar` display stack. These methods have the same
187+
* behavior as mounting and unmounting a `StatusBar` component.
188+
*
189+
* For example, you can call `StatusBar.pushStackEntry` to update the status bar
190+
* before launching a third-party native UI component, and then call
191+
* `StatusBar.popStackEntry` when completed.
192+
*
193+
* ```
194+
* const openThirdPartyBugReporter = async () => {
195+
* // The bug reporter has a dark background, so we push a new status bar style.
196+
* const stackEntry = StatusBar.pushStackEntry({barStyle: 'light-content'});
197+
*
198+
* // `open` returns a promise that resolves when the UI is dismissed.
199+
* await BugReporter.open();
200+
*
201+
* // Don't forget to call `popStackEntry` when you're done.
202+
* StatusBar.popStackEntry(stackEntry);
203+
* };
204+
* ```
205+
*
206+
* There is a legacy imperative API that enables you to manually update the
207+
* status bar styles. However, the legacy API does not update the internal
208+
* `StatusBar` display stack, which means that any changes will be overridden
209+
* whenever a `StatusBar` component is mounted or unmounted.
210+
*
211+
* It is strongly advised that you use `pushStackEntry`, `popStackEntry`, or
212+
* `replaceStackEntry` instead of the static methods beginning with `set`.
190213
*
191214
* ### Constants
192215
*
@@ -300,6 +323,48 @@ class StatusBar extends React.Component<Props> {
300323
StatusBarManager.setTranslucent(translucent);
301324
}
302325

326+
/**
327+
* Push a StatusBar entry onto the stack.
328+
* The return value should be passed to `popStackEntry` when complete.
329+
*
330+
* @param props Object containing the StatusBar props to use in the stack entry.
331+
*/
332+
static pushStackEntry(props: any) {
333+
const entry = createStackEntry(props);
334+
StatusBar._propsStack.push(entry);
335+
StatusBar._updatePropsStack();
336+
return entry;
337+
}
338+
339+
/**
340+
* Pop a StatusBar entry from the stack.
341+
*
342+
* @param entry Entry returned from `pushStackEntry`.
343+
*/
344+
static popStackEntry(entry: any) {
345+
const index = StatusBar._propsStack.indexOf(entry);
346+
if (index !== -1) {
347+
StatusBar._propsStack.splice(index, 1);
348+
}
349+
StatusBar._updatePropsStack();
350+
}
351+
352+
/**
353+
* Replace an existing StatusBar stack entry with new props.
354+
*
355+
* @param entry Entry returned from `pushStackEntry` to replace.
356+
* @param props Object containing the StatusBar props to use in the replacement stack entry.
357+
*/
358+
static replaceStackEntry(entry: any, props: any) {
359+
const newEntry = createStackEntry(props);
360+
const index = StatusBar._propsStack.indexOf(entry);
361+
if (index !== -1) {
362+
StatusBar._propsStack[index] = newEntry;
363+
}
364+
StatusBar._updatePropsStack();
365+
return newEntry;
366+
}
367+
303368
static defaultProps = {
304369
animated: false,
305370
showHideTransition: 'fade',
@@ -311,33 +376,27 @@ class StatusBar extends React.Component<Props> {
311376
// Every time a StatusBar component is mounted, we push it's prop to a stack
312377
// and always update the native status bar with the props from the top of then
313378
// stack. This allows having multiple StatusBar components and the one that is
314-
// added last or is deeper in the view hierarchy will have priority.
315-
this._stackEntry = createStackEntry(this.props);
316-
StatusBar._propsStack.push(this._stackEntry);
317-
this._updatePropsStack();
379+
// added last or is deeper in the view hierachy will have priority.
380+
this._stackEntry = StatusBar.pushStackEntry(this.props);
318381
}
319382

320383
componentWillUnmount() {
321384
// When a StatusBar is unmounted, remove itself from the stack and update
322385
// the native bar with the next props.
323-
const index = StatusBar._propsStack.indexOf(this._stackEntry);
324-
StatusBar._propsStack.splice(index, 1);
325-
326-
this._updatePropsStack();
386+
StatusBar.popStackEntry(this._stackEntry);
327387
}
328388

329389
componentDidUpdate() {
330-
const index = StatusBar._propsStack.indexOf(this._stackEntry);
331-
this._stackEntry = createStackEntry(this.props);
332-
StatusBar._propsStack[index] = this._stackEntry;
333-
334-
this._updatePropsStack();
390+
this._stackEntry = StatusBar.replaceStackEntry(
391+
this._stackEntry,
392+
this.props,
393+
);
335394
}
336395

337396
/**
338397
* Updates the native status bar with the props from the stack.
339398
*/
340-
_updatePropsStack = () => {
399+
static _updatePropsStack = () => {
341400
// Send the update to the native module only once at the end of the frame.
342401
clearImmediate(StatusBar._updateImmediate);
343402
StatusBar._updateImmediate = setImmediate(() => {
@@ -355,7 +414,7 @@ class StatusBar extends React.Component<Props> {
355414
) {
356415
StatusBarManager.setStyle(
357416
mergedProps.barStyle.value,
358-
mergedProps.barStyle.animated,
417+
mergedProps.barStyle.animated || false,
359418
);
360419
}
361420
if (!oldProps || oldProps.hidden.value !== mergedProps.hidden.value) {

0 commit comments

Comments
 (0)