-
-
Notifications
You must be signed in to change notification settings - Fork 675
Prep work toward decoupling navigation from Redux. #4273
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
5706b5d
to
05be167
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! This direction makes sense to me. Some comments below -- notably I think we should be able to simplify the "wait until the store is hydrated" part. Which IIUC runs through these three commits:
844646a messagesActions: Don't let doNarrow
cancel an expected navigation (2/2).
dfac22a AppEventHandlers: Make handleInitialShare
wait for rehydration.
3a90d90 AppEventHandlers: Use handleInitialCare
correctly.
src/boot/AppEventHandlers.js
Outdated
|
||
componentDidMount() { | ||
/** | ||
* Do one-off tasks that require persisted data to have loaded. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, remove the existing check for whether the Redux store is
hydrated.
But that's not *quite* enough: there's one case in the status quo in
which this check is a band-aid for a crash. In the status quo, if
the app is completely closed and you receive a notification, and tap
it, the app will open and we'll run some JavaScript
(`handleInitialNotification`) to send the user on their way to the
narrow corresponding to the notification. This code runs quite soon
after the app opens; in fact, it doesn't even wait to be sure the
data is rehydrated. It should wait; then, we can avoid the crash
without the band-aid.
Hmm dang -- did you observe this crash empirically?
The existing strategy we have that should prevent this kind of thing is, we use redux-action-buffer
to cause all actions to be held up in a queue until a REHYDRATE action comes along. See listMiddleware
in src/boot/store.js
, and 42adfb3 where we started applying this to thunk actions.
More precisely, I believe all actions other than nav actions (because those are handled by a preceding middleware), and REHYDRATE actions themselves (because that's what we tell redux-action-buffer to look out for), will get enqueued that way. But in particular thunk actions will indeed get held in the queue, because that middleware comes after the createActionBuffer
middleware.
I like that strategy fairly well, because it's relatively easy to stick to -- most of the interesting complex things that we do that interact with Redux, if they aren't on some component which isn't going to get shown before a rehydrate, are in a thunk action anyway, which means that for the most part when we don't think about it the right thing happens naturally. Occasionally we've had a bug where we get that wrong (I remember we've seen this at least once), but the fix is easy -- just push the relevant code inside a thunk action creator, and add a dispatch
call at its call site -- and that's also nice.
Looking at handleInitialNotification
:
export const handleInitialNotification = async (dispatch: Dispatch) => {
const data = await getInitialNotification();
dispatch(narrowToNotification(data));
};
it looks like the narrowing happens inside a dispatch
, so it ought to be covered by that strategy. If you found empirically that something goes wrong, then let's debug.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm dang -- did you observe this crash empirically?
I did observe a crash while investigating my suspicion of a crash, but I'm not reproducing it right now; I'll try more tomorrow?
The existing strategy we have that should prevent this kind of thing is, we use
redux-action-buffer
to cause all actions to be held up in a queue until a REHYDRATE action comes along.
True, but I was specifically thinking to prepare for the future when navigation actions aren't dispatched through Redux and redux-action-buffer
will have no power over them. But you're right; now I think more closely about it, being in a thunk action creator should do the trick (and it seems to in my testing just now).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm dang -- did you observe this crash empirically?
I did observe a crash while investigating my suspicion of a crash, but I'm not reproducing it right now; I'll try more tomorrow?
OK, I think I've solved this mystery—I was logged out when I observed the crash. If I set it up so that handleInitialNotification
dispatches doNarrow
unconditionally (I had it do so before await getInitialNotification()
), it does crash if the user is logged out; I get the "no server data" message. No real surprise there.
But my test case case wasn't representative—narrowToNotification
will handle checking to see that the account the notification is meant for is logged in, so there is no crash to worry about here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great -- glad that's diagnosed.
// Since we've put this screen in a stack-nav route config, it gets the | ||
// `navigation` prop for free. | ||
navigation: NavigationStackProp<{| ...NavigationStateRoute, params: {| userId: number |} |}>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The main example in the TypeScript doc is the following:
```
type Props = {
navigation: NavigationStackProp<{ userId: string }>;
};
```
We find `NavigationStackProp` is a good thing to use, but
`{ userId: string }` is bad for a couple of reasons:
- It's off by a level of nesting; surely they mean to describe
`navigation.state.params`. To do that, the type needs to look
something like `{ params: { userId: string } }`.
- It leaves out all the other things on `navigation.state` including
`key` and `routeName`, which might be nice to have someday.
Experimentally, these are conveniently filled in by saying
`NavigationStackProp<NavigationStateRoute>` [3].
Yeah, that's odd!
I wondered if this might be a difference between the TS definitions and the Flow libdef, in what they have NavigationStackProp
take as a type parameter. But AFAICT it's equally wrong there -- the first type parameter is called State
and is used for the state
property, not state.params
. (Though there is a second type parameter for the latter.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, it's kind of all over the place.
src/account/AccountPickScreen.js
Outdated
// Since we've put this screen in a stack-nav route config, it gets the | ||
// `navigation` prop for free. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIUC the precise logic is: because the only way we invoke this component is as a screen in such a route config, it will always get that prop.
Or I guess, equally correct and broader: because that's the only non-type-checked way we invoke it.
Typically I wouldn't expect us to use one of these components in any other way. But as a way of helping confirm that, I did this quick search: rg -w '[A-Z]\w+Screen' src/
to find all references to things that fit the naming pattern common to almost all these components. Other than their own definitions, including react-redux connect
calls, the only ones I found referred to anywhere other than AppNavigator
were:
CompatibilityScreen
ChooseRecipientsScreen
And indeed those don't appear in this commit, so that checks out.
I repeated the search on the components that appear in this commit and don't fit that naming pattern: rg -we SmartUrlInput -e MainScreenWithTabs -e MessageReactionList src/
That actually found one reference that doesn't line up! Did you mean to include SmartUrlInput
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah—SmartUrlInput should probably go in its own commit. It's a reusable component that expects...a navigation
prop, to be passed down in the normal React way (i.e., it's not plugged into a navigator), and it shouldn't care whether that navigation
prop comes from a stack navigator, drawer navigator, etc. I landed on NavigationScreenProp<NavigationState>
, without much consideration, from reading react-navigation/react-navigation#3643 (comment), and it could be wrong.
navigation: NavigationTabProp<{| | ||
...NavigationStateRoute, | ||
params: {| sharedData: SharedData |}, | ||
|}>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm -- for some reason it seems like this type annotation is not working (it's resulting in any
types), whereas in at least some spot-checking the ones from the previous commit with NavigationStackProp
are working.
Specifically, if I go and do something silly like
state = (() => {
- const { sharedData } = this.props.navigation.state.params;
+ const { sharedData, asdfasdf } = this.props.navigation.state.params;
then I don't get any type error at the attempt to pull an asdfasdf
property from params
.
Also, hovering on the identifier navigation
in that line I just quoted shows "any(implicit)" as the type, and ditto for the subsequent state
or params
. (That's the symptom I first noticed; I tried the asdfasdf
thing just to confirm that it wasn't some IDE glitch.)
Perhaps relatedly, if in VS Code I hit F12 on NavigationTabProp
to try to go to its definition, it doesn't find it -- it can only take me to the import type
line at the top of this file. Whereas it does work fine for NavigationStackProp
in a file like AccountDetailsScreen.js
. There does seem to be a libdef in flow-typed/
, so not sure what the issue is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have a hunch that I'll look into tomorrow—we've got two libdefs for react-navigation-tabs
in flow-typed/npm
, and one of them is a stub where everything is any
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aha...that's it. Yeah, getting rid of that stub libdef (which we don't want anyway; I don't have reasons to suspect the real one is irredeemably bad) reveals lots of Flow errors that seem productive to address.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aha...that's it. Yeah, getting rid of that stub libdef (which we don't want anyway; I don't have reasons to suspect the real one is irredeemably bad) reveals lots of Flow errors that seem productive to address.
Aha! Good catch.
…2/2). In the status quo, in all cases where `doNarrow` cancels a navigation, it's a bug -- the user's attempt to navigate is being silently thwarted, which causes confusion. So, remove the existing check for whether the Redux store is hydrated. As Greg points out [1], this removal is fine because the act of navigating (to a screen that will crash if the store is not hydrated) happens in a thunk action (`doNarrow`) that will be held up in the queue managed by the middleware from `redux-action-buffer` until rehydration is complete. We're confident that a thunk action will be buffered like this because the thunk middleware comes after the `redux-action-buffer` middleware in `listMiddleware`; see 42adfb3 for more detail on that design. The actual plain-object navigation action (created by `StackActions` in navActions.js), if it were dispatched directly, may not be buffered [2], because the `react-navigation-redux-helpers` middleware comes before the `redux-action-buffer` middleware. But we're safe as long as the code that dispatches it is inside `doNarrow` or another thunk action creator. [1] zulip#4273 (comment) [2] This didn't stand up to some quick testing, but some caution wouldn't be harmful. In any case, when we eliminate the `react-navigation-redux-helpers` middleware and separate the navigation-action dispatch from Redux entirely, `redux-action-buffer` will have no control over when the navigation happens.
As an ordinary, reusable component that wraps something as multipurpose as the `Input` component, `SmartUrlInput` is meant to get the `navigation` prop in the normal React way. This is in contrast to the many screen components that are invoked by passing them to `create*Navigator` as part of the route config (and in no other places), and that therefore get their `navigation` prop "for free". There's no need to make `SmartUrlInput` too picky about the shape of the `navigation` prop it receives -- it shouldn't obviously care that it's the kind of `navigation` prop that you get from a stack navigator, as opposed to a drawer navigator, etc. I've found a way to spell that -- `NavigationScreenProp<NavigationState>` -- and it seems to work [1]. In particular, it seems to work nicely with the pattern we'll take in the next commit of annotating our "screen" components with the `navigation` prop. So, in this commit, also adopt that pattern for all the screens that currently use `SmartUrlInput`. Currently, there's just one, `RealmScreen`. [1] zulip#4273 (comment)
05be167
to
c2de8bc
Compare
Thanks for the review! Just pushed some changes and responded to your comments above. |
src/start/RealmScreen.js
Outdated
type Props = $ReadOnly<{| | ||
navigation: NavigationScreenProp<{ | ||
params: ?{| | ||
params: {| |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
e192254 RealmScreen: Assume navigation.state.params
will exist.
Oops! This commit is wrong; please don't merge until I've pushed a fix. I forgot about reaching RealmScreen through navReducer
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, done.
As an ordinary, reusable component that wraps something as multipurpose as the `Input` component, `SmartUrlInput` is meant to get the `navigation` prop in the normal React way. This is in contrast to the many screen components that are invoked by passing them to `create*Navigator` as part of the route config (and in no other places), and that therefore get their `navigation` prop "for free". There's no need to make `SmartUrlInput` too picky about the shape of the `navigation` prop it receives -- it shouldn't obviously care that it's the kind of `navigation` prop that you get from a stack navigator, as opposed to a drawer navigator, etc. I've found a way to spell that -- `NavigationScreenProp<NavigationState>` -- and it seems to work [1]. In particular, it seems to work nicely with the pattern we'll take in the next commit of annotating our "screen" components with the `navigation` prop. So, in this commit, also adopt that pattern for all the screens that currently use `SmartUrlInput`. Currently, there's just one, `RealmScreen`. [1] zulip#4273 (comment)
c2de8bc
to
c2b927e
Compare
src/message/messagesActions.js
Outdated
* As a thunk action creator, this is expected to be buffered by | ||
* `redux-action-buffer` so that it doesn't navigate before the store | ||
* has rehydrated. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One quibble remaining on this commit: this bit of comment doesn't really belong here. In a couple of ways, I guess:
-
It doesn't say anything that's part of the interface, something that's actionable from the perspective of when you're using this function. So it doesn't belong in the jsdoc, though it could as an implementation comment.
-
Even as an implementation comment, the state of affairs it's describing is really common to almost all our thunk actions creators, not distinctive to this one.
In seeing it the reader naturally wonders, wait, why is this a thing we have to worry about here in particular? What's happening here that brings it up? The answer is really just that there used to be a
getIsHydrated
check here -- now that there no longer is, there's nothing in particular to say about it at this spot. But that means this remark is really naturally about the change that removed that check, not about the code after that change, so it belongs just in the commit message.
Or from another angle: this commit is basically completing a cleanup that ideally I'd have done right after 42adfb3 as part of the same series. In order to really clean up completely, this code should end up with no lasting complications from that wrinkle at all.
navigation: NavigationTabProp<{| | ||
...NavigationStateRoute, | ||
params: {| sharedData: SharedData |}, | ||
|}>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aha...that's it. Yeah, getting rid of that stub libdef (which we don't want anyway; I don't have reasons to suspect the real one is irredeemably bad) reveals lots of Flow errors that seem productive to address.
Aha! Good catch.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the revisions!
This all LGTM. A few comments below (and above, thanks GitHub) on small things; please merge at will after going through those.
type Props = $ReadOnly<{| | ||
navigation: NavigationScreenProp<{ | ||
params: ?{| | ||
params?: {| |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why optional at all? I think the logic in your previous version (at 6f6a3ee) was sound for why it shouldn't simply go missing; and following that logic we make params
non-optional on other screens too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah I see, you answered that here 🙂 : #4273 (comment)
I think this calls for a comment saying why it's optional, in any case -- since that is out of the ordinary compared to how all, I think, of our other screens work.
... Then I went reading this code some more to see how it handles the case where params
is missing. OK, effectively realm
gets defaulted to ''
, but initial
? It seems to default to false
. So when is it ever true -- is that just dead code? ... No, it's not. The answer is this hack in navReducer.js
:
export const getStateForRoute = (route: string): NavigationState => {
// TODO: this is kind of a hack! Refactor to a better way.
// * Perhaps pass `initial: true` unconditionally in this initialization
// code, to all routes? Then that'd just be part of the interface of
// making a screen work as an initial screen.
// * Alternatively, we could replace the whole system of `ModalNavBar`,
// `canGoBack`, etc., with our own "navigation view" that would be more
// directly integrated into the navigation framework:
// https://reactnavigation.org/docs/en/2.x/navigation-views.html
const params = route === 'realm' ? { initial: true } : undefined;
So in fact in navReducer
when we navigate to this screen, we also pass a params
object (which I guess means we always do.) It has a property initial: true
, and no params
.
I guess now this extra definitely calls for a comment here 😛 Which really I should have added at the same time as adding that hack: 7a04a56
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I've already got plans to get rid of that hack soon, as part of the process :)
*/ | ||
defaultDomain: string, | ||
navigation: NavigationScreenProp<mixed>, | ||
navigation: NavigationScreenProp<NavigationState>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not a big deal, but I suspect you can make this change after the "Tell all screens on AppNavigator" commit (and include the RealmScreen
change in that one.) If you do, then I think the story to tell in this one's commit message becomes simpler, because no foreshadowing is necessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I get an error if I leave this as mixed
with the RealmScreen
change applied:
Error ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ src/start/RealmScreen.js:121:10
Cannot create SmartUrlInput element because in property navigation:
• Either cannot create exact type from mixed [1].
• Or property getParam is missing in object type [2] but exists in NavigationScreenProp [3].
src/start/RealmScreen.js
118│ shouldShowLoadingBanner={false}
119│ >
120│ <Label text="Enter your Zulip server URL:" />
121│ <SmartUrlInput
122│ style={styles.input}
123│ navigation={navigation}
124│ defaultProtocol="https://"
125│ defaultOrganization="your-org"
126│ defaultDomain="zulipchat.com"
127│ defaultValue={initialRealm}
128│ onChangeText={this.handleRealmChange}
129│ onSubmitEditing={this.tryRealm}
130│ enablesReturnKeyAutomatically
131│ />
132│ {error !== null ? (
133│ <ErrorMsg error={error} />
134│ ) : (
flow-typed/npm/react-navigation-stack_v1.x.x.js
[2] 595│ & {
596│ pop: (n?: number, params?: {| immediate?: boolean |}) => boolean,
597│ popToTop: (params?: {| immediate?: boolean |}) => boolean,
598│ push: (
599│ routeName: string,
600│ params?: NavigationParams,
601│ action?: NavigationNavigateAction
602│ ) => boolean,
603│ replace: (
604│ routeName: string,
605│ params?: NavigationParams,
606│ action?: NavigationNavigateAction
607│ ) => boolean,
608│ reset: (actions: NavigationAction[], index: number) => boolean,
609│ ...
610│ };
src/common/SmartUrlInput.js
[3][1] 52│ navigation: NavigationScreenProp<mixed>,
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I could do a temporary $FlowFixMe
, but I wonder if that would mean roughly the same amount of explanation that's already in this commit?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Huh. That's a bit puzzling to me: it feels like it should be perfectly fine, and also when I look at the actual type definitions it seems like it should be accepted. In particular, the new type NavigationScreenProp<NavigationState>
should be a subtype of the old type NavigationScreenProp<mixed>
-- NavigationState
is a subtype of mixed
because everything is, and NavigationScreenProp
is declared in the react-navigation libdef as covariant (with a +
) in its type parameter:
declare export type NavigationScreenProp<+S> = {
so S a subtype of T should make NavigationScreenProp<S>
a subtype of NavigationScreenProp<T>
.
But not worth trying to debug further, since you've already made a fine workaround.
…2/2). In the status quo, in all cases where `doNarrow` cancels a navigation, it's a bug -- the user's attempt to navigate is being silently thwarted, which causes confusion. So, remove the existing check for whether the Redux store is hydrated. As Greg points out [1], this removal is fine because the act of navigating (to a screen that will crash if the store is not hydrated) happens in a thunk action (`doNarrow`) that will be held up in the queue managed by the middleware from `redux-action-buffer` until rehydration is complete. We're confident that a thunk action will be buffered like this because the thunk middleware comes after the `redux-action-buffer` middleware in `listMiddleware`; see 42adfb3 for more detail on that design. The actual plain-object navigation action (created by `StackActions` in navActions.js), if it were dispatched directly, may not be buffered [2], because the `react-navigation-redux-helpers` middleware comes before the `redux-action-buffer` middleware. But we're safe as long as the code that dispatches it is inside `doNarrow` or another thunk action creator. [1] zulip#4273 (comment) [2] This didn't stand up to some quick testing, but some caution wouldn't be harmful. In any case, when we eliminate the `react-navigation-redux-helpers` middleware and separate the navigation-action dispatch from Redux entirely, `redux-action-buffer` will have no control over when the navigation happens.
As an ordinary, reusable component that wraps something as multipurpose as the `Input` component, `SmartUrlInput` is meant to get the `navigation` prop in the normal React way. This is in contrast to the many screen components that are invoked by passing them to `create*Navigator` as part of the route config (and in no other places), and that therefore get their `navigation` prop "for free". There's no need to make `SmartUrlInput` too picky about the shape of the `navigation` prop it receives -- it shouldn't obviously care that it's the kind of `navigation` prop that you get from a stack navigator, as opposed to a drawer navigator, etc. I've found a way to spell that -- `NavigationScreenProp<NavigationState>` -- and it seems to work [1]. In particular, it seems to work nicely with the pattern we'll take in the next commit of annotating our "screen" components with the `navigation` prop. So, in this commit, also adopt that pattern for all the screens that currently use `SmartUrlInput`. Currently, there's just one, `RealmScreen`. [1] zulip#4273 (comment)
c2b927e
to
beba5ca
Compare
Since you said it's not a big deal, I'll go ahead as-is, if that's OK; see the error in #4273 (comment) that I got when I tried the suggested adjustment. I've addressed everything else, so I'll go ahead and merge. |
We use the default export from this file (calling it `tabsOptions` in all cases`) for both `createBottomTabNavigator` and `createMaterialTopTabNavigator`. Both of these two options were actually removed from `createBottomTabNavigator`'s config in react-navigation v2 [1], with much controversy [2]. So neither of them has been doing anything for `MainTabs`, our only use of `createBottomTabNavigator`, for quite a while. When we start using a proper libdef in an upcoming commit, we would get errors about passing these two options to `createBottomTabNavigator`. The `swipeEnabled` option remains available for `createMaterialTopTabNavigator`. We have three uses of `createMaterialTopTabNavigator`, and they all have `swipeEnabled` set to `true` (we did this in ad0b5f3) so we don't even have to think about what the default value is, and this is NFC for those. `animationEnabled` isn't documented for `createMaterialTopTabNavigator`, so its removal should also be NFC. It gets a mention in the v3 doc [3] but not the v4 doc [4]. [1] https://reactnavigation.org/blog/2018/05/07/react-navigation-2.0#tab-navigator-split-into-separate-components [2] react-navigation/react-navigation#4146 [3] https://reactnavigation.org/docs/3.x/material-top-tab-navigator [4] https://reactnavigation.org/docs/4.x/material-top-tab-navigator
There are differences in the config options between `createBottomTabNavigator` and `createMaterialTopTabNavigator`, which Flow will soon start telling us about, when we start using an actual libdef. So, be specific about which options we give which type of navigator. Testing on iOS and Android shows that this doesn't obviously changed anything. It shouldn't, anyway, because the status quo is that we've been handing `createBottomTabNavigator` some options that (if the docs are to be trusted) it has no idea how to handle, and plausibly just drops on the floor. And in particular, the removal of the zulip#2065 workaround (`borderWidth: 0` and `elevation: 0`, targeting material top tabs) from the bottom-tab-nav config doesn't seem to give any adverse effects.
And leave in place the actual, meaningful libdef, so that that one will prevail. Now, we get proper type-checking for our calls of `createMaterialTopTabNavigator` and `createBottomTabNavigator`. This means a return of the kind of `$FlowFixMe`s [1] we removed in 0d2066f, at a time when there wasn't a suitable libdef for react-navigation-tabs. [1] Some thoughts on this were written in a36814e, but see zulip#4114 (comment) for more recent concerns about typing our component classes with a static `navigationOptions` property. I'm inclined to not revisit these suppressions until we're on React Navigation v5, which has quite a different, component-based API, and a particular guide for type-checking the route config and the screen components in a unified way; that's at https://reactnavigation.org/docs/typescript/.
… so. And remove DO_NARROW. We greatly reduced the responsibility of the DO_NARROW action in 55361d8, leaving only this site in our reducers. This last remaining site in `fetchingReducer` resembles a garbage-collection mechanism, to clear out the `fetching` state so it can't grow to be too big. But, as Greg says, """ Yeah -- I think that isn't really even essential to have at all. The `fetching` state is a tiny bit of metadata for each narrow, and whenever we have it we'll have much more data at the same narrow in `state.narrows`. The logic there also doesn't totally make sense to me: when we navigate to a new narrow, we forget all about the fetching state of other narrows? Basically if that ever results in dropping an entry where one of the values was `true`, then that seems like a bug to me. Possibly latent and not visible to the user. """
…1/2). As Greg points out in discussion [1], it's not great for `doNarrow` to silently not navigate when the user expects to navigate. In the case of an invalid narrow, allow the navigation, but make sure `ChatScreen` gracefully handles it by showing an error state to the user. That view naturally looks a lot like the `FetchError` view added in 353cd3a for showing an error in fetching data. A few tests that have an appropriately narrow focus to confirm the now-absent behavior of `doNarrow` (checking that the narrow is valid) are now wrong, so, remove them. A natural question is whether these tests were giving us more coverage than we already have in `isNarrowValid`'s tests, and it appears that they're not. In particular, the two removed tests seem to map neatly onto two of `isNarrowValid`'s tests, as follows: (in messageActions-test.js -> in narrowsSelectors-test.js) 'if new narrow stream is not valid, do nothing' -> 'narrowing to a non-existing stream is invalid' 'if new narrow user is deactivated, do nothing' -> 'narrowing to a PM with non-existing user is not valid' [1] https://chat.zulip.org/#narrow/stream/243-mobile-team/topic/decouple.20nav.20from.20redux.20%28.23M3804%29/near/1027854
…2/2). In the status quo, in all cases where `doNarrow` cancels a navigation, it's a bug -- the user's attempt to navigate is being silently thwarted, which causes confusion. So, remove the existing check for whether the Redux store is hydrated. As Greg points out [1], this removal is fine because the act of navigating (to a screen that will crash if the store is not hydrated) happens in a thunk action (`doNarrow`) that will be held up in the queue managed by the middleware from `redux-action-buffer` until rehydration is complete. We're confident that a thunk action will be buffered like this because the thunk middleware comes after the `redux-action-buffer` middleware in `listMiddleware`; see 42adfb3 for more detail on that design. The actual plain-object navigation action (created by `StackActions` in navActions.js), if it were dispatched directly, may not be buffered [2], because the `react-navigation-redux-helpers` middleware comes before the `redux-action-buffer` middleware. But we're safe as long as the code that dispatches it is inside `doNarrow` or another thunk action creator. [1] zulip#4273 (comment) [2] This didn't stand up to some quick testing, but some caution wouldn't be harmful. In any case, when we eliminate the `react-navigation-redux-helpers` middleware and separate the navigation-action dispatch from Redux entirely, `redux-action-buffer` will have no control over when the navigation happens.
Like `handleInitialNotification`, `handleInitialShare` isn't a special thunk action creator to be passed to `dispatch`; rather, it's a plain action that takes `dispatch` as its only argument. So, use it that way.
… work. `createSelector` from `reselect` can be useful when we're dealing with a lot more data than we are here, e.g., users, messages, etc.
Make this action creator's name match the type of action it creates, `ACCOUNT_SWITCH`. Good for searchability.
I'm not aware that `params` will ever be null. If we thought it might be null, we would use the more transparent `| null`. Also, add a TODO to stop using a hack; we plan to do this later in the process for zulip#3804.
As an ordinary, reusable component that wraps something as multipurpose as the `Input` component, `SmartUrlInput` is meant to get the `navigation` prop in the normal React way. This is in contrast to the many screen components that are invoked by passing them to `create*Navigator` as part of the route config (and in no other places), and that therefore get their `navigation` prop "for free". There's no need to make `SmartUrlInput` too picky about the shape of the `navigation` prop it receives -- it shouldn't obviously care that it's the kind of `navigation` prop that you get from a stack navigator, as opposed to a drawer navigator, etc. I've found a way to spell that -- `NavigationScreenProp<NavigationState>` -- and it seems to work [1]. In particular, it seems to work nicely with the pattern we'll take in the next commit of annotating our "screen" components with the `navigation` prop. So, in this commit, also adopt that pattern for all the screens that currently use `SmartUrlInput`. Currently, there's just one, `RealmScreen`. [1] zulip#4273 (comment)
…prop. This has been done on an as-needed basis in the past, but in a few different ways, and it would be good to be consistent. With zulip#3804 approaching, it makes sense to proactively furnish all these components with the knowledge that they have the `navigation` prop if they ever need it. In doing so, we're making an unchecked assumption: that the components will in fact be passed the `navigation` prop, in the shape we say it will take. It's unchecked because of the `$FlowFixMe` from aaaf847 on the route config passed to `createStackNavigator` [0]. ----- There is a doc for doing this in TypeScript [1], but it's clearly wrong about how to handle it in Flow (and I believe it's also wrong about TypeScript in ways that would matter to us if we were using TypeScript). In particular, `NavigationStackScreenProps` is marketed (under "Type checking all props for a screen") as something you can spread in your `Props` type to get `theme` and `screenProps` along with `navigation`. Sounds great. But it doesn't exist in the Flow libdef, and I haven't found a clear alternative. In zulip#4127, a demo PR branch that informed 17f73d8 -- in particular, f482827 [FOR DISCUSSION] One way to get `sharedData` into ShareToStream, ShareToPm -- I seemed [2] to have favored something called `NavigationNavigatorProps` for this purpose. But it has at least two points against it: 1. I don't see documentation that we're supposed to use it, or how. 2. It's not tailored (or conveniently tailorable) to screens that get put on a particular type of navigator (stack, drawer, tabs, etc.) So, be conservative and just type the `navigation` prop, and in a way that says we know it comes from a stack navigator. The main example in the TypeScript doc is the following: ``` type Props = { navigation: NavigationStackProp<{ userId: string }>; }; ``` We find `NavigationStackProp` is a good thing to use [3], and it exists in the Flow libdef, but `{ userId: string }` is bad for a couple of reasons: - It's off by a level of nesting; surely they mean to describe `navigation.state.params`. To do that, the type needs to look something like `{ params: { userId: string } }`. This is also the case in the TypeScript. - It leaves out all the other things on `navigation.state` including `key` and `routeName`, which might be nice to have someday. Experimentally, these are conveniently filled in by saying `NavigationStackProp<NavigationStateRoute>` [4]. So, account for those deficiencies straightforwardly. [0] Initial thoughts on this were written in a36814e, but see zulip#4114 (comment) for more recent concerns about typing our component classes with a static `navigationOptions` property. I'm inclined to not revisit these suppressions until we're on React Navigation v5, which has quite a different, component-based API, and a particular guide for type-checking the route config and the screen components in a unified way; that's at https://reactnavigation.org/docs/typescript/. [1] https://reactnavigation.org/docs/4.x/typescript/ [2] Possibly following a train of thought from https://chat.zulip.org/#narrow/stream/243-mobile-team/topic/react-navigation.20types/near/878262. [3] In one or two places, we've been using `NavigationScreenProp` -- the doc for upgrading from v3 (which we did in f3b6c1f) says to replace that with `NavigationStackProp` for stack navigators. This didn't catch my eye at the time because it's under the "TypeScript" heading and we don't use TypeScript. That doc is at https://reactnavigation.org/docs/4.x/upgrading-from-3.x/#typescript. [4] Like at react-navigation/react-navigation#3643 (comment), but with `NavigationStackProp` instead of `NavigationScreenProp`.
Just like in the previous commit, but here, we include the knowledge that `navigation` comes from a tab navigator rather than a stack navigator.
beba5ca
to
90f23b7
Compare
In the previous commit, we removed our last call site of `getStateWithRoute` where 'realm' was passed for `route`. Therefore, this conditional can disappear. See more about this hack at zulip#4273 (comment).
In the previous commit, we removed our last call site of `getStateWithRoute` where 'realm' was passed for `route`. Therefore, this conditional can disappear. See more about this hack at zulip#4273 (comment).
In the previous commit, we removed our last call site of `getStateWithRoute` where 'realm' was passed for `route`. Therefore, this conditional can disappear. See more about this hack at zulip#4273 (comment).
In the previous commit, we removed our last call site of `getStateWithRoute` where 'realm' was passed for `route`. Therefore, this conditional can disappear. See more about this hack at zulip#4273 (comment).
In the previous commit, we removed our last call site of `getStateWithRoute` where 'realm' was passed for `route`. Therefore, this conditional can disappear. See more about this hack at zulip#4273 (comment).
In the previous commit, we removed our last call site of `getStateWithRoute` where 'realm' was passed for `route`. Therefore, this conditional can disappear. See more about this hack at zulip#4273 (comment).
In the previous commit, we removed our last call site of `getStateWithRoute` where 'realm' was passed for `route`. Therefore, this conditional can disappear. See more about this hack at zulip#4273 (comment).
In the previous commit, we removed our last call site of `getStateWithRoute` where 'realm' was passed for `route`. Therefore, this conditional can disappear. See more about this hack at zulip#4273 (comment).
In the previous commit, we removed our last call site of `getStateWithRoute` where 'realm' was passed for `route`. Therefore, this conditional can disappear. See more about this hack at zulip#4273 (comment).
Some preparation for #3804.