wix / react-native-navigation

A complete native navigation solution for React Native
https://wix.github.io/react-native-navigation/
MIT License
13.04k stars 2.67k forks source link

Best design pattern for uppermost wrapped component(migrating from react-navigation) #4830

Closed hahtml closed 5 years ago

hahtml commented 5 years ago

Issue Description

I am currently migrating my codebase from react-navigation to RNN. For the previous implementation, I have two more wrapped components under the Provider component. The architecture shows below

<Provider>
  <WebsocketManager>
     <ConnectionManager>
        <App> (render react-navigation navigator inside)
     </ConnectionManager>
  </WebsocketManager>
</Provider>

But right now if I understand and implement correctly, the new architecture of RNN should be this way

<Provider>
  <Screen 1 />
</Provider>

<Provider>
  <Screen 2 />
</Provider>

These screens share the same redux store. But what is the best way to embed my two more upper components here? Notice that I need those screen to use the same instance like singleton way. If I use HOC to wrap the Screen 1 and Screen 2..., each screen will be wrapped by new instances of managers. Some logics like open ws might run multiple times if there is no global flag to control. I am wondering if there is a RNN way to handle this condition and what the best design pattern is.


Environment

jinshin1013 commented 5 years ago

I'm not sure how your managers are being instantiated but you could instantiate those in the files where you register all your screens and pass it as a prop so that your provider files get the same instance. Very naive example:

// Provider.js
export const createProvider = (WrappedComponent, store, managers) => {
  class HOC extends React.Component {
    render() {
      return (
        <Provider store={store}>
          <WebSocketManager manager={managers.websocket}>
            <ConnectionManager manager={managers.connection}>
              <WrappedComponent {...this.props} />
            </ConnectionManager>
          </WebSocketManager>
        </Provider>
      )
    }
  }
  return HOC
}

// App.js
import { createProvider } from './Provider'
import screens from './screens'

Navigation.events().registerAppLaunchedListener(() => {
  const store = new store()
  const websocket = new WebsocketManager()
  const connection = new ConnectionManager()

  screens.forEach((screen) => {
    Navigation.registerComponent(screen.route, () => 
      createProvider(screen.component, store, { websocket, connection })
    )
  })

  ....
})
hahtml commented 5 years ago

@jinshin1013 Thx for ur reply. But the issue is that my WebSocketManager and ConnectionManager component need to be singleton pattern for all screens so that some state can be shared (for example, there is a ws opened or not). It is kind of weird that there are multiple websocket control centers (WebSocketManager in my implementation) for one app.

Both manager component render function return chidren

render() {
   return this.props.children;
  }

Why u pass those 'global' websocket and connection manager component as props into new instance of them.

jinshin1013 commented 5 years ago

I made an assumption that these websockets and connection managers can simply be a class instance and not a component where you can use like websocketManager.isOpened or something like this. That way you can just simply pass these instances to all the screens. Surely they don't have to be a component, or would you mind sharing your WebsocketManager and ConnectionManager components?

hahtml commented 5 years ago

Both managers include tons of functions, states and props. We use component to implement them so that some props and states are subscribed and some functions like reconnect can be self-triggered in lifecycle functions (reuse consideration).

jinshin1013 commented 5 years ago

Yes, states and props can still be there and it can still function the same as long as you pass them with the same instance of websocket or whatever you are using. Assuming yo are using a regular WebSocket:

// App.js
import { createProvider } from './Provider'
import screens from './screens'

Navigation.events().registerAppLaunchedListener(() => {
  const store = new store()
  const websocket = new WebSocket('ws:127.0.0.1:3000')
  // const connection = new ConnectionManager()

  screens.forEach((screen) => {
    Navigation.registerComponent(screen.route, () => 
      createProvider(screen.component, store, { websocket, connection })
    )
  })

  ....
})