Calling navigation.navigate('DrawerOpen') from inside a HOC doesn't open the drawer menu
I have written a higher order component which returns a screen with header for drawer navigator.
Header
const Header = (props)=>{
return (
<View style={{flex:1,backgroundColor:"red",justifyContent:"center"}}>
<TouchableWithoutFeedback onPress={() => {
props.navigation.navigate('DrawerOpen');
if (props.navigation.state.index === 0) {
props.navigation.navigate('DrawerOpen')
} else {
props.navigation.navigate('DrawerClose')
}
}}>
<View >
<Text style={{fontSize:20,color:"white"}}>{"Menu"}</Text>
</View>
</TouchableWithoutFeedback>
</View>
);
}
HOC
const createCompWithHeader = (Comp)=>{
return (props)=>{
return (
<View style={{flex:1,marginTop:20}}>
<Header {...props}/>
<View style={{flex:9}}>
<Comp {...props}/>
</View>
</View>
);
}
}
Drawer Navigator
const MyApp = DrawerNavigator({
Home: {
screen: createCompWithHeader(MyHomeScreen),
},
Notifications: {
screen: createCompWithHeader(MyNotificationsScreen),
},
});
Components(Screens)
class MyHomeScreen extends Component {
static navigationOptions = {
drawerLabel: 'Home'
};
render() {
return (
<View>
<Button
onPress={()=>this.props.navigation.navigate('DrawerOpen')}
title={"Open menu"}
/>
<Button
onPress={() => this.props.navigation.navigate('Notifications')}
title="Go to notifications"
/>
</View>
);
}
}
class MyNotificationsScreen extends Component {
static navigationOptions = {
drawerLabel: 'Notifications'
};
render() {
return (
<View>
<Button
onPress={()=>this.props.navigation.navigate('DrawerOpen')}
title={"Open Menu"}
/>
<Button
onPress={() => this.props.navigation.goBack()}
title="Go home"
/></View>
);
}
}
Current Behavior
when I click on the button in header(props.navigation.navigate('DrawerOpen')), the drawer menu doesn't open
When I click on the button inside the component, menu opens
Expected Behavior
On click of the button in the header, menu should open.
How to reproduce
Expo Url : Snack
Environment
| react-navigation |1.5.8
| react-native |0.55.2
After 2.0 release, rather than opening a drawer with navigation.navigate(‘DrawerOpen’), you can now call navigation.openDrawer().
Other methods are closeDrawer() and toggleDrawer().
I've checked your snack expo and the problem lies in this snippet
if (props.navigation.state.index === 0) {
props.navigation.navigate('DrawerOpen')
} else {
props.navigation.navigate('DrawerClose')
}
The navigation does not add an index to the state, therefore it's value remains undefined
So what you are doing is , Opening and Closing the drawer yourself
Therefore remove this snippet to see it work.
onPress={() => props.navigation.navigate('DrawerOpen')}
Related
I have two components one is Messages and other component is Navigation it's a stack navigator component. I would like to call function named onRefresh of Messages component from component Navigation header buttons. Please see my code how can I achieve this.
Messages.js (component file)
export default class Messages extends Component {
// Constructor
constructor(props) {
super(props)
this.state = {
messages: [],
isLoading: true
};
}
// End Constructor
// Getting Messages from server
async getMessages() {
try {
const response = await fetch('https://reactnative.dev/movies.json');
const json = await response.json();
this.setState({ messages: json.movies });
} catch (error) {
console.log(error);
} finally {
this.setState({ isLoading: false });
}
}
// End Getting messages from server
componentDidMount() {
this.getMessages();
}
// On refresh the messages
onRefresh = async () => {
this.setState({isLoading: true,}, () => {this.getMessages();});
}
// Send now sms using button.
sendNowMessage = async (title) => {
Alert.alert(title, "asdasdas");
}
render() {
const { messages, isLoading } = this.state;
return (
<SafeAreaView style={styles.container}>
{isLoading ? <ActivityIndicator size="large" color="#0000ff" style={ styles.horizontal } /> : (
<FlatList
data={ messages }
keyExtractor={({ id }, index) => id}
onRefresh={() => this.onRefresh()}
refreshing={this.state.isLoading}
renderItem={({ item }) => (
<View style={styles.item}>
<Text style={{ marginBottom: 12, }}>{item.title}, {item.releaseYear}</Text>
<Button
onPress={() => this.sendNowMessage(item.title)}
title="Send SMS"
style={styles.sendSMS}
color="#841584"
accessibilityLabel="Learn more about this purple button"
/>
</View>
)}
/>
)}
</SafeAreaView>
);
}
}
Navigation.js (component file)
const Stack = createStackNavigator();
export default function Navigation() {
return (
<Stack.Navigator>
<Stack.Screen
name="AuthScreen"
component={AuthScreen}
options={{
title: "Welcome",
}}
/>
<Stack.Screen
name="Messages"
component={Messages}
options={{
headerTitle: 'Messages',
headerRight: () => {
return (
<View style={styles.headerButtons}>
<View style={{ marginRight:10, }}>
<TouchableOpacity
onPress={() => {
new Messages().onRefresh() <---- I am calling it like this.
}}
>
<Text
style={styles.actionButtons}
>
<Ionicons name="reload-sharp" size={20} color="black" />
</Text>
</TouchableOpacity>
</View>
<View>
<TouchableOpacity
onPress={() => {
alert("working")
}}
>
<Text
style={styles.actionButtons}
>
<Ionicons name="filter" size={20} color="black" />
</Text>
</TouchableOpacity>
</View>
</View>
)
}
}}
/>
</Stack.Navigator>
);
}
I am calling onRefresh function like this new Messages().onRefresh() and getting the following error.
Can't call setState on a component that is not yet mounted. This is a no-op, but it might indicate a bug in your application. Instead, assign to this.state directly or define a state = {}; class property with the desired state in the Messages component.
I want to learn what is the best way to call onRefresh function.
I have this flatlist which lists all the jobs,uponclick we are taken to a Job details page where you can apply to the job. On apply, the button name changes to "applied" and it is disabled. I got this part working,. however when I go back to homepage and click on the same job again, the button is clickable and still says "apply now", instead of "applied". Does this have anything to do with flatlist and navigation?
Home.js:
export default function Home(params) {
const{ navigation}=params;
...
return(
<FlatList
data={getCompanies(companies, filter)}
renderItem={({ item }) => {
return(
<TouchableOpacity onPress={()=>{ navigation.navigate('Job Details',item)}}>
<View style={styles.listingCard}>
<View style={styles.listingCardBody}>
<Text style={styles.listingCardTitle}>{item.title}</Text>
<Text style={styles.listingCardDescription}>
Here is a really simple description from {item.title} about the
role being advertized...
</Text>
</View>
</View>
</TouchableOpacity>
);
}}
keyExtractor={(item, index) => { return item.id; }}
contentContainerStyle={styles.content}
/>
);}
Job Details:
function Jobdetails({route, navigation}){
const{ title, Category, rateHourly, responsibilities,location, postedBy,id,jobOverview, jobType, startDate, endDate, latitude, longitude}= route.params;
const [ butText, setButText] = useState("Apply Now");
const [ clickable, setClickable ]= useState(false);
<TouchableOpacity disabled={ clickable } style={page.button} onPress={()=>{
//apply() works fine
apply(id).then(async (result)=>{
if(result.succeeded()){
setButText('Application Successful!');
setClickable(true);
}
});
}}>
<Text style={page.text}>{ butText }</Text>
</TouchableOpacity>
Why isn't 'clickable' value being remembered by the app?
Following up on my comment,
Create a CLickableContext.js with
import { createContext } from "react";
export const CLickableContext= createContext(null);
On a component higher up in your app, set up the provider with the usestate hook.
import { CLickableContext} from "../CLickableContext";
...
const [clickable, setClickable] = useState(false);
...
<CLickableContext.Provider value={{ clickable, setClickable }}>
<YourApp/>
</CLickableContext.Provider>
Then include the context in any compenent throughout the app
import { useContext } from "react";
import { CLickableContext} from "../CLickableContext";
//Home.js
export default function Home(params) {
const { clickable, setClickable } = useContext(CLickableContext);
const{ navigation}=params;
...
return(
<FlatList
data={getCompanies(companies, filter)}
renderItem={({ item }) => {
return(
<TouchableOpacity onPress={()=>{ navigation.navigate('Job Details',item)}}>
<View style={styles.listingCard}>
<View style={styles.listingCardBody}>
<Text style={styles.listingCardTitle}>{item.title}</Text>
<Text style={styles.listingCardDescription}>
Here is a really simple description from {item.title} about the
role being advertized...
</Text>
</View>
</View>
</TouchableOpacity>
);
}}
keyExtractor={(item, index) => { return item.id; }}
contentContainerStyle={styles.content}
/>
);}
And
//Jobdetails.js
import { useContext } from "react";
import { CLickableContext} from "../CLickableContext";
function Jobdetails({route, navigation}){
const { clickable, setClickable } = useContext(CLickableContext);
const{ title, Category, rateHourly, responsibilities,location, postedBy,id,jobOverview, jobType, startDate, endDate, latitude, longitude}= route.params;
const [ butText, setButText] = useState("Apply Now");
<TouchableOpacity disabled={ clickable } style={page.button} onPress={()=>{
//apply() works fine
apply(id).then(async (result)=>{
if(result.succeeded()){
setButText('Application Successful!');
setClickable(true);
}
});
}}>
<Text style={page.text}>{ butText }</Text>
</TouchableOpacity>
So I have a homescreen that renders a modal component. I use some states for modal visibility. However, the problem is that when I navigate from the modal to another screen and then back to my home screen, the modal is still opened and I cannot figure out how to close it. I tried using useEffect but it does not do anything. Any tips?
This is home screen.
The AddButton component is a simple TouchableOpacity that onPress call the toggleModal function
const [isModalVisible, setIsModalVisible] = useState(false);
const toggleModal = () => {
setIsModalVisible(!isModalVisible);
};
useEffect(() => {
setIsModalVisible(false)
}, [navigation])
return (
<AddButton
title="ADD BOOK"
toggleModal={toggleModal}
/>
)
And the Modal Component is this:
const ModalComponent = ({navigation, isModalVisible, toggleModal, title, author, save, onNavigate }) => {
return (
<Modal isVisible={isModalVisible}>
<View style={styles.container}>
<Text>Modal</Text>
<View style={styles.footer}>
<TouchableOpacity onPress={toggleModal}>
<Text>Cancel</Text>
</TouchableOpacity>
<TouchableOpacity onPress={() => navigation.navigate("AddBookScreen")}>
<Text>{save}</Text>
</TouchableOpacity>
</View>
</View>
</Modal>
);
};
Modify this code
<TouchableOpacity onPress={() => navigation.navigate("AddBookScreen")}>
<Text>{save}</Text>
</TouchableOpacity>
to
<TouchableOpacity onPress={() => {
navigation.navigate("AddBookScreen")
toggleModal();
}}>
<Text>{save}</Text>
</TouchableOpacity>
extract onPress function and move it up to your page and pass it as onSaveClick callback to the modal component.
// on your page
const onSaveClick = () => {
navigation.navigate("AddBookScreen")
setIsModalVisible(false)
}
then in the modal
<TouchableOpacity onPress={onSaveClick}>
<Text>{save}</Text>
</TouchableOpacity>
How can I access (get and set) state inside a container component?
I am getting Undefined is not an object evaluating this.state.* because it cannot be accessed from MultiImagePost. Is there a way to bind this to the container component?
export default class ImagePost extends React.Component {
state = {
index: 0,
modalVisible: false,
currentImages: []
};
render() {
const { imageCount } = this.props;
if (imageCount == 1) {
return <SingleImagePost postObject={this.props} />
} else {
return <MultiImagePost postObject={this.props} />
}
}
const MultiImagePost = ({ postObject }) => (
<View>
<FlatList
data={[{key: 'a'}]}
renderItem={({item}) =>
<TouchableOpacity>
<View>
<FlatGrid
itemDimension={100}
items={postObject.images}
renderItem={({ item, index }) => (
<TouchableHighlight
onPress={() => {
this.setState({
modalVisible: true,
index: this.state.index,
currentImages: postObject.images
});
}}>
<Image
source={{ uri: item }}
/>
</TouchableHighlight>
)}
/>
</View>
</TouchableOpacity>
}
/>
</View>
);
}
MultiImagePost is a stateless(functional) component, so it has no state.
U should manage state in ImagePost or consider using React hook.
I can't run your code, if MultiImagePost is inside ImagePost, you may try to see if it works.
I think you have mixed up class component with functional component. You need to change your code a bit like,
export default class ImagePost extends React.Component {
state = {
index: 0,
modalVisible: false,
currentImages: []
};
render() {
const { imageCount } = this.props;
if (imageCount == 1) {
return <SingleImagePost postObject={this.props} />
} else {
return <>{this.MultiImagePost(this.props)}</> //Instead of component make it as function call
}
}
//Make this as instance function
MultiImagePost = (postObject) => (
<View>
<FlatList
data={[{key: 'a'}]}
renderItem={({item}) =>
<TouchableOpacity>
<View>
<FlatGrid
itemDimension={100}
items={postObject.images}
renderItem={({ item, index }) => (
<TouchableHighlight
onPress={() => {
this.setState({
modalVisible: true,
index: this.state.index,
currentImages: postObject.images
});
}}>
<Image source={{ uri: item }} />
</TouchableHighlight>
)}
/>
</View>
</TouchableOpacity>
}
/>
</View>
);
}
I am pulling data from my firestore DB, one of the fields on each DB item is image, the value of each being a URL to an image with my firebase storage.
What I'd like to do is pull the image directly from storage as part of the loop that pulls from firestore.
I am trying to do this as when this loop renders the items, the images are taking a few seconds to appear on the screen.
Please advise if there is an alternative to solve this issue?
FirebaseData.js
onCollectionUpdate = (querySnapshot) => {
const data = [];
querySnapshot.forEach((doc) => {
const { title, image, url, description } = doc.data();
data.push({
key: doc.id,
doc, // DocumentSnapshot
title,
image,
description,
url
});
});
this.setState({
data,
loading: false
});
}
render() {
if (this.state.loading) {
return <Spinner />; //RETURN A LOADING ICON
}
return (
<List containerStyle={styles.listStyle}>
<FlatList
data={this.state.data}
renderItem={({ item }) => <ChildList {...item} />}
/>
</List>
);
}
}
ChildList.js
export default class ChildListRow2 extends React.PureComponent {
render() {
return (
<ListItem
title={
<View>
<Image source={{ uri: this.props.image }} style={styles.imageStyle} />
<Text style={styles.titleTextStyle}>{this.props.title}</Text>
</View>
}
subtitle={
<View>
<Text style={styles.subTitleTextStyle}>{this.props.description}</Text>
</View>
}
hideChevron
onPress={() => Linking.openURL(this.props.url)}
containerStyle={styles.listItemStyle}
/>
);
}
}
You can use onLoadEnd and onLoadStart properties of Image component to show and hide the rest of the data. You can show a loading spinner till the file loads and then show the complete component when it finishes.
Example
export default class ChildListRow2 extends React.PureComponent {
constructor() {
super();
this.state = {
loaded: false
};
}
imageLoaded = () => {
this.setState({ loaded: true })
}
render() {
return (
<ListItem
title={
<View>
<Image
source={{ uri: this.props.image }}
style={styles.imageStyle}
onLoadEnd={this.imageLoaded}
/>
<Text style={styles.titleTextStyle}>{this.props.title}</Text>
</View>
}
subtitle={
<View>
<Text style={styles.subTitleTextStyle}>{this.props.description}</Text>
</View>
}
hideChevron={this.state.loaded}
onPress={() => Linking.openURL(this.props.url)}
containerStyle={styles.listItemStyle}
/>
);
}
}