Trying to use react navigation with hooks and a button in the navigation header.
I can pass the handleShowModalLogin function to the navigation header and I can see the button is clicked, but the problem is the setShowLoginModal is not updating the showLoginModal state to true. Not sure why this is not working.
import React, { useState, useEffect, useLayoutEffect } from "react";
import {
Image,
Platform,
ScrollView,
StyleSheet,
Text,
TouchableOpacity,
View,
Button,
} from 'react-native';
import LoginModal from './users/LoginModal';
const HomeScreen = ({navigation}, props) => {
const [showLoginModal, setShowLoginModal] = useState(false);
const handleShowModalLogin = (value) => {
console.log("showLoginModal button clicked: ", value)
if(value === "on"){
setShowLoginModal(true);
}else{
setShowLoginModal(false);
}
}
useEffect(() => {
console.log('navigation handler set with showLoginModal set:', showLoginModal)
navigation.setParams({ handleShowModalLogin: handleShowModalLogin });
}, []);
useEffect(() => {
console.log("showLoginModal value changed: ", showLoginModal), [showLoginModal]
})
return (
<View style={styles.container}>
<LoginModal showLoginModal={showLoginModal} />
<ScrollView
style={styles.container}
contentContainerStyle={styles.contentContainer}>
</ScrollView>
</View>
);
};
HomeScreen.navigationOptions = ({ navigation }) => ({
title: "Home",
headerRight: (
<View style={styles.headerComContainer}>
<Button
onPress={() => {
navigation.getParam('handleShowModalLogin')('on')
}}
title="Login"
color="#841584"
accessibilityLabel="Login" />
</View>
)
});
Here's the login modal component.
import React, { useState } from "react";
import { Text, TouchableOpacity, View, ScrollView } from "react-native";
import Modal from 'modal-enhanced-react-native-web';
export default function LoginModal(props){
const [visibleModal, setModalVisible] = useState(props.showLoginModal);
return (
<View>
<Modal
isVisible={visibleModal}
onBackdropPress={() => setModalVisible(false)}
>
<View>
<Text>Hello!</Text>
<TouchableOpacity onPress={() => setModalVisible(false)}>
<View>
<Text>Close</Text>
</View>
</TouchableOpacity>
</View>
</Modal>
</View>
);
}
const [visibleModal, setModalVisible] = useState(props.showLoginModal);
This code in the LoginModal creates a state, who's initial initial value is props.showLoginModal. After that initial value though, there's no connection with the prop. Changing the prop later will not cause the state to change.
You seem to be trying to mix having LoginModal be a controlled component (where a parent handles the logic, and then controls it through props) and an uncontrolled component (where the component manages its own state). Instead, i'd recommend picking one or the other.
From the fact that you're trying to control it externally, it looks like you want to create a controlled component. So your login modal will need modification to have additional props to notify the parent of the clicks. Perhaps an "onBackdropPressed" and an "onClosePressed", as in:
export default function LoginModal(props){
return (
<View>
<Modal
isVisible={props.showLoginModal}
onBackdropPress={() => props.onBackdropPressed()}
>
<View>
<Text>Hello!</Text>
<TouchableOpacity onPress={() => props.onClosePressed()>
<View>
<Text>Close</Text>
</View>
</TouchableOpacity>
</View>
</Modal>
</View>
);
}
Don't forget to modify the home screen to pass those additional props in, as in:
<LoginModal
showLoginModal={showLoginModal}
onBackdropPressed={() => setShowLoginModal(false)}
onClosePressed={() => setShowLoginModal(false)}
/>
Related
I've been trying to replace a view in React Native, but to no success. The app closes without errors whenever I try <TouchableOpacity onPress={() => {handleChangeMyView();}}> :
What am I doing wrong? How can I make it work?
Thank you all in advance.
import React, {
useState
} from 'react';
import {
SafeAreaView,
View,
TouchableOpacity,
} from 'react-native';
import MyInitialView from './MyInitialView';
const SiteContainer = () => {
let MyDynamicView = () => {
return (
<View></View>
);
};
const [MyDynamicViewArea, setMyDynamicViewArea] = useState(MyInitialView);
const handleChangeMyView = () => {
setMyDynamicViewArea(MyDynamicView);
};
return (
<SafeAreaView>
{MyDynamicViewArea}
<TouchableOpacity onPress={() => {handleChangeMyView();}}>
<View>
<FontAwesome name="quote-left"></FontAwesome>
</View>
</TouchableOpacity>
</SafeAreaView>
);
};
export default SiteContainer;
MyInitialView :
import React from 'react';
import {
View
} from 'react-native';
export default function MyInitialView() {
return (
<View></View>
);
}
You can use boolean value for viewing MyInitialView using useState
const [toViewMyInitialView, setToViewMyInitialView] = useState(false);
and in handleChangeMyView function set the above value as true
const handleChangeMyView = () => {
setToViewMyInitialView(true);
};
And in the SiteContainer
<SafeAreaView style={styles.siteContainer}>
// here I don't know a way to display a component in react-native, so
// you need to display the component MyInitialView if the
// toViewMyInitialView is true and when toViewMyInitialView id false it
// display MyDynamicViewArea
{toViewMyInitialView && <MyInitialView/>}
{!toViewMyInitialView && <MyDynamicViewArea/>}
<TouchableOpacity onPress={() => {handleChangeMyView()}}>
<View>
<FontAwesome name="quote-left"></FontAwesome>
</View>
</TouchableOpacity>
</SafeAreaView>
I've been trying to replace a view in React Native, but to no success. The app closes without errors whenever I try <TouchableOpacity onPress={() => {handleChangeMyView();}}> :
What am I doing wrong? How can I make it work?
Thank you all in advance.
import React, {
useState
} from 'react';
import {
SafeAreaView,
View,
} from 'react-native';
import MyInitialView from './MyInitialView';
const SiteContainer = () => {
let MyDynamicView = () => {
return (
<View></View>
);
};
const [MyDynamicViewArea, setMyDynamicViewArea] = useState(MyInitialView);
const handleChangeMyView = () => {
setMyDynamicViewArea(MyDynamicView);
};
return (
<SafeAreaView style={styles.siteContainer}>
{MyDynamicViewArea}
<TouchableOpacity onPress={() => {handleChagnStaceMyView();}}>
<View>
<FontAwesome name="quote-left"></FontAwesome>
</View>
</TouchableOpacity>
</SafeAreaView>
);
};
export default SiteContainer;
MyInitialView :
import React from 'react';
import {
View
} from 'react-native';
export default function MyInitialView() {
return (
<View></View>
);
}
You have incorrect name of function in onPress of TouchableOpacity.
Change this
onPress={() => {handleChagnStaceMyView();}}
to
onPress={() => {handleChangeMyView();}}
you are calling wrong function <TouchableOpacity onPress={() => {handleChagnStaceMyView();}}>
make it right as <TouchableOpacity onPress={() => {handleChangeMyView();}}>
good luck
I wonder you are not getting error like handleChagnStaceMyView is not defined
I have this custom hook I want to be executed when pressing a button inside a custom component
import { useDispatch } from 'react-redux';
import { useCallback } from 'react';
import { retireUserFromEvents } from '../../../redux/actions/Events';
export default function useDeleteAssistantToEvent() {
const dispatch = useDispatch();
const deleteAssistantToEvent = useCallback(userID => {
dispatch(retireUserFromEvents(userID));
}, [dispatch]);
return deleteAssistantToEvent;
}
I declare the hook on the screen where the custom component is:
import {
AttendantList
} from '../../../../components';
import useDeleteAssistantToEvent from '../../../../hooks/assistance/useDeleteAssistantToEvent';
export default function EventAssistantsView({ navigation }) {
const deleteAssistant = useDeleteAssistantToEvent();
return (
<SafeAreaProvider>
<SafeAreaView style={styles.safeAreaViewStyle}>
<View style={styles.mainViewStyle}>
<AttendantList
...
undoInvite={() => deleteAssistant}
/>
</View>
</SafeAreaView>
</SafeAreaProvider>
);
}
And this is my custom component (AttendantList):
export default function AttendantList({ attendees, goToUserProfile, undoInvite, imHostOrCohost }) {
return (
<View style={styles.mainViewStyle}>
{attendees.length > 0 ? (
<FlatList
...
renderItem={({ item }) => (
<TouchableOpacity onPress={goToUserProfile()}>
<InviteCard
...
onPress={() => undoInvite(item.id)}
/>
</TouchableOpacity>
)}
/>
) : (
...
)}
</View>
);
}
ÌnviteCard is the button in question.
The problem is that, anytime I want to press the button, nothing happens. So, how can I pass the hook call to the custom component?
For example, in react-native, I can pass a ref to a regular TextInput and then, through this ref, call the methods:
const inputRef = useRef(null);
const myCallback = () => {
inputRef?.current?.focus();
}
<>
<TouchableOpacity onPress={() => myCallback()}>
<Text>
Press here to focus the input!
</Text>
</TouchableOpacity>
<TextInput
ref={inputRef}
{...props} // Doesn't matter, nothing special
>
</>
So, my question is, how can I create methods on my components, so I can call them from outside of component using ref.
Of course, I'm interested in creating a method in a functional component.
You can use useImperativeHandle hook to expose the methods you need to have with the input element.
Try like this.
import React, { useImperativeHandle, forwardRef, useRef } from "react";
import { Button, StyleSheet, View, TextInput } from "react-native";
const MyTextInput = (props, ref) => {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
doFocus: () => {
inputRef.current.focus();
},
doBlur: () => {
inputRef.current.blur();
}
}));
return <TextInput ref={inputRef} style={props.style} />;
};
const MyCustomTextInput = forwardRef(MyTextInput);
const App = () => {
const myInputRef = useRef();
return (
<View style={styles.app}>
<MyCustomTextInput ref={myInputRef} style={styles.input} />
<View style={styles.button}>
<Button
onPress={() => {
myInputRef?.current?.doFocus();
}}
title="focus"
style={styles.button}
/>
</View>
<View style={styles.button}>
<Button
onPress={() => {
myInputRef?.current?.doBlur();
}}
title="blur"
style={styles.button}
/>
</View>
</View>
);
};
Code sandbox => https://codesandbox.io/s/react-native-web-forked-mnwee?file=/src/App.js
I am building a mobile app using react native. I am using the stack navigator from react-native-navigation library.
the stack has two screens, the first screen ItemsListScreen displays a list of items with add button, clicking the add button navigates to the second screen AddItemScreen which displays some inputs for the item with a save button. clicking the save button will navigate back to the list screen.
Items are saved currently on a local SQLite database, but in future will be saved on a server.
ItemsListScreen.js
import React, { useEffect, useState, useContext } from "react";
import { Button, Image, Platform, StyleSheet, View, TouchableOpacity } from "react-native";
import { ScrollView } from "react-native-gesture-handler";
import { Ionicons } from "#expo/vector-icons";
import DatabaseContext from "../db/DatabaseContext";
export default function ItemsListScreen({ navigation }) {
const db = useContext(DatabaseContext);
const [items, setItems] = useState([]);
useEffect(() => {
navigation.setOptions({
headerRight: () => {
return (
<TouchableOpacity activeOpacity={0.5} style={{ paddingRight: 10 }} onPress={() => navigation.navigate("AddItem")}>
<Ionicons name="md-add" size={30} />
</TouchableOpacity>
);
},
});
db.getItems().then((data) => {
setItems(data);
});
}, []);
return (
<View style={styles.container}>
<ScrollView style={styles.container} contentContainerStyle={styles.contentContainer}>
{items.map((item, index) => (
<View key={index} style={styles.item}>
<Text style={styles.subSection}>{item.title}</Text>
</View>
))}
</ScrollView>
</View>
);
}
AddItemScreen.js
import React, { useState, useEffect, useContext } from "react";
import { StyleSheet, View, Dimensions, TextInput, ScrollView, TouchableOpacity, Picker, Switch } from "react-native";
import DateTimePicker from "#react-native-community/datetimepicker";
import { Ionicons } from "#expo/vector-icons";
import DatabaseContext from "../db/DatabaseContext";
export default function AddItemScreen({ navigation }) {
const db = useContext(DatabaseContext);
const [item, setItem] = useState({
title: "",
description: "",
// more properties
});
const saveItem = () => {
db.saveItem(item);
navigation.navigate("ItemsList");
};
useEffect(() => {
navigation.setOptions({
headerRight: () => {
return (
<TouchableOpacity activeOpacity={0.5} style={{ paddingRight: 10 }} onPress={saveItem}>
<Ionicons name="md-save" size={30} />
</TouchableOpacity>
);
},
});
}, [item]);
return (
<View style={styles.scene}>
<ScrollView>
<Text style={styles.label}>Title</Text>
<TextInput style={styles.field} value={item.title} onChangeText={(text) => setItem((prevState) => ({ ...prevState, title: text }))} />
<Text style={styles.label}>Description</Text>
<TextInput style={styles.field} value={item.description} onChangeText={(text) => setItem((prevState) => ({ ...prevState, description: text }))} />
{/* more inputes */}
</ScrollView>
</View>
);
}
My problem is that the list on ItemsListScreen is not updated when a new item is added. I need to reload the app to get the list updated.
I tried to remove the second parameter (the empty array) from the useEffect in ItemsListScreen, it works but I think it is a bad solution as it keeps reading from the db on each re-render.
How can I refresh the list whenever the user navigates back to the list screen? I thought the useEffect will be executed whenever the screen is activated but it seems not.
You will be resolved by adding navigation focus listener to ItemsListScreen.js. Replace your useEffet in this way
useEffect(() => {
navigation.setOptions({
headerRight: () => {
return (
<TouchableOpacity activeOpacity={0.5} style={{ paddingRight: 10 }} onPress={() => navigation.navigate("AddItem")}>
<Ionicons name="md-add" size={30} />
</TouchableOpacity>
);
},
});
const unsubscribe = navigation.addListener('focus', () => {
db.getItems().then((data) => {
setItems(data);
});
});
return unsubscribe;
}, []);