I'm very new to learning react and was simply following this example:
https://facebook.github.io/react-native/docs/navigation.html
import { StackNavigator, } from 'react-navigation';
const App = StackNavigator(
{ Home: { screen: HomeScreen }, Profile: { screen: ProfileScreen }, });
class HomeScreen extends React.Component {
static navigationOptions = { title: 'Welcome', };
render() {
const { navigate } = this.props.navigation;
return ( <Button title="Go to Jane's profile" onPress={() => navigate('Profile', { name: 'Jane' }) } /> ); }
}
But when I run this I get an error that says
"ProfileScreen is not defined"
I can't see what to do here since this wasn't on the documents page I linked to.
You are simply missing a React component called ProfileScreen. You have a HomeScreen:
class HomeScreen extends React.Component {
static navigationOptions = { title: 'Welcome', };
render() {
const { navigate } = this.props.navigation;
return (
<Button
title="Go to Jane's profile"
onPress={() => navigate('Profile', { name: 'Jane' }) }
/>
);
}
}
Now just define some kind of ProfileScreen:
const ProfileScreen = () => (
<View>
<Text>ProfileScreen</Text>
</View>
);
Related
When switching between the Home.js and Chat.js files, I get this warning: "Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method". I removed the only listener that's on Chat.js and tried only setting state when the component is mounted in Home.js and removing it on unmount but I still get this warning.
Home.js
import React, { Component } from "react";
import { View, FlatList } from "react-native";
import { ListItem } from "react-native-elements";
import fireStoreDB from "../database/FirestoreDB";
let _isMounted = false;
export default class Home extends Component {
constructor(props) {
super(props);
this.state = {
usersInfo: [],
refreshing: false
};
}
componentDidMount() {
_isMounted = true;
this.LoadUsers();
}
componentWillUnmount() {
_isMounted = false;
}
LoadUsers = () => {
fireStoreDB
.getAllUsersExceptCurrent()
.then(users =>
Promise.all(
users.map(({ id, username, avatar }) =>
fireStoreDB
.getUserLastMessage(fireStoreDB.getUID, id)
.then(message => ({ id, username, avatar, message }))
)
)
)
.then(users => {
if (_isMounted) {
this.setState({
usersInfo: users.filter(x => typeof x.avatar !== "undefined"),
refreshing: false
});
}
});
};
renderItem = ({ item }) => (
<ListItem
onPress={() => {
this.props.navigation.navigate("Chat", {
userTo: item.id,
UserToUsername: item.username,
LoadUsers: this.LoadUsers
});
}}
title={item.username}
subtitle={item.message}
leftAvatar={{ source: { uri: item.avatar } }}
bottomDivider
chevron
/>
);
render() {
return (
<View>
<FlatList
data={this.state.usersInfo}
renderItem={this.renderItem}
keyExtractor={item => item.id}
refreshing={this.state.refreshing}
onRefresh={() => {
this.setState({ refreshing: true });
this.LoadUsers();
}}
/>
</View>
);
}
}
Chat.js
import React, { Component } from "react";
import { View, KeyboardAvoidingView } from "react-native";
import { HeaderBackButton } from "react-navigation-stack";
import { GiftedChat } from "react-native-gifted-chat";
import * as Progress from "react-native-progress";
import fireStoreDB from "../database/FirestoreDB";
const Themes = {
primaryTheme: "#30D921",
secondaryTheme: "#B32D83",
layoutTheme: "#c0c0c0"
};
export default class Chat extends Component {
static navigationOptions = ({ navigation }) => ({
title: navigation.getParam("UserToUsername"),
headerLeft: (
<HeaderBackButton
onPress={() => {
navigation.state.params.LoadUsers();
navigation.goBack();
}}
/>
)
});
constructor(props) {
super(props);
this.state = {
messages: [],
userToId: this.props.navigation.getParam("userTo")
};
}
componentDidMount() {
fireStoreDB.getMessages(
message =>
this.setState(previousState => ({
messages: GiftedChat.append(previousState.messages, message)
})),
this.chatId
);
}
componentWillUnmount() {
fireStoreDB.removeSnapshotListener(this.chatId);
}
// gifted chat user props
get user() {
return {
_id: fireStoreDB.getUID,
name: fireStoreDB.getName,
avatar: fireStoreDB.getAvatar
};
}
// merge ids between two parties for one to one chat
get chatId() {
const userFromId = fireStoreDB.getUID;
const chatIdArray = [];
chatIdArray.push(userFromId);
chatIdArray.push(this.state.userToId);
chatIdArray.sort(); // prevents other party from recreating key
return chatIdArray.join("_");
}
render() {
if (this.state.messages.length === 0) {
return (
<View
style={{
alignItems: "center",
marginTop: 260
}}
>
<Progress.Bar indeterminate color={Themes.primaryTheme} />
</View>
);
}
return (
<View style={{ flex: 1 }}>
<GiftedChat
messages={this.state.messages}
onSend={messages => fireStoreDB.sendMessages(messages, this.chatId)}
user={this.user}
/>
<KeyboardAvoidingView behavior="padding" keyboardVerticalOffset={80} />
</View>
);
}
}
FirestoreDB.js
removeSnapshotListener = chatId => {
firebase
.firestore()
.collection("messages")
.doc(chatId)
.collection("chats")
.orderBy("createdAt")
.onSnapshot(() => {});
};
UPDATE:
With your implementation, you cannot unsubribe messages collection.
You could try to return unsubscribe function from getMessages, then use it in componentWillUnmount
FirestoreDB.js
getMessages = (callback, chatId) => {
return firebase
.firestore()
.collection("messages")
.doc(chatId)
.collection("chats")
.orderBy("createdAt")
.onSnapshot(callback);
}
Chat.js
componentDidMount() {
this.unsubcribe = fireStoreDB.getMessages(
message =>
this.setState(previousState => ({
messages: GiftedChat.append(previousState.messages, message)
})),
this.chatId
);
}
componentWillUnmount() {
this.unsubcribe();
}
i'm new in react native. i want to add if condition in initialRouteName like code below. when "notification" variable is null move to "MemberProfile" page. but if "notification" variable is not null move to "ReviewMember" page. i try it code but still move to "MemberProfile" page. any solution?.
this is my code
var notification = null;
class DrawerMember extends Component {
constructor(props) {
super(props);
this.state = {
notifData: null
};
this.callCheck();
}
async callCheck() {
await AsyncStorage.getItem("#tryCode:notification", (err, result) => {
if (result != null) {
this.setState({
notifData: "testing data"
});
}
});
}
render() {
notification = this.state.notifData;
return <Root />;
}
}
const Root = createDrawerNavigator(
{
MemberProfile: {
screen: MemberProfileScreen
},
ReviewMember: {
screen: ReviewScreen
}
},
{
drawerPosition: "right",
initialRouteName: notification == null ? "MemberProfile" : "ReviewMember",
contentComponent: props => <SideBar {...props} />,
}
);
export default DrawerMember;
I think Root is created before the async function returns so notification is always null.
A possible way to solve this problem would be to use a SwitchNavigator as the first screen in your drawer. This navigator would be responsible for loading the notification and redirecting to the right screen.
Something along the lines of:
import React from 'react';
import { View, AsyncStorage, ActivityIndicator, StatusBar } from 'react-native';
export default class DummySwitch extends React.Component {
async componentDidMount() {
this.listener = this.props.navigation.addListener('willFocus', async () => {
const notification = await AsyncStorage.getItem('#tryCode:notification');
if (notification === null) {
this.props.navigation.navigate('MemberProfile');
}
else {
this.props.navigation.navigate('ReviewMember');
}
});
}
render() {
return (
<View>
<ActivityIndicator />
<StatusBar barStyle='default' />
</View>
);
}
}
As you can see, the switch screen just displays a loading button while accessing the async storage and deciding which route to take.
Then you define the drawer as usual but you add the switch screen as the initial route. You can also hide the label if you want by defining your own drawerLabel:
export default createDrawerNavigator({
Switch: {
screen: Switch,
navigationOptions: () => ({
drawerLabel: () => null,
}),
},
MemberProfile: {
screen: MemberProfileScreen,
},
ReviewMember: {
screen: ReviewScreen,
},
}, { initialRouteName: 'Switch' });
This is it, the drawer now selects the route based on your async storage.
Hey guys im just start learning about React native context api I want to know that how can I implement this as globally like global state
and its also not working after navigate to another screen and why do we include class name in provider <ProfileScreen screen= {this.state.contextData}/> can we do it globally..
here's my code
global.cart=1
const Context = React.createContext(global.cart)
class HomeScreen extends Component<Props> {
constructor(props){
super(props);
this.state={
contextData:5
}
}
Incrementhome=()=>{
this.setState({contextData:global.cart})
global.cart++
}
Decrementhome=()=>{
this.setState({contextData:global.cart})
global.cart--
}
render() {
return (
<View>
<Context.Provider value={this.state.contextData}>
<Button title="Incrementhome"
onPress={this.Incrementhome}/>
<Button title="decrementhome"
onPress={this.Decrementhome}/>
<ProfileScreen screen= {this.state.contextData}/>
</Context.Provider>
<Button title='sd' onPress={()=>{this.props.navigation.navigate('Profile')}}/>
</View>
)
}
}
class profile screen which can show my data
class ProfileScreen extends Component<Props> {
render() {
return (
<View style={{}}>
<Context.Consumer>
{data=> <Text style={{fontSize:50}}>{data}</Text>}
</Context.Consumer>
</View>
);
}
}
class profile screens that is also a provider
class ProfileScreens extends Component<Props> {
static navigationOptions =
{
title: 'MainActivity', header: <Button title='sd' onPress={()=>{this.props.navigation.navigate('ProfileScreen')}}/>
};
constructor(props){
super(props);
this.state={contextData:0
}
}
render() {
return (
<View >
<Context.Provider value={this.state.contextData}>
<Button title="decrement" onPress={()=>{ this.props.changeHomeScreen() }}/>
<Button title='sd' onPress={()=>{this.props.navigation.navigate(Profile)}}/>
</Context.Provider>
</View>
);
}
}
my navigator
export default HomeScreen = createStackNavigator({
HomeScreen:{
screen:HomeScreen
},
Profile:{
screen:ProfileScreen
},
ProfileScreens:{
screen:ProfileScreens
},
})
Sorry but you didn’t implement well the React Context API. Read this https://medium.com/#mcssym/react-context-api-why-you-dont-surely-need-redux-co-e6d96ca8abca?source=linkShare-1d75ea07b723-1539164899
The way you pass contextData via screen prop is useless if you use Context.Consumer.
The navigation.navigate take a string not a React Component as parameter.
I Don't really know how to explain you easily so i'll rewrite your code with how you must do that job.
YOUR NAVIGATOR (somewhere/navigation.js)
export default Home = createStackNavigator({
HomeScreen:{
screen: HomeScreen
},
Profile:{
screen: ProfileScreen
},
ProfileScreens:{
screen: ProfileScreens // Don't need to be a Provider
},
})
Your ProfileScreens Don't need to be a Provider because you don't use it as a wrapper. But can be a Consumer because you use the contextData. I guess it's the same as in Your HomeScreen and the one you want to make global.
//IMPORTANT
import { withHomeContext } from './somewhere/contexts/home';
class ProfileScreens extends Component<Props> {
static navigationOptions = {
title: 'MainActivity',
header: <Button title='sd' onPress={()=> this.props.navigation.navigate('ProfileScreen')}/>
};
constructor(props){
super(props);
this.state = {
contextData: props.homeProvider.contextData // Get from global context home provider
};
}
decrementHome = () => {
// Calling decrement from homeProvider
if(this.props.homeProvider) this.props.homeProvider.decrement();
}
render() {
return (
<View >
{/*You must call the decrementHome from your provider*/}
<Button title="decrement" onPress={this.decrementHome}/>
<Button title='sd' onPress={()=> this.props.navigation.navigate('ProfileScreen') }/>
</View>
);
}
}
export default withHomeContext(ProfileScreens);
YOUR ProfileScreen.You must change the way you create it as a Consumer. A better to use a function withHomeContext created in your HomeContext class.
//IMPORTANT
import { withHomeContext } from './somewhere/contexts/home';
class ProfileScreen extends Component<Props> {
render() {
return (
<View style={{}}>
<Text style={{fontSize:50}}>{this.props.homeProvider.contextData}</Text>
</View>
);
}
}
export default withHomeContext(ProfileScreen);
And finally your HomeContext with your Provider and Consumer could be:
// In Your context/home.js
const HomeContext = React.createContext();
export class HomeProvider extends React.Component {
state = {
contextData: 5 //Default Value
};
decrementHome = () => {
this.setState(prevState => {
contextData: prevState.contextData - 1;
});
}
incrementHome = () => {
this.setState(prevState => {
contextData: prevState.contextData + 1;
});
}
getValues = () => {
return {
contextData: this.state.contextData,
decrement: this.decrementHome, // Call via homeProvider prop
increment: this.incrementHome // Call via homeProvider prop
}
}
render() {
return (
<HomeContext.Provider value={this.getValues()}>
{this.props.children}
</HomeContext.Provider>
);
}
}
export function withHomeContext(Component) {
class ComponentWithContext extends React.Component {
render {
return (
<HomeContext.Consumer>
{(value) => <Component {...this.props} homeProvider={value} />
</HomeContext.Consumer>
);
};
}
return ComponentWithContext;
}
In Your root App now
import { HomeProvider } from './somwhere/context/home';
import Home from './somwhere/navigation';
export default class App extends React.Component {
render() {
return (
<HomeProvider>
<Home />
</HomeProvider>
);
}
}
I'm working with React Native and React Navigation.
I have a component called App.js in which I declare the Drawer Navigation of React-Navigation.
In this I have an option to log out but I can not navigate to another component after removing the AsyncStorage
Does anyone know how to achieve it?
Thank you.
This is my code:
App.js
import { createDrawerNavigator, DrawerItems, NavigationActions } from 'react-navigation';
const customDrawerComponent = (props) => (
<SafeAreaView style={{ flex: 1 }}>
<ScrollView>
<DrawerItems
{...props}
/>
<TouchableOpacity style={styles.button} onPress={this.logOut} >
<Text> Logout </Text>
</TouchableOpacity>
</ScrollView>
</SafeAreaView>
);
logOut = () => {
// NOT WORKS
// this.props.navigation.navigate('Login')
//NOT WORKS:
this.myAction();
}
myAction = () => {
const nav = NavigationActions.navigate({
routeName: 'App',
});
return nav;
};
const AppDrawNavigator = createDrawerNavigator(
{
MainComponent: { screen: MainComponent,
navigationOptions: ({navigation}) => ({
drawerLockMode: 'locked-closed'
}) },
Login: { screen: LoginComponent,
navigationOptions: ({navigation}) => ({
drawerLockMode: 'locked-closed'
}) },
User: { screen: UsersComponent }
},
{
contentComponent: customDrawerComponent,
}
);
make this as a class like
export default class App extends React.Component {
constructor(props) {
super(props)
this.state = {
}
}
From your question I understand that either you want to :-
navigate from outside the components
navigate from components which do not have navigation prop.
For this I have tried 2 solutions and both work extremely fine though I based towards the second one.
First Solution
Use withNavigation from react-navigation package. If your components are deeply nested they wont have navigation prop unless u specify them manually or put them in context ;passing navigation prop becomes a real pain. So instead use withNavigation and your component would have navigation prop.
import {withNavigation} from "react-navigation";
const Component = ({navigation}) => {
const onPress = () => {
navigation.navigate(//ROUTE_NAME//)
}
return (
<TouchableOpacity onPress={onPress}>
<Text>Navigate</Text>
</TouchableOpacity>
)
}
export default withNavigation(Component);
Second Solution
Create a helper script and use that.
"use strict";
import React from "react";
import {NavigationActions} from "react-navigation";
let _container; // eslint-disable-line
export const navigation = {
mapProps: (SomeComponent) => {
return class extends React.Component {
static navigationOptions = SomeComponent.navigationOptions; // better use hoist-non-react-statics
render () {
const {navigation: {state: {params}}} = this.props;
return <SomeComponent {...params} {...this.props} />;
}
}
},
setContainer: (container) => {
_container = container;
},
reset: (routeName, params) => {
_container.dispatch(
NavigationActions.reset({
index: 0,
actions: [
NavigationActions.navigate({
type: "Navigation/NAVIGATE",
routeName,
params
})
]
})
);
},
goBack: () => {
_container.dispatch(NavigationActions.back());
},
navigate: (routeName, params) => {
_container.dispatch(
NavigationActions.navigate({
type: "Navigation/NAVIGATE",
routeName,
params
})
);
},
navigateDeep: (actions) => {
_container.dispatch(
actions.reduceRight(
(prevAction, action) =>
NavigationActions.navigate({
type: "Navigation/NAVIGATE",
routeName: action.routeName,
params: action.params,
action: prevAction
}),
undefined
)
);
},
getCurrentRoute: () => {
if (!_container || !_container.state.nav) {
return null;
}
return _container.state.nav.routes[_container.state.nav.index] || null;
}
};
In your parent component when you mount the navigation call following:-
"use strict";
import React from "react";
import App from "./routes";
import {navigation} from "utils";
class Setup extends React.Component {
render () {
return (
<App
ref={navigatorRef => {
navigation.setContainer(navigatorRef);
}}
/>
);
}
}
export default App;
Now, in your components you can directly use helpers from this script itself and navigation would be accessibly globally now.
import {navigate} from "utils/navigation";
For more details you can this thread
Your logout function is declared outside of the Navigator. This means you don't have access to the navigation prop there. However, your customDrawerComponent is a screen of your Navigator and it should have access to it.
So you can try something like this (props here are the props passed to the customDrawerComponent):
onPress={()=> {props.navigation.navigate("Login")}}
Plus your App.js seems kind of strange since you're not exporting any component. Have you pasted the whole code of App.js or just parts of it?
I have 2 classes: my default class HomeScreen used for the home page and another class MyList which I use to generate a flatlist on my HomeScreen.
My problem is that I do not succeed in building my navigation function in my MyList class.
I always get the following error: "Can't find variable: navigate".
I took a look at this Calling Navigate on Top Level Component but I really don't know how to implement it in my code.
Here's what I've tried:
class MyList extends React.Component {
_keyExtractor = (item, index) => item.note.id;
_renderItem = ({ item }) => (
<TouchableNativeFeedback
onPress={() => navigate('Note', { noteId: item.note.id })} >
<View>
<Text style={styles.noteElementTitle} >{item.note.title}</Text>
<Text style={styles.noteElementBody} >{item.note.body}</Text>
</View>
</TouchableNativeFeedback>
);
render() {
return (
<FlatList
data={this.props.data}
keyExtractor={this._keyExtractor}
renderItem={this._renderItem}
/>
);
}
}
export default class HomeScreen extends React.Component {
static navigationOptions = {
title: 'Notes',
headerStyle: { backgroundColor: 'rgb(255, 187, 0)' },
headerTitleStyle: { color: 'white' },
};
render() {
const { navigate } = this.props.navigation;
return (
<MyList
data={this.state.data}
load={this.state.load}
navig={this.props.navigation}
>
</MyList>
);
}
}
const Project = StackNavigator({
Home: { screen: HomeScreen },
NewNote: { screen: NewNoteScreen },
Note: { screen: NoteScreen }
});
AppRegistry.registerComponent('Project', () => Project);
Thanks for your help.
Because your MyList component is not part of your stack the navigation prop is not available for that.
You have 2 options.
First option you can pass the navigation prop manually to MyList
render() {
const { navigate } = this.props.navigation;
return (
<MyList
data={this.state.data}
load={this.state.load}
navigation={this.props.navigation}
>
</MyList>
);
}
Second option you can use withNavigation.
withNavigation is a Higher Order Component which passes the navigation
prop into a wrapped Component. It's useful when you cannot pass the
navigation prop into the component directly, or don't want to pass it
in case of a deeply nested child.
import { Button } 'react-native';
import { withNavigation } from 'react-navigation';
const MyComponent = ({ to, navigation }) => (
<Button title={`navigate to ${to}`} onPress={() => navigation.navigate(to)} />
);
const MyComponentWithNavigation = withNavigation(MyComponent);