How to know drawer state inside react Component onStateChange V5 in react-navigation - reactjs

I would like to know when the drawer was opened/closed on onStateChange (in V4 I could get it from the state, but in V5 I can't find this info). Or any other idea as long as I stay with the component context
App.js
const Drawer = createDrawerNavigator();
function MyDrawer(props) {
return (
<NavigationContainer onStateChange={props.onStateChange}>
<Drawer.Navigator drawerType="front" drawerPosition="left">
<Drawer.Screen name="Properties" component={PropertiesTabs} />
<Drawer.Screen name="Profile" component={Profile} />
</Drawer.Navigator>
</NavigationContainer>
)
};
export default with(MyDrawer)
With.tsx
export function with(Component: any) {
class PNDContainer extends React.Component {
child: any;
componentDidMount() {
//debugger;
console.log('PND Mounted - First Time Screen');
}
componentWillUnmount() { }
render() {
const myHookValue = this.props.myHookValue;
debugger;
return (<Component onStateChange={(state) => {
debugger;
console.log('Screen Changed');
}} />)
}
}
return PNDContainer;
}
I know that it's possible to implement with functional components (but that's not what I need).
I followed couple of examples with no luck How to Use React Hooks in Class Components

onStateChange={(state) => {
const isDrawerOpen = state.history.some((it) => it.type === 'drawer');
}
Another option is to use the fiber node tree find the DraverView type and check if it's open

Related

React Native - pass props from One screen to another screen (using tab navigator to navigate)

I need to pass data from my HomeScreen to my SecondScreen. There are a ton of examples of how to do this if i'm clicking a button on the HomeScreen to navigate to SecondScreen, but can't find anything showing how to pass to SecondScreen if I'm using a v2 bottom tab navigator to go from HomeScreen to SecondScreen. I've tried screenprops and a couple other methods and spent about 8 hours trying to figure it out but could not get it to work. Any idea how to do this? Please, any hint would be amazing. Here is my code:
MainTabNavigator.js:
const config = Platform.select({
web: { headerMode: 'screen' },
default: {},
});
const HomeStack = createStackNavigator(
{
Home: HomeScreen,
},
config
);
HomeStack.navigationOptions = {
tabBarLabel: 'Home',
tabBarIcon: ({ focused }) => (
<MaterialIcons name="home" size={32} />
),
};
HomeStack.path = '';
const SecondStack= createStackNavigator(
{
Second: SecondScreen,
},
config
);
SecondStack.navigationOptions = {
tabBarLabel: 'Second screen stuff',
tabBarIcon: ({ focused }) => (
<MaterialIcons name="SecondScreenIcon" size={32} />
),
};
SecondStack.path = '';
const tabNavigator = createBottomTabNavigator({
HomeStack,
SecondScreen
});
tabNavigator.path = '';
export default tabNavigator;
HomeScreen.js:
class HomeScreen extends Component {
constructor(props){
super(props);
}
componentDidMount(){
this.setState({DataFromHomeScreen: 'my data that Im trying to send to SecondScreen'})
}
//....
SecondScreen.js:
class SecondScreen extends Component {
constructor(props){
super(props);
}
render()
return(
<View>{this.props.DataFromHomeScreen}</View>
)
//....
****Please find THINGS I'VE TRIED below:****
HomeScreen.js: when i do this it receives it at first but then passes null
render(){
return(
<View>
//all of my home screen jsx
<SecondScreen screenProps={{DataFromHomeScreen : 'data im trying to pass'}}/>
</View>
)
}
MaintTabNavigator.js: when i do this it receives it at first but then passes null
HomeStack.navigationOptions = {
tabBarLabel: 'Home',
tabBarIcon: ({ focused }) => (
<MaterialIcons name="home" size={32} />
),
};
<HomeStack screenProps={{DataFromHomeScreen:'data im trying to pass'}}/>
HomeStack.path = '';
I've tried like 5 other ways too that I can't even remember at this point. I don't want to have to call my database again in the second screen to get user info. Nobody I know knows react or react native. The React Native documentation at https://reactnavigation.org/docs/en/stack-navigator.html is minimal at best, only showing the below:
const SomeStack = createStackNavigator({
// config
});
<SomeStack
screenProps={/* this prop will get passed to the screen components as this.props.screenProps */}
/>
even if you go to the examples in the documentation and search for the word 'screenprop' you will not see any mention of the screen prop feature in either of the examples. All questions that I've seen only address how to pass props on button click which is easy. Is what I'm trying to do possible? I'm sure I'm not the only person using tab navigator who's retrieved data in the homescreen and need to pass it to other screens . Any advice helps. thanks.
ps.
Here is my Sign in class that is calling the Home screen:
class SignInScreen extends React.Component {
static navigationOptions = {
title: 'Please sign in',
};
render() {
return (
<View
style={styles.container}
contentContainerStyle={styles.contentContainer}>
<View>
<SocialIcon
title='Continue With Facebook'
button
type='facebook'
iconSize="36"
onPress={this._signInAsync}
/>
</View>
);
}
_signInAsync = async () => {
let redirectUrl = AuthSession.getRedirectUrl();
let result = await AuthSession.startAsync({
authUrl:
`https://www.facebook.com/v2.8/dialog/oauth?response_type=token` +
`&client_id=${FB_APP_ID}` +
`&redirect_uri=${encodeURIComponent(redirectUrl)}`,
});
var token = result.params.access_token
await AsyncStorage.setItem('userToken', token);
await fetch(`https://graph.facebook.com/me?fields=email,name&access_token=${token}`).then((response) => response.json()).then((json) => {
this.props.navigation.navigate('Home',
{
UserName : json.name,
FBID : json.id,
email : json.email
});
}) .catch(() => {
console.log('ERROR GETTING DATA FROM FACEBOOK')
});
};
}
export default SignInScreen;
I think you're calling your database in componentDidMount in your HomeScreen component, (I'm right?) and because another component in the same hierarchy needs the same data, you should considerer wrapping this into a new component and do the call to your data in that father component, then you pass the data to all the children that needs it. This is the react way to do things. The state of HomeScreen should not have the data, your data should live in a parent component in a higher hierarchy and pass the data to children as props.
In this way when you create your tabs you can pass the props as the react native docs suggest:
import { createBottomTabNavigator, BottomTabBar } from 'react-navigation-tabs';
const TabBarComponent = (props) => (<BottomTabBar {...props} />);
const TabScreens = createBottomTabNavigator(
{
tabBarComponent: props =>
<TabBarComponent
{...props}
style={{ borderTopColor: '#605F60' }}
/>,
},
);
Another solution could be to use a global state management with Redux or something similar.
I hope this helps.
Edit:
class Home extends React.Component{
constructor(props){
super(props);
this.state = {data: null}
}
componentDidMount() {
//get your props from navigation (your facebook credentials)
//your call to database
this.setState({data: yourResponseData});
}
render(){
const TabNavigator = createBottomTabNavigator(
{
HomeScreen: props =>
<HomeScreenStack
{...this.state.data}
/>,
SecondStack: props =>
<SecondStack
{...this.state.data}
/>,
},
);
return(
<TabNavigator />
)
}
}
const App = createAppContainer(Home);
export default App;
Use this.props.navigation.navigate.
In your HomeScreen, once you have the data you want to send, then navigate over to SecondScreen like so:
this.props.navigation.navigate('Second', { data: yourData })
To access this in SecondScreen whenever it is navigated to using navigation props, you can use NavigationEvents along with this.props.navigation.getParam.
/* your imports */
import { NavigationEvents } from 'react-navigation';
export default class SecondScreen extends React.Component {
/* your methods and properties */
render() {
<View>
<NavigationEvents
onDidFocus={() => this.setState({ data: this.props.navigation.getParam('data', {}) })}
/>
{ /* your SecondScreen render code */ }
</View>
}
}
Edit: For example, with your SignInScreen implementation, to access the props, use:
const username = this.props.navigation.getParam('UserName', '')
const fbid = this.props.navigation.getParam('FBID', 0)
const email = this.props.navigation.getParam('email', '')
This is the basic approach that I am using:
import {createBottomTabNavigator} from '#react-navigation/bottom-tabs';
const TestComponent = (props) => {
return <Text>{`TestComponent: ${props.name}`}</Text>;
};
const Home = () => {
const Tab = createBottomTabNavigator();
return (
<View style={{flex: 1}}>
<Tab.Navigator>
<Tab.Screen name="Screen 1">
{() => <TestComponent name="test 1" />}
</Tab.Screen>
<Tab.Screen name="Screen 2">
{() => <TestComponent name="test 2" />}
</Tab.Screen>
</Tab.Navigator>
</View>
);
};
Notice that to pass the props to a Screen I am using a child function instead of passing a value to component. The child function can then return the component you want in the syntax you are used to, that has the props available. In this case, the props are the simple name, but you can expand this to handle your state.
I ended up using Redux, it only took me like 100 read throughs and attempts to learn it, but once I learned it it's amazing and simple.

react-navigation wrap root AppContainer with react Context

I'm looking for the way to manage global state across my react-native app using react-navigation. I tried to implement basic React Context, which I wanted to wrap around the react-navigation's createAppContainer() method but it didn't work.
I ended up wrapping an app container from index.js file using Context's HOC, but it seems like react-navigation has a problem with re-rendering of nested components, when Context's state is changed. I can access my Context from nested Components but they just aren't re-rendered when context state is changed.
My index.js file looks like:
import { AppRegistry } from "react-native";
import App from "./src/App";
import { withAppContextProvider } from "./src/AppContext";
import { name as appName } from "./app.json";
AppRegistry.registerComponent(appName, () => withAppContextProvider(App));
My context class looks like:
// for new react static context API
export const AppContext = createContext({});
// create the consumer as higher order component
export const withAppContext = ChildComponent => props => (
<AppContext.Consumer>
{context => <ChildComponent {...props} global={context} />}
</AppContext.Consumer>
);
// create the Provider as higher order component (only for root Component of the application)
export const withAppContextProvider = ChildComponent => props => (
<AppContextProvider>
<ChildComponent {...props} />
</AppContextProvider>
);
export class AppContextProvider extends Component {
state = {
isOnline: true
};
handleConnectivityChange = isOnline => {
this.setState({ isOnline });
};
componentDidMount = async () => {
NetInfo.isConnected.addEventListener(
"connectionChange",
this.handleConnectivityChange
);
};
componentWillUnmount() {
NetInfo.isConnected.removeEventListener(
"connectionChange",
this.handleConnectivityChange
);
}
render() {
return (
<AppContext.Provider
value={{
...this.state
}}
>
{this.props.children}
</AppContext.Provider>
);
}
}
my App.js file looks like:
const HomeStack = createStackNavigator(
{
Home: HomeScreen,
Cities: CitiesScreen
},
getStackConfig({ initialRouteName: "Home" })
);
const SettingsStack = createStackNavigator(
{
Settings: SettingsScreen
},
getStackConfig({ initialRouteName: "Settings" })
);
export default createAppContainer(
createBottomTabNavigator(
{
Home: HomeStack,
Settings: SettingsStack
}
)
);
CitiesScreen component example:
import { AppContext } from "../AppContext";
class CitiesScreen extends Component {
static contextType = AppContext;
render() {
return (
<View style={styles.container}>
<Text>This value should change on isOnline update: {this.context.isOnline}</Text>
</View>
);
}
}
Now, when I'm accessing Context, from for example CitiesScreen component, I'm currently able to get the value of isOnline state of context but whenever I switch my internet connection (on android emulator) on/off, the context state is changed but the component isn't re-rendered and my shouldComponentUpdate() method isn't triggered. Any help to make this work?
In my case, I downgraded React from 16.8 to 16.5.0, react navigation version is 3.
I'm still investigating but that's a temporary solution for now.

React context update does not cause component rerender

I'm creating a code splitting solution for react app using react router, webpack and dynamic imports. The idea is to map all the routes and corresponding components to different app contexts and split code according to app contexts. When user visits some route the whole code chunk of related app context is being loaded.
Code examples:
App.tsx:
class App extends React.Component<Props, State> {
render() {
if (this.props.data.loading || this.props.localData.loading) {
return <Loader />
}
return (
<IntlProvider locale={this.props.localData.session.locale} messages={this.state.translations}>
<Router history={history}>
<Route
exact
path={`/screens/:action?`}
render={() => <ComponentLoader contextName={Context.Screens} componentName={'ScreenList'} />}
/>
</Router>
</IntlProvider>
)
}
}
export default withData(App)
ComponentLoader.tsx:
export enum Context {
Screens = 'Screens',
Channels = 'Channels'
}
const CONTEXT_LOADERS: { [name: string]: ComponentChunkLoader } = {
[Context.Screens]: () => import('../../routerContexts/screens'),
[Context.Channels]: () => import('../../routerContexts/channels'),
}
const loadedContexts: ContextsCollection = {}
class ComponentLoader extends React.PureComponent<Props, State> {
state: State = {
Component: null
}
async componentDidMount() {
this._updateComponent(this.props)
}
async componentDidUpdate(prevProps: Props, prevState: State) {
if (this.props.componentName !== prevProps.componentName || this.props.contextName !== prevProps.contextName) {
this._updateComponent(this.props)
}
}
_updateComponent = async (props: Props) => {
let module = loadedContexts[props.contextName]
? loadedContexts[props.contextName]
: await CONTEXT_LOADERS[props.contextName]()
if (!loadedContexts[props.contextName]) loadedContexts[props.contextName] = module
let ComponentClass = module[props.componentName]
this.setState({
Component: ComponentClass
})
}
render() {
if (this.state.Component !== null) {
return <this.state.Component />
}
return <Loader />
}
}
export default ComponentLoader
So when I switch to different routes, I see all the components correctly, and code splitting works correctly.
The problem is: when the data in react context updates, ScreenList component in the example doesn't get updated. When I pass ScreenList directly to react router Route everything works well. So the problem is in my ComponentLoader component that I use for code splitting.
Any ideas what can be the reason?
UPDATE:
Alrite, what i figured out now: if I wrap my ComponentLoader in a HOC that injects some data from context (like export default withRouter(ComponentLoader)) , everything works well and components rerender as expected. Why is happening like this?

How to pass props in StackNavigator

const MainNavigator = StackNavigator({
Home: {
screen: Tabs
},
Collection_Products : {
screen : Collection_Products,
navigationOptions : ({ navigation }) => ({
title : `${navigation.state.params.name.toUpperCase()}`
}),
},
MainProduct : {
screen : (props) => <MainProduct {...props} />,
},
});
export default class MainNav extends React.Component {
constructor() {
super();
this.state = {
checkout: { lineItems: { edges: [] } }
};
}
method1 = (args) => {
}
render() {
return (
<View style={{flex: 1}}>
<MainNavigator checkout= {this.state.checkout} method1 = {this.method1}/>
</View>
);
}
}
I am using react native to build application. I want to pass method 1 to different child components. How can I pass method1 function in MainProduct Component? With the syntax I am using I am only able to pass navigator props to child components.
The accepted answer is outdated for React Navigation v5 and v6
screenProps is no longer available, which caused several problems.
Use react context instead
Quote from react-navigation 5.x,
Due to the component based API of React Navigation 5.x, we have a much better alternative to screenProps which doesn't have these disadvantages: React Context. Using React Context, it's possible to pass data to any child component in a performant and type-safe way, and we don't need to learn a new API!
Alternatively, you may use routeProps
Passing parameters to routes. v6 doc
navigation.navigate('RouteName', { /* params go here */ })
In case you are new to context
Here is how you may use context to pass props.
Example:
import React from 'react'
// Create context outside of your component
// which can be export to your child component
const MyContext = React.createContext()
export default function MyParentComponent() {
const myContext = {
whatIWhatMyChildToKnow: 'Hi, I am your father.',
}
return (
<MyContext.Provider value={myContext}>
// Dont pass your own props to your navigator
// other than RNavigation props
<YourStackNavigator>
...stack logic
</YourStackNavigator>
</MyContext.Provider>
)
}
// access your context value here
function MyChildScreen() {
const { whatIWhatMyChildToKnow } = React.useContext(MyContext)
// will display 'Hi, I am your father.'
return <span>{whatIWantMyChildToKnow}</span>
}
You need to send the props as below. You need to send props name as 'screenProps' literally then only it is send. Yeah, This is little strange. I tried to change the name of the props, it did not get trough.
const propsForAll = {
checkout:/*your checkout data */,
method1: /**Your medhod1*/
}
<View style={{flex: 1}}>
<MainNavigator screenProps={propsForAll}/>
</View>
I think you may want screenProps, from the docs:
const SomeStack = StackNavigator({
// config
});
<SomeStack
screenProps={/* this prop will get passed to the screen components as
this.props.screenProps */}
/>

React router 1.0 pass multiple props to children routes

I'm using react-router 1.0 and react-redux on an app, and I'm wondering what strategy is best to pass props to children on larger apps. Here's a basic situation:
Let's say I have a route /admin/users/edit/:id with the following structure on its components:
Routes:
<Router>
<Route path="admin" component={Admin}>
<Route path="users" component={Users}>
<Route path="edit/:id" component={Edit}/>
</Route>
</Route>
</Router>
Admin:
class Admin extends React.Component {
render() {
return (
{this.props.children}
)
}
}
Users:
class User extends React.Component {
edit = (id, params) => {
const { dispatch } this.props;
dispatch(edit(id, params));
}
other functions (add, remove) ...
render() {
return (
{this.props.children}
)
}
}
function mapStateToProps(state) {
const { users } = state;
return { users };
}
export default connect(mapStateToProps)(User);
Edit:
class Edit extends React.Component {
submit () => {
const { id } = this.props.params;
const { firstName } = this.refs;
this.props.edit(id, {firstName: firstName.value});
}
render() {
const { id } = this.props.params;
const user = this.props.users[id];
return (
<form>
<input ref='firstName' defaultValue={user.firstName}/>
<button onClick={this.submit}>Submit</button>
</form>
)
}
}
How would I pass the users & edit function props down to the children?
I know about React.cloneElement() (as in https://github.com/rackt/react-router/tree/master/examples/passing-props-to-children), but if I have multiple routes like /users/add, /users/remove/:id, etc, I would be passing and exposing all the functions (edit, add, remove...) to all children. That solution doesn't seem to work very well when you have more than one children.
I would like to keep my children as dumb as possible, and use this same structure across the application (/images/add, /images/remove/:id, etc).
Any suggestions?
You have a few options:
First level children:
Use React.cloneElement(), that's something you are already aware of. I wouldn't use it for deeply nested Components though.
To all routes:
Use createElement():
<Router createElement={createElement} />
// default behavior
function createElement(Component, props) {
// make sure you pass all the props in!
return <Component {...props}/>
}
// maybe you're using something like Relay
function createElement(Component, props) {
// make sure you pass all the props in!
return <RelayContainer Component={Component} routerProps={props}/>
}
Check more on that in the React Router docs.
Use context:
Note:
Context is an advanced and experimental feature. The API is likely to change in future releases.
See how to use it in the React docs.
There is also the whole thread about it in the react-router#1531.

Resources