React Native Unmount component before navigation goBack() - react-native

I have a screen that has content that is updated by and api call. Whenever I leave the screen, I want to unmount the component so that the next time it is rendered again, because the api is called with different data. Right now, if I enter for the first time, everything works fine (activity indicator shows that it is loading and info is display), but if I go back and then enter again to view the info for another id, it shows the previously loaded information and only after a time it reloads (without the activity indicator displaying). I don't mind the delay, but at least I would like the activity indicator to be displayed while loading.
const AllReservationsScreen = ({navigation, route}) => {
const isFocused = useIsFocused();
const [data, setData] = React.useState({
tableHead: ['Car Number', 'Reserved From', 'Expiration Time', 'Reserved By'],
reservations: "",
isLoading: true
});
useEffect(() => {
setTimeout(async() => {
ReservationService.getReservationsByParkingSpot(route.params.parkingSpotId, route.params.token).then(
(response) => {
if (response.status !== 200) {
return;
}
setData({
... data,
isLoading: false,
reservations: convertReservationsToArray(response.data)
});
}
);
}, 1000);
}, [isFocused]);
const convertReservationsToArray = (reservations) => {
let reservationsArray = [];
reservations.map(
(reservation) =>
reservationsArray.push(
[
reservation.registrationPlateNumber,
moment(reservation.startTime).format('MMMM Do YYYY HH:mm'),
moment(reservation.endTime).format('MMMM Do YYYY HH:mm'),
reservation.user
]
)
);
return reservationsArray;
};
return (
<View style={styles.modal}>
<View style={styles.titleView}>
<View style={styles.titleDetails}>
<Icon
name='ios-arrow-back'
size={30}
color='#8ea7f8'
onPress={() => {
setData({
... data,
isLoading: true,
reservations: []
});
navigation.goBack()
}}
/>
</View>
</View>
<Text style={styles.modalTitle}>All Reservations</Text>
<View style={styles.modalForm}>
{
data.isLoading ? (
<View style={{flex:1,justifyContent:'center',alignItems:'center'}}>
<ActivityIndicator size="large"/>
</View>
) : (
<View style={styles.container}>
<Table borderStyle={{borderWidth: 1, borderColor: 'transparent'}} style={{borderRadius: 10}}>
<Row data={data.tableHead} style={styles.headStyle} textStyle={styles.headText}/>
<ScrollView>
{
//console.log(data.reservations)
data.reservations.map(
(reservation, i) => {
if (i % 2 === 0) {
return <Row
key={i}
data={reservation}
style={styles.evenRow}
textStyle={styles.tableText}
/>
} else {
return <Row
key={i}
data={reservation}
style={styles.oddRow}
textStyle={styles.tableText}
/>
}
}
)
}
</ScrollView>
</Table>
</View>
)
}
</View>
</View>
);
};

You can use navigation.pop() instead of the goBack(), they are exactly the same the only difference is that pop removes the current component.
see: https://reactnavigation.org/docs/stack-actions/#pop
And because you use useEffect you should in the beginning of that function set loading to true again! useEffect works like componentDidMount and componentDidUpdate so you want to be loading everytime you're calling the api. Or just use componentDidMount.

Related

improving elements styles to make a full screen scan

I will need a helping hand to edit this page. i have all the elements but i need help styling.
I would like to have the camera (the image you see is the typical emulator camera, that's why it makes an image) in full screen and from above at the top, the message in red and the 'autocomplete.
If you want, to explain better, I would like to respect the image below: autocomplete at the top left above the camera in full screen.
would it be possible for you to help me, I'm getting a little confused. I tried to do a snack but failed. I will add it later if i can.
const autocompletes = [...Array(10).keys()];
const apiUrl = "https://5b927fd14c818e001456e967.mockapi.io/branches";
class Tickets extends Component {
constructor(props) {
super(props);
this.state = {
Press: false,
hasCameraPermission: null,
reference: '',
lastScannedUrl:null,
displayArray: []
};
}
initListData = async () => {
let list = await getProductByRef(1);
if (list) {
this.setState({
displayArray: list,
reference: list.reference
});
}
// console.log('reference dans initListData =', list.reference)
};
async UNSAFE_componentWillMount() {
this.initListData();
// console.log('reference dans le state =', this.state.reference)
};
componentDidMount() {
this.getPermissionsAsync();
}
getPermissionsAsync = async () => {
const { status } = await Permissions.askAsync(Permissions.CAMERA);
this.setState({ hasCameraPermission: status === "granted" });
};
_onPress_Scan = () => {
this.setState({
Press: true
});
}
handleBarCodeScanned = ({ type, data }) => {
this.setState({ Press: false, scanned: true, reference: data });
this.props.navigation.navigate('ProductDetails', {reference : parseInt(this.state.state.reference)})
};
renderBarcodeReader = () => {
const { hasCameraPermission, scanned } = this.state;
if (hasCameraPermission === null) {
return <Text>{i18n.t("scan.request")}</Text>;
}
if (hasCameraPermission === false) {
return <Text>{i18n.t("scan.noaccess")}</Text>;
}
return (
<View
style={{
flex: 1,
...StyleSheet.absoluteFillObject
}}
>
<BarCodeScanner
onBarCodeScanned={scanned ? undefined : this.handleBarCodeScanned}
style={{ flex:1, height:'100%', ...StyleSheet.absoluteFillObject}}
/>
{scanned && (
<Button
title={"Tap to Scan Again"}
onPress={() => this.setState({ scanned: false })}
/>
)}
</View>
);
}
handleSelectItem(item, index) {
const {onDropdownClose} = this.props;
onDropdownClose();
console.log(item);
}
render() {
const { hasCameraPermission, scanned, Press } = this.state;
let marker = null;
const {scrollToInput, onDropdownClose, onDropdownShow} = this.props;
// console.log('displayArray', this.state.displayArray, 'reference', this.state.displayArray.reference)
return (
<View style={styles.container}>
{Press ? (
<View style={{flex:1}}>
<View style={styles.dropdownContainerStyle}>
<Autocomplete
key={shortid.generate()}
containerStyle={styles.autocompleteContainer}
inputStyle={{ borderWidth: 1, borderColor: '#F78400'}}
placeholder={i18n.t("tickets.warning")}
pickerStyle={styles.autocompletePicker}
scrollStyle={styles.autocompleteScroll}
scrollToInput={ev => scrollToInput(ev)}
handleSelectItem={(item, id) => this.handleSelectItem(item, id)}
onDropdownClose={() => onDropdownClose()}
onDropdownShow={() => onDropdownShow()}
fetchDataUrl={apiUrl}
minimumCharactersCount={2}
highlightText
valueExtractor={item => item.name}
rightContent
rightTextExtractor={item => item.properties}
/>
</View>
{this.renderBarcodeReader()}
</View>
) : (
<View style={{flex:1, justifyContent:'center', alignItems:'center'}}>
<Button
color="#F78400"
title={i18n.t("scan.scan")}
onPress={this._onPress_Scan}>
</Button>
</View>
)}
</View>
);
}
}
export default Tickets;
This gives me (after pressing the button) :
SNACK CODE TEST
I notice You are using a component from Expo called BarCodeScanner
There's a github issue open about the fact that this component is not possible to be styled for full screen: https://github.com/expo/expo/issues/5212
However one user proposes a good solution: replace BarCodeScanner with Camera and use barcodescannersettings
Here's a link for the answer on the gitHub issue: https://github.com/expo/expo/issues/5212#issuecomment-653478266
Your code should look something like:
renderBarcodeReader = () => {
const { hasCameraPermission, scanned } = this.state;
[ ... ] // the rest of your code here
return (
<View
style={{
flex: 1,
...StyleSheet.absoluteFillObject
}}
>
<Camera
onBarCodeScanned={scanned ? undefined : this.handleBarCodeScanned}
style={{ flex:1}}
barCodeScannerSettings={{
barCodeTypes: [BarCodeScanner.Constants.BarCodeType.qr],
}}
/>
</View>
);
}

fetch API call in react native is not working when I load the screeen

I have made an App in react native. My app makes API calls to my webserver and then Displays information based on that. The problem Is when I first load this screen... I get the loading screen and the information is display in the way it is supposed to but when I leave the screen and then comeback to the screen, it shows nothing and my array containing the items is empty, hence I think I am having problems with the API call when I leave the screen and then come back.
I am using React navigation 5 in My App.
export default function ({ navigation }) {
const [openQueries, setOpenQueries] = useState([]);
const [isLoading, seIsLoading] = useState(true);
const open_queries = [];
function getOpenQueries() {
var retrieveData = async () => {
try {
var value = await AsyncStorage.getItem("user");
var data = JSON.parse(value);
return data.user._id;
} catch (error) {
alert(error);
}
};
retrieveData().then((user) => {
fetch(URL + "/api/contact/open_queries", {
method: "POST",
body: "user=" + user + "&status=open",
headers: { "Content-type": "application/x-www-form-urlencoded" },
})
.then((response) => {
return response.json();
})
.then((responseJson) => {
if (responseJson.error === null) {
setOpenQueries(responseJson.open_queries);
seIsLoading(false);
}
});
});
}
getOpenQueries();
openQueries.forEach((query) => {
open_queries.push(
<TouchableOpacity
onPress={() =>
navigation.navigate("Chat", {
id: query._id,
title: query.title,
query: query,
showInput: true,
})
}
>
<View style={styles.inboxItem}>
<Text style={styles.inboxTitle}>{query.title}</Text>
<Text style={styles.inboxSubtext}>
{query.chats[query.chats.length - 1].chat}
</Text>
<View style={styles.lineBreak}></View>
</View>
</TouchableOpacity>
);
});
return (
<SafeAreaView style={styles.container}>
<Text style={styles.title}>Contacts</Text>
<TouchableOpacity
style={styles.button}
onPress={() => navigation.navigate("NewQuery")}
>
<Text style={styles.text}>Start a new query</Text>
</TouchableOpacity>
<TouchableOpacity onPress={() => navigation.navigate("ClosedQueries")}>
<View style={styles.button}>
<Text style={styles.text}>Closed Queries</Text>
</View>
</TouchableOpacity>
<Text style={styles.subTitle}>Open Queries</Text>
{isLoading ? (
<View style={styles.loader}>
<Code />
</View>
) : (
<ScrollView style={{ paddingTop: 10 }}>
{openQueries.length > 0 ? (
open_queries
) : (
<Text style={styles.noQuery}>No Open Queries found</Text>
)}
</ScrollView>
)}
<ScrollView></ScrollView>
<BottomNavigation navigation={navigation} active={"contact"} />
</SafeAreaView>
);
}
Try this way
export default function ({ navigation }) {
const [openQueries, setOpenQueries] = useState([]);
const [isLoading, seIsLoading] = useState(true);
const [open_queries_views, setOpenQueriesViews] = useState([]);
function getOpenQueries() {
....
}
// Similar to componentDidMount
useEffect(() => {
getOpenQueries();
});
function renderViews(){
const open_queries = [];
openQueries.forEach((query) => {
open_queries.push(
<TouchableOpacity> ... </TouchableOpacity>
);
});
setOpenQueriesViews(open_queries); // set state here to auto reflect on view
}
return (
<SafeAreaView style={styles.container}>
....
<ScrollView style={{ paddingTop: 10 }}>
{open_queries_views.length > 0 ? (
open_queries_views
) : (
<Text style={styles.noQuery}>No Open Queries found</Text>
)}
</ScrollView>
.....
</SafeAreaView>
);
}

Component not re-rendering on useState array change

I have a favorite button on the 'tweet' card that I show on the FeedScreen.js.
~~~~~~~~~ IMPORTS SNIP ~~~~~~~~~
function FeedScreen(props) {
const [feed, setFeed] = useState([]);
const [favorites, setFavorite] = useState([]);
const [refreshing, setRefreshing] = useState(false);
useEffect(() => {
loadFeed(0, 4);
}, []);
const loadFeed = async (last_id = 0, limit = 1) => {
setRefreshing(true);
const response = await tweetsApi.getTweets(last_id, limit);
if (response.ok) {
setFeed(response.data["data"].concat(feed));
} else {
console.log(response.problem);
}
setRefreshing(false);
};
const handleBookmark = async (item_id) => {
const response = await tweetsApi.toggleBookmark(item_id);
if (response.ok) {
console.log("ok response");
setFavorite(favorites.concat(item_id));
// I've tried this as well
// setFavorite([...favorites].concat(item_id));
// but in vain
console.log(favorites);
}
};
return (
<Screen style={styles.screen}>
<FlatList
data={feed}
keyExtractor={(tweet) => {
return tweet.id.toString();
}}
renderItem={({ item }) => (
~~~~~~~~~ SNIP ~~~~~~~~~
<CardFooter
style={{ marginLeft: 20 }}
item={item}
onPress={handleBookmark}
/>
</View>
)}
ItemSeparatorComponent={ListItemSeparator}
refreshing={refreshing}
onRefresh={() => {
loadFeed(feed[0]["id"], 2);
}}
/>
</Screen>
);
}
~~~~~~~~~ SNIP ~~~~~~~~~
And here's the CardFooter.js :
~~~~~~~~~ SNIP ~~~~~~~~~
function CardFooter({ item, onPress }) {
return (
<View style={styles.bookmark}>
<TouchableOpacity
onPress={() => {
return onPress(item.id);
}}
>
{item.bookmarked && (
<FontAwesome name="bookmark" size={24} color="red" />
)}
{!item.bookmarked && (
<FontAwesome5 name="bookmark" size={24} color="black" />
)}
</TouchableOpacity>
</View>
</View>
);
}
export default CardFooter;
~~~~~~~~~ SNIP ~~~~~~~~~
However the component doesn't seem to re render.
I've looked at these :
react-component-not-re-rendering-after-using-usestate-hook
Similar here
Another one 17 days back - why-usestate-is-not-re-rendering
usestate-not-re-rendering-when-updating-nested-object
All of these and similar other ones, each one of them point to the fact that the a new array should be created so that react re-renders it.
Update
console.log output
yes the console.log is printing the array, although one value previous. That's because useState is async so it isn't printing the realtime array. So, when the second time this is called, it would show one item_id ( the previous one ) added to favorites
I finally solved this by managing the state in the component itself.
Not sure if this is 'the proper way' to do this, but read here (how-to-add-a-simple-toggle-function-in-react-native) that this is how you can do this.
So, now the bookmark component gets its response from the top level component ( FeedScreen.js ) :
const handleBookmark = async (item_id) => {
const response = await tweetsApi.toggleBookmark(item_id);
if (response.ok) {
return true;
} else {
return false;
}
};
And changing the CardFooter.js i.e. where the bookmark component resides.
function CardFooter({ item, onPress }) {
const [favorite, setFavorite] = useState(item.bookmarked);
return (
<View style={styles.bookmark}>
<TouchableOpacity
onPress={async () => {
let response = await onPress(item.id);
if (response) {
setFavorite(!favorite);
} else {
alert("Some error occurred");
}
}}
>
{favorite && <FontAwesome name="bookmark" size={24} color="red" />}
{!favorite && (
<FontAwesome5 name="bookmark" size={24} color="black" />
)}
</TouchableOpacity>
</View>
</View>
);
}
Concerns
I am a bit concerned about handling the response in this component.
Should I handle the async operation in the bottom component ?

React-Native-Component not rendering when state is changed

I am making the show more and show less functionality inside a flat list but the state pressed is not working as expected .When I am setting the state value component is not being rendered when the state changes its value.
My constructor is set like below
this.state = {
accList: [],
expanded: false,
expandedText: "Show More"
}
In componentdidmount() I am updating the value of accList value like below
componentDidMount = () => {
this.setState({
accList:[{
"customer_name": "Shubhangi J Thakur",
"message":"Hello"
},
{
"customer_name": "Arthur S Campbell",
"message":"Hello_World"
},
{
"customer_name": "Susan R Brill",
"message":"hellow"
}]
});
}
I have defined the flatlist in render() like below
<FlatList
onScroll={this.handleScroll}
data={this.state.accList}
renderItem={this.renderItem}
keyExtractor={this._keyExtractor}
/>
renderItem = ({ item, index }) => (
<Card style={style.cardLayout} key={index}>
<CardItem header>
<Text>{item.customer_name}</Text>
</CardItem>
{this.seemorefunctionality(item)}
</Card>
);
seemorefunctionality = (item) => {
return <View>
{this.state.expanded ? this.expandedView(item) :null}
//To view Show More and Show Less Text
<TouchableOpacity onPress={this.expandedText}>
<Text> {this.state.expandedText}</Text>
</TouchableOpacity>
</View>
}
}
expandedText = () => {
console.log('Setting the expanded text value', this.state.expanded)
if (this.state.expanded) {
this.setState({
expandedText: "Show More"
});
}
else {
this.setState({
expandedText: "Show Less"
});
}
value=!this.state.expanded
this.setState({
expanded: value
});
}
expandedView = (item) => {
return <View>
{item.map((obj, index) => {
return (
<View key={index} >
<Text>{obj.message}</Text>
</View>
)
})}
</View>
When I am clicking on the this.state.expandedText value is getting changed when we see in the console but its not reflecting in the View also expandedView is not being rendered when this.state.expanded is set to true.
In View the value of this.state.expandedText is always showing Show More while I can see In console that the value is getting changed to Show more and Show Less on click
for re-rendering flatlist you have to add extraData={this.state} as mention on https://facebook.github.io/react-native/docs/flatlist

How to pass message from children Input to parent Chat in react-native-gifted-chat

I got a chat based on react-native-gifted-chat, with a children InputBox component that has the layout for the input and some buttons plus the Send button.
I'm passing 2 functions to handle onSend and the camera, but I was wondering how to send the text that I'm writing on the InputBox to parent that contains the GiftedChat.
GiftedChat handles an array of messages, but how do I create a new text message based on the input and the button onPress ?
Here's my current code:
On Parent
constructor(props) {
super(props)
this.handleCameraPress = this.handleCameraPress.bind(this);
this.onSend = this.onSend.bind(this);
this.state = {
chatData: {},
messages: []
}
}
onSend(messages = []) {
alert('sending message');
this.setState(previousState => ({
messages: GiftedChat.append(previousState.messages, messages),
}))
}
handleCameraPress() {
alert('camera tapped');
}
renderInputToolbar(props) {
return ( <InputBox
{...props}
messages={ this.messages }
onSend={ this.onSend }
handleCameraPress={ this.handleCameraPress }
containerStyle={ styles.inputToolbarStyle }
/>);
}
This is how the GiftedChat looks like:
<GiftedChat
style={{ zIndex: 1 }}
messages={this.state.messages}
bottomOffset={Platform.OS === "ios" ? 335 : 0}
maxComposerHeight={150}
isAnimated={true}
user={{ _id: 1 }}
renderInputToolbar={ this.renderInputToolbar.bind(this) }
/>
On Children
render() {
return (
<View style={ styles.container }>
<TouchableOpacity
activeOpacity={0.6}
style={styles.cameraButton}
onPress={ this.props.handleCameraPress }>
<Icon name='md-camera' style={ styles.cameraIcon } />
</TouchableOpacity>
<TextInput
style={ styles.textInput }
placeholder={i18n.t('chatInputPlaceholder')}
returnKeyType={'send'}
// onChangeText={ message => this.setState({ message })}
// value={this.props.message}
blurOnSubmit={false}
ref={'chatInputRef'}
/>
<Button
onPress={ this.props.onSend }
style={ styles.sendButton}>
<Icon name='md-send' style={ styles.sendIcon } />
</Button>
</View>
);
}
I have to I guess pass a this.props.message to this.props.onSend? And then merge it to parent's messages?
You have to create a state variable , which will be your current Message , and then in Gifted chat , you implement :
onInputTextChanged={text => {this.setState({typingMessage: text});}}
So now your "this.state.typingMessage" , will always have the value that you are writing in your InputToolbar
If you have a custom render you can access to Input Toolbar value like this with "props.text" :
renderSend(props) {
return (
<TouchableOpacity onPress={() => props.onSend({
_id: 10,
text: props.text,
createdAt: new Date(),
user: {
_id: 1,
name: 'Mike',
},
})}>
</TouchableOpacity>
);
}