Context API w/ React Navigation (React Native) - react-native

I'm trying to wrap my mind around using Context in my React Native app that uses React Navigation. I think I am way off on this one. I am simply trying to pass the name of a book to my entire app through the navigation stacks.
App.js
const BookContext = React.createContext();
class BookProvider extends Component {
state = {
name: 'book name'
}
render() {
return (
<BookContext.Provider value={{
name: this.state.name
}}>
{this.props.children}
</BookContext.Provider>
)
}
}
export default function App() {
return (
<BookProvider>
<BookContext.Consumer>
{({ name }) => (<Routes name={name} />)} //my react navigation stacks component
</BookContext.Consumer>
</BookProvider>
);
}
and in Book.js (a component in the navigation stack)
componentDidMount() {
console.log(this.context)
}
returns an empty object {}
Any help is appreciated!

To save you some Googling, this is the correct approach: https://github.com/react-navigation/react-navigation/issues/935#issuecomment-359675855
Another way, if you're using a relatively new version of React, and your component in question at that route is a functional component, is to use the useContext React hook.
e.g.
import React, { useContext } from 'react'
import { BookContext } from '/path/to/BookContext'
function BookConsumerComponent() {
const { name } = useContext(BookContext);
console.log(name);
}

Related

Making navigation available to all components in React Native app

I'm using React Navigation 5 in my React Native app and I'm not exactly clear about how to make navigation available to all components. I also want to mention that my app uses Redux for state management but I didn't integrate React Navigation with Redux -- maybe I should!
My App.js renders my Navigator.js component which looks like below. BTW, my app requires authentication and one of the key functions of the navigator function is to redirect unauthenticated users to login screen.
class Navigator extends Component {
render() {
return (
<NavigationContainer>
{
this.props.isAuthenticated
? <MainMenuDrawer.Navigator drawerContent={(navigation) => <DrawerContent member={this.props.member} navigation={navigation} drawerActions={DrawerActions} handleClickLogOut={this.handleClickLogOut} />}>
<MainMenuDrawer.Screen name="Home" component={HomeStack} />
<MainMenuDrawer.Screen name="ToDoList" component={ToDoListStack} />
</MainMenuDrawer.Navigator>
: <SignInStack />
}
</NavigationContainer>
);
}
}
Then in my HomeStack, I have my Dashboard component which happens to be a class component that needs to access navigation. Here's my HomeStack:
const HomeStackNav = new createStackNavigator();
const HomeStack = () => {
return(
<HomeStackNav.Navigator screenOptions={{ headerShown: false }}>
<HomeStackNav.Screen name="DashboardScreen" component={Dashboard} />
<HomeStackNav.Screen name="SomeOtherScreen" component={SomeOtherComponent} />
</HomeStackNav.Navigator>
);
}
export default HomeStack;
Say, my Dashboard component looks like below. How do I make navigation available to this component?
class Dashboard extends Component {
handleNav() {
// Need to use navigation here...
}
render() {
return (
<Text>Welcome to Dashboard</Text>
<Button onPress={() => this.handleNav()}>Go somewhere</Button>
);
}
}
function mapStateToProps(state) {
return {
member: state.app.member
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(appActions, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Dashboard);
Do I need to pass navigation manually to each component? That seems too repetitive and prone to error. How do I make navigation available throughout my app?
Solution 1:
you can use useNavigation hook for functional component
import {useNavigation} from '#react-navigation/native';
const navigation = useNavigation();
navigation.navigate("interests");
//or
navigation.push("interests");
Solution 2:
you can use HOC withNavigation to navigation in props in any component for class component Ref
you can install #react-navigation/compat by
yarn add #react-navigation/compat
You can import like below
import { withNavigation } from '#react-navigation/compat';
you can use withNavigation like below
export default withNavigation(Dashboard)
Note: then you can use this.props.navigation in Dashboard component

How to use withNavigation in react-navigation v5?

I have a nested component, and I want to use withNavigation in the nested component in react-navigation v5.
why you don't create your own withNavigation
import React from 'react';
import { useNavigation } from '#react-navigation/native'; // not sure package name
export const withNavigation = (Component: any) => {
return (props: any) => {
const navigation = useNavigation();
return <Component navigation={navigation} {...props} />;
};
};
React Navigation Version: 5.x
Sometimes you need to trigger a navigation action from places where you do not have access to the navigation prop, such as a Redux middleware. For such cases, you can dispatch navigation actions from the navigation container.
If you're looking for a way to navigate from inside a component without needing to pass the navigation prop down. Do not use this method when you have access to a navigation prop or useNavigation since it will behave differently, and many helper methods specific to screens won't be available.
You can get access to the root navigation object through a ref and pass it to the RootNavigation which we will later use to navigate.
// App.js
import { NavigationContainer } from '#react-navigation/native';
import { navigationRef } from './RootNavigation';
export default function App() {
return (
<NavigationContainer ref={navigationRef}>{/* ... */}</NavigationContainer>
);
}
In the next step, we define RootNavigation, which is a simple module with functions that dispatch user-defined navigation actions.
// RootNavigation.js
import * as React from 'react';
export const navigationRef = React.createRef();
export function navigate(name, params) {
navigationRef.current?.navigate(name, params);
}
// add other navigation functions that you need and export them
Then, in any of your javascript modules, just import the RootNavigation and call functions that you exported from it. You may use this approach outside of your React components and, in fact, it works just as well when used from within them.
// any js module
import * as RootNavigation from './path/to/RootNavigation.js';
// ...
RootNavigation.navigate('ChatScreen', { userName: 'Lucy' });
Apart from navigate, you can add other navigation actions:
import { StackActions } from '#react-navigation/native';
export function push(...args) {
navigationRef.current?.dispatch(StackActions.push(...args));
}
https://reactnavigation.org/docs/navigating-without-navigation-prop/

How to change react native app layout from api

I have developed an store app, my boss wants a feature that from wordpress panel select predefined layout to change the whole design and choose which category to be first or .... .
I have created all designs and components that needed, but I do not know how to change app layout that I recieved from api, is there any code or help for that. This change is not about color , its about changing whole home page app layout
Sorry for my english
Here is a simple example that you could implement.
You'll need to create custom complete components for each layout for the homepage.
Then you'll need to call the Wordpress API to get the layout name that needs to be displayed.
import React, { PureComponent } from 'react';
import { AppRegistry } from 'react-native';
import Layout1 from './components/Home/Layout1';
import Layout2 from './components/Home/Layout2';
import Layout3 from './components/Home/Layout3';
import Layout4 from './components/Home/Layout4';
import Loading from './components/Loading';
class HomePage extends PureComponent {
constructor(props) {
super(props);
this.state = {
layout: null
};
}
async componentDidMount() {
const response = await fetch('https://example.com/wp-json/whatever-api-endpoint')
.then(r => r.json());
this.setState({
layout: response
});
}
getContentElement = () => {
switch (this.state.layout) {
case 'layout_1': return <Layout1 />;
case 'layout_2': return <Layout2 />;
case 'layout_3': return <Layout3 />;
case 'layout_4': return <Layout4 />;
default: return <Loading />
}
};
render() {
const contentElement = this.getContentElement();
return (
<View>
{contentElement}
</View>
);
}
}
AppRegistry.registerComponent('MyApp', () => HomePage);

Why is this.props.componentId needed?

Why is this.props.componentId needed?
What is its purpose?
Why can't we use the library without that id being needed?
react-navigation doesn't need something like that, and react-native-navigation v1 didn't use anything like that. So why does v2 needs and uses that? The reason I ask is firstly to understand it, and secondly to see if I can skip this since now I cannot use RNN v2 from a saga.
Here's a detailed answer from a blogpost by the react-native-navigation library developer.
So now we want to enable the following behavior: after user clicks on the text, the app pushes the ViewPost screen. Later on it will be very easy to attach the same function to a list item instead of the text. To push a new screen into this screen’s navigation stack, we will use Navigation.push. In the new API this method expects to receive the current componentId which can be found in props.componentID. So in PostsList.js we create a pushViewPostScreen function and attach it to the onPress event of the Text.
import React, {PureComponent} from 'react';
import {View, Text} from 'react-native-ui-lib';
import PropTypes from 'prop-types';
import {Navigation} from 'react-native-navigation';
class PostsList extends PureComponent {
static propTypes = {
navigator: PropTypes.object,
componentId: PropTypes.string
};
constructor(props) {
super(props);
this.pushViewPostScreen = this.pushViewPostScreen.bind(this);
}
pushViewPostScreen() {
// We pass the componentId to Navigation.push
// to reference the which component will be pushed
// to the navigation stack
Navigation.push(this.props.componentId, {
component: {
name: 'blog.ViewPost',
passProps: {
text: 'Some props that we are passing'
},
options: {
topBar: {
title: {
text: 'Post1'
}
}
}
}
});
}
render() {
return (
<View flex center bg-blue60>
<Text onPress={this.pushViewPostScreen}>Posts List Screen</Text>
</View>
);
}
}
export default PostsList;
In the official docs, it seems that the Screen API, which is responsible of pushing, popping and changing navigation, will be accessible through the Navigation module always expecting a props.componentId to get the reference to the component.

Could not find "store" in either the context or props of "Connect"

I have been using React Native for a few years and have only recently needed to utilise Redux on a new, more complex project. I am currently in the process of following a number of tutorials trying to work my way through the basics.
I am currently stuck with the following error:
Invariant Vilation: Could not find "store" in either the context of props of "Connect(App)"
I have found a number of posts with information about this error but because of the low amount of knowledge I currently have, I am unsure as to how to correctly implement a fix.
This project was created with create-react-native-app and I am using the Expo app to test.
In my eyes this should work because the root element of App is a Provider element passing the store as a prop which seems to contradict what the error is saying.
configureStore.js
import { createStore, applyMiddleware } from 'redux';
import app from './reducers';
import thunk from 'redux-thunk';
export default function configureStore() {
return createStore(app, applyMiddleware(thunk));
}
App.js:
import React from 'react';
import { Text } from 'react-native';
import { Provider, connect } from 'react-redux';
import configureStore from './configureStore';
import fetchPeopleFromAPI from './actions';
const store = configureStore();
export class App extends React.Component {
componentDidMount() {
props.getPeople()
}
render() {
const { people, isFetching } = props.people;
return (
<Provider store={store}>
<Text>Hello</Text>
</Provider>
);
}
}
function mapStateToProps(state) {
return {
people: state.people
}
}
function mapDispatchToProps(dispatch) {
return {
getPeople: () => dispatch(fetchPeopleFromAPI())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
You are trying to access the store in the App component even before it has been passed. Therefore it is not able to find the store.
You need to make a separate component and connect that using react-redux such as
<Provider store={store}>
<ConnectedComponent />
</Provider>
...
class ConnectedComponent extends React.Component {
componentDidMount () {
this.props.getPeople()
}
render() {
return (
<View>
<Text> ... </Text>
</View>
)
}
}
function mapStateToProps(state) {
return {
people: state.people
}
}
function mapDispatchToProps(dispatch) {
return {
getPeople: () => dispatch(fetchPeopleFromAPI())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ConnectedComponent);