Navigating to a specific screen introduces a breaking bug - javascript

I'm building an app in React Native where a user can plan out novels by creating and storing characters, locations, and chapters, with details about them. Currently, there is an issue where navigating to the screen where one can edit a character's details (CharEdit.js below), from a screen that displays those details in a better format (CharInfo.js), introduces the following warning:
Warning: Cannot update a component from inside the function body of a different component
CharEdit.js
import React from 'react';
import { ScrollView, Text, TextInput, View, TouchableOpacity, StyleSheet } from 'react-native';
import { Picker } from '#react-native-picker/picker';
import { useSelector, useDispatch } from 'react-redux';
import { updateCharacter } from '../redux/actions';
import { names, descriptions, avatars } from '../assets/images';
export default function charEditScreen({ navigation, route }) {
const { id } = route.params;
let character = useSelector(state => state.metaReducer.characters.filter((item) => {if (item.id === id) return item;}))[0];
let nameValue;
let ageValue;
let professionValue;
let appearanceValue;
let motiValue;
let summaryValue;
let role;
let pictureValue = names.filter((key) => {
return avatars[key] === character.picture;
})[0];
if (pictureValue === undefined) {
pictureValue = 'icon';
}
try {
role = character.role;
} catch {
role = '';
}
const dispatch = useDispatch();
const charUpdate = () => {
let changedCharacter = {
name: (nameValue) ? nameValue : character.name,
age: (ageValue) ? ageValue : character.age,
profession: (professionValue) ? professionValue : character.profession,
appearance: (appearanceValue) ? appearanceValue : character.appearance,
picture: avatars[pictureValue],
motivation: (motiValue) ? motiValue : character.motivation,
summary: (summaryValue) ? summaryValue : character.summary,
role: role,
id: id,
};
let action = updateCharacter(changedCharacter);
dispatch(action);
navigation.navigate('CharacterList');
}
return(
<ScrollView contentContainerStyle={{alignItems: 'center', justifyContent: 'center', flexGrow: 1, backgroundColor: '#F0F2F2'}}>
<Text style={styles.header}>{character.name}</Text>
<View style={styles.fieldContainer}>
<Text style={styles.fieldLabel}>Full Name</Text>
<TextInput value={nameValue} onChangeText={(change) => {nameValue = change}} style={styles.textInput} autoCapitalize='words' defaultValue={character.name} />
</View>
<View style={styles.fieldContainer}>
<Text style={styles.fieldLabel}>Age</Text>
<TextInput value={ageValue} onChangeText={(change) => {ageValue = change;}} style={styles.textInput} defaultValue={character.age} />
</View>
<View style={styles.fieldContainer}>
<Text style={styles.fieldLabel}>Profession/Occupation</Text>
<TextInput value={professionValue} onChangeText={(change) => {professionValue = change;}} style={styles.textInput} defaultValue={character.profession} />
</View>
<View style={styles.fieldContainer}>
<Text style={styles.fieldLabel}>Physical Appearance</Text>
<TextInput value={appearanceValue} onChangeText={(change) => {appearanceValue = change;}} style={styles.textInputMultiline} multiline={true} defaultValue={character.appearance} />
</View>
<View style={styles.fieldContainer}>
<Text style={styles.fieldLabel}>Avatar</Text>
<View style={styles.pickerContainer}>
<Picker selectedValue={pictureValue} onValueChange={(itemValue, itemIndex) => {pictureValue=itemValue;}} style={styles.picker} >
{names.map((value, index) => {
return <Picker.Item style={styles.pickerItem} label={descriptions[index]} value={value} key={index} />
})}
</Picker>
</View>
</View>
<View style={styles.fieldContainer}>
<Text style={styles.fieldLabel}>Role</Text>
<View style={styles.pickerContainer}>
<Picker selectedValue={role} onValueChange={(itemValue, itemIndex) => {role=itemValue;}} style={styles.picker} >
<Picker.Item style={styles.pickerItem} label="Protagonist" value="Protagonist" />
<Picker.Item style={styles.pickerItem} label="Deuteragonist" value="Deuteragonist" />
<Picker.Item style={styles.pickerItem} label="Antagonist" value="Antagonist" />
<Picker.Item style={styles.pickerItem} label="Major Character" value="Major Character" />
<Picker.Item style={styles.pickerItem} label="Minor Character" value="Minor Character" />
</Picker>
</View>
</View>
<View style={styles.fieldContainer}>
<Text style={styles.fieldLabel}>Motivation/Conflict</Text>
<TextInput value={motiValue} onChangeText={(change) => {motiValue = change;}} style={styles.textInputMultiline} multiline={true} defaultValue={character.motivation} />
</View>
<View style={styles.fieldContainer}>
<Text style={styles.fieldLabel}>Summary of Character</Text>
<TextInput value={summaryValue} onChangeText={(change) => {summaryValue = change;}} style={styles.textInputMultiline} multiline={true} defaultValue={character.summary} />
</View>
<TouchableOpacity style={styles.submit} onPress={charUpdate} >
<Text style={styles.submitText}>Update</Text>
</TouchableOpacity>
</ScrollView>
)
}
const styles = StyleSheet.create({
textInput: {
height: 40,
width: 300,
backgroundColor: '#65AFBF',
borderRadius: 10,
color: 'white',
padding: 10,
fontSize: 18,
fontFamily: 'Baloo-Paaji-2',
},
textInputMultiline: {
width: 300,
height: 160,
backgroundColor: '#65AFBF',
borderRadius: 10,
color: 'white',
padding: 10,
fontSize: 18,
fontFamily: 'Baloo-Paaji-2',
},
picker: {
height: 40,
width: 300,
color: 'white',
},
pickerItem: {
fontFamily: 'Baloo-Paaji-2',
fontSize: 18,
},
pickerContainer: {
backgroundColor: '#65AFBF',
borderRadius: 10,
},
fieldContainer: {
marginTop: 40,
},
header: {
fontSize: 48,
fontFamily: 'Abril-Fatface',
textAlign: 'center',
marginTop: 100,
marginBottom: 10,
color: '#143C44',
},
fieldLabel: {
fontFamily: 'Baloo-Paaji-2',
fontSize: 18,
color: '#143C44',
marginBottom: 5,
},
submit: {
marginVertical: 50,
backgroundColor: '#65AFBF',
alignItems: 'center',
justifyContent: 'center',
height: 50,
width: 100,
borderRadius: 20,
},
submitText: {
fontFamily: 'Baloo-Paaji-2',
fontSize: 18,
color: 'white',
}
})
CharInfo.js
import React from 'react';
import { ScrollView, Text, View, TouchableOpacity, StyleSheet, Image, Dimensions} from 'react-native';
import { useSelector } from 'react-redux';
import Ionicons from 'react-native-vector-icons/Ionicons';
import Constants from 'expo-constants';
import { LinearGradient } from 'expo-linear-gradient';
const width = Dimensions.get('window').width;
const height = Dimensions.get('window').height;
export default function charInfoScreen({ navigation, route }) {
const { id } = route.params;
let character = useSelector(state => state.metaReducer.characters.filter((item) => {if (item.id === id) return item;}))[0];
return (
<ScrollView style={styles.container} >
<View>
{character.picture === 'icon' ? <Ionicons name='ios-person' size={108} color='white' style={{alignSelf: 'center', minHeight: height /2 - 100, paddingTop: Constants.statusBarHeight + 75}} /> : <Image source={character.picture} resizeMode='cover' style={{width: width, height: height / 2 - 100}} />}
</View>
<LinearGradient colors={['#332e30', '#141213']} style={styles.gradient}>
<Text style={styles.fieldName}>Full Name:</Text>
<Text style={styles.majorText}>{character.name}</Text>
<Text style={styles.fieldName}>Age:</Text>
<Text style={styles.majorText}>{character.age}</Text>
<Text style={styles.fieldName}>Profession:</Text>
<Text style={styles.majorText}>{character.profession}</Text>
<Text style={styles.minorText}>{character.name.split(' ')[0]} is a {character.age} year old {character.profession.toLowerCase()}, described as {character.appearance.toLowerCase()}. You have assigned {character.name.split(' ')[0]} a character role of {character.role.toLowerCase()}. {character.name.split(' ')[0]}'s motivation is {character.motivation.toLowerCase()}. In summary, {character.name.split(' ')[0]} {character.summary.toLowerCase()}.</Text>
<TouchableOpacity style={styles.editButton} onPress={navigation.navigate('CharEdit', {id: id})} >
<Ionicons name='chevron-forward-circle' color='white' size={48} />
</TouchableOpacity>
</LinearGradient>
</ScrollView>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F0F2F2',
marginHorizontal: 0,
minHeight: height,
},
gradient: {
minHeight: height / 2 + 100,
},
fieldName: {
color: 'white',
marginTop: 25,
marginLeft: 30,
fontFamily: 'Baloo-Paaji-2',
fontSize: 18,
},
majorText: {
color: 'white',
marginLeft: 30,
fontFamily: 'Abril-Fatface',
fontSize: 24,
},
minorText: {
color: 'gray',
marginHorizontal: 30,
marginTop: 25,
fontFamily: 'Baloo-Paaji-2',
fontSize: 16,
},
editButton: {
backgroundColor: 'transparent',
borderRadius: 100,
height: 60,
width: 60,
marginVertical: 30,
marginLeft: width - 90,
alignItems: 'center',
justifyContent: 'center',
}
})
Currently, trying to navigate CharInfo will automatically navigate to CharEdit and will bring up the aforementioned warning. However, if I navigate to CharEdit from any of my other screens, it works perfectly.
From the research I've done, both this question on StackOverflow and this explanation on the React blog suggest that the issue has to do with calling dispatch() in a React Native Hook. However, I'm not using any hooks.
The suggested solution is to wrap where I'm calling dispatch() in the useEffect() Hook. Doing so did not fix the error.
I had the same issue when trying to implement editing and deleting a character on the same screen, which I fixed by moving the deletion logic to a different screen. I can find a workaround, but I'd like to keep this structure if I can.
Here is the error message in full:
Warning: Cannot update a component from inside the function body of a different component.
at node_modules/#react-navigation/native/node_modules/#react-navigation/core/src/useSyncState.tsx:39:22 in React.useCallback$argument_0
at node_modules/#react-navigation/native/node_modules/#react-navigation/core/src/useNavigationBuilder.tsx:309:21 in React.useCallback$argument_0
at node_modules/#react-navigation/native/node_modules/#react-navigation/core/src/SceneView.tsx:67:14 in React.useCallback$argument_0
at node_modules/#react-navigation/native/node_modules/#react-navigation/core/src/useNavigationBuilder.tsx:309:21 in React.useCallback$argument_0
at node_modules/#react-navigation/native/node_modules/#react-navigation/core/src/useOnAction.tsx:105:20 in React.useCallback$argument_0
at node_modules/#react-navigation/native/node_modules/#react-navigation/core/src/useNavigationHelpers.tsx:43:30 in dispatch
at node_modules/#react-navigation/native/node_modules/#react-navigation/core/src/useNavigationCache.tsx:91:10 in dispatch
at node_modules/#react-navigation/native/node_modules/#react-navigation/core/src/useNavigationCache.tsx:122:22 in withStack$argument_0
at node_modules/#react-navigation/native/node_modules/#react-navigation/core/src/useNavigationCache.tsx:109:18 in withStack
at node_modules/#react-navigation/native/node_modules/#react-navigation/core/src/useNavigationCache.tsx:120:21 in acc.name
Can anyone explain why this error is occurring please?

Related

Async Storage movie watchlist in React Native

its my first time i create an APP for my study project , i created app of database of movies using TMDB API , and it remain one last step to finish my project , and it's creating WatchList or Plan to Watch , and i have no idea how to make it . please can someone who have idea of how to create it with Async Storage or anything to save watchlist, and where to add it?
I will put my code
this code of Movie screen, and i need a touchableopacity that make save the movie into favorite component
import React, {useEffect, useState} from 'react';
import {
View,
Text,
Image,
Dimensions,
StyleSheet,
Linking,
ImageBackground,
TouchableOpacity,
ScrollView,
FlatList
} from 'react-native';
import {IMAGE_POSTER_URL} from '../config';
import {GET} from '../../Services/API';
import Loader from '../Components/Loader';
import Constants from '../Components/Constants';
import TrendingMovies from '../Components/TrendingMovies';
import TrendingPeople from '../Components/TrendingPeople';
import { createStackNavigator } from "#react-navigation/stack";
import PeopleDetails from '../Components/PeopleDetails.js';
import { LinearGradient } from 'expo-linear-gradient';
import {POSTER_IMAGE} from '../config';
import AsyncStorage from '#react-native-async-storage/async-storage';
const deviceHeight = Dimensions.get('window').height;
const deviceWidth = Dimensions.get('window').width;
const MovieDetails = props => {
const [loading, setLoading] = useState(true);
const [details, setDetails] = useState();
const [favourites, setFavourites] = useState([]);
useEffect(() => {
const getDetails = async () => {
const data = await GET(`/movie/${props.route.params.movieId}`);
setDetails(data);
setLoading(false);
};
getDetails();
}, []);
useEffect(() => {
const getVideo = async () => {
const results = await GET(`/movie/${props.route.params.movieId/videos}`);
setDetails(results);
setLoading(false);
};
getVideo();
}, []);
const getGenre = () => {
return details.genres.map(genre => (
<View >
<Text style={styles.genre}>{genre.name}</Text>
</View>
));
};
return (
<View style={styles.sectionBg}>
{loading ? (
<Loader />
) : (
<View style={{ flex: 1 }} >
<Image
source={{uri: `${IMAGE_POSTER_URL}${details.backdrop_path}`}}
style={styles.imageBg}
/>
<View style={{flexDirection:'row', justifyContent:'space-between', alignItems:'center', marginRight:15, marginTop:10}}>
<TouchableOpacity onPress={() => props.navigation.goBack()}
style={{ marginLeft:20, marginTop: 20, marginBottom:20}}
>
<Image
source={require("../../assets/icons/back.png")}
style={{ width:93/1.4 , height: 50/1.4 }} />
</TouchableOpacity>
<TouchableOpacity
style={{ marginLeft:20, marginTop: 20, marginBottom:20}}
>
<Image
source={require("../../assets/icons/nolicked.png")}
style={{ width:256/5.7 , height: 252/5.7 }} />
</TouchableOpacity>
</View>
<ScrollView style={{ flex:1 ,}} >
<TouchableOpacity onPress={() => {
Linking.openURL('https://www.youtube.com/watch?v=${details.key}');
}}
style={{ marginTop:240, marginLeft: "70%",zIndex:1 }}
>
<Image
source={require("../../assets/icons/youtube.png")}
style={{ width: 75, height: 75}} />
</TouchableOpacity>
<ImageBackground
source={require("../../assets/icons/hmm.png")}
style={{ width:'100%' , height: '84%',zIndex:-1 ,marginTop: -60,marginBottom:20}} >
<View style={{ alignSelf: 'center' , marginTop:40 }} >
<View style={{flexDirection: 'row' }} >
<Image
source={{uri: `${IMAGE_POSTER_URL}${details.poster_path}`}}
style={{ width: 150/1.2 , height: 220/1.2 , borderRadius: 20, marginTop: 40 , marginLeft: 20, zIndex:1 }} />
<View style= {{flexDirection:'column' , }} >
<Text style={styles.detailsMovieTitle}>{details.title}</Text>
<View style={{flexDirection: 'row' , alignItems: 'center', marginLeft: 15 , backgroundColor:'orange' , width:70, marginVertical: 5, borderRadius:10}} >
<Image
source={require("../../assets/icons/star.png")}
style={{ width: 20, height: 20 , marginLeft: 5, marginVertical:8}} />
<Text style= {{color:'#20222A' , fontSize: 18, marginLeft: 6, fontWeight:'bold' ,marginRight: 15}} >{details.vote_average}</Text>
</View>
<View style={{ flexDirection: 'row', width: 80 ,marginTop:25, marginLeft:20}}>
{getGenre()}
</View>
<Text style={{marginLeft: 14,
marginHorizontal:-5,
marginVertical:5,
fontWeight:'bold',
color: '#C3C3C3',
fontSize: 10,}}>{details.runtime} Minutes</Text>
<Text style={{marginLeft: 14,
marginHorizontal:-5,
color: '#C3C3C3',
fontWeight:'bold',
fontSize: 10,}}> Release Date: {details.release_date} </Text>
</View>
</View>
<View style={{marginLeft:15, zIndex:1 , marginBottom:50, marginLeft:20}} >
<Text style={{color:'white', fontSize:16, fontWeight:'bold' , marginBottom: 1,marginLeft: -5}} > Overview </Text>
<Text style={{color: 'white',
fontSize: 12, width:330, marginBottom:15}}>{details.overview}</Text>
<TrendingPeople
title="Actors"
navigation={props.navigation}
url={`/movie/${props.route.params.movieId}/credits`}
isForPage="details"
/>
<View style={{marginLeft: -15, marginTop:10, }} >
<TrendingMovies
title="SIMILAR MOVIES"
navigation={props.navigation}
url={`/movie/${props.route.params.movieId}/similar`}
/>
</View>
</View>
</View>
</ImageBackground>
</ScrollView>
</View>
)}
</View>
);
};
const styles = StyleSheet.create({
sectionBg: {
backgroundColor: Constants.baseColor,
height: deviceHeight,
flex:1
},
trendingPeopleImage: {
height: 70,
width: 70,
borderRadius: 500,
},
trendingPeopleName: {
width: 60,
color: Constants.textColor,
fontSize: 12,
textAlign: 'center',
marginTop: 10,
},
trendingPeopleContainer: {
margin: 10,
},
heading: {
fontSize: 12,
color: 'white',
margin: 10,
fontWeight:'bold'
},
posterImage: {
height: 800,
width: 150,
borderRadius: 10,
},
movieTitle: {
color: Constants.textColor,
width: 150,
textAlign: 'center',
marginTop: 5,
fontSize: 20,
fontWeight:'bold'
},
imageBg: {
position: 'absolute', top:0, left:0 ,
width: deviceWidth,
height: 400,
opacity: 0.8
},
detailsMovieTitle: {
fontSize: 20,
width: 180,
fontWeight:'bold',
color: 'white',
marginLeft:15,
marginTop: 35
},
linkContainer: {
backgroundColor: Constants.secondaryColor,
borderRadius: 100,
padding: 10,
width: 45,
marginLeft: 20,
marginTop: -20,
},
overview: {
color: 'white',
marginHorizontal: 10,
textAlign: 'justify',
fontSize: 16,
},
details: {
color: 'white',
fontSize: 11,
marginLeft: 10,
fontWeight: 'bold',
},
detailsContainer: {
display: 'flex',
marginLeft:15
},
genreContainer: {
borderColor: Constants.textColor,
paddingHorizontal: 0,
},
genre: {
width: 50,
marginHorizontal:-5,
fontWeight:'bold',fontWeight:'bold',
color: '#C3C3C3',
marginRight:4,
fontSize: 9,
},
image : {
height: 100,
width: 180,
borderRadius: 15,
paddingHorizontal: 5,
marginTop: 10,
marginLeft: 20
},
Text: {
marginLeft: 20,
color: 'white',
fontWeight: 'bold',
fontSize: 20
},
title: {
marginTop: 10,
width: 150,
textAlign: 'center',
color: 'white',
fontWeight: 'bold',
fontSize: 12
}
});
export default MovieDetails;```
First you create a favorite state
const [ favorites, setFavorites ] = useState([]);
Then you use useEffect to load the favorite from async storage.
const getData = async () => {
try {
const value = await AsyncStorage.getItem('#storage_key')
if(value !== null) {
setFavorites(JSON.parse('#storage_key'));
}
} catch(e) {
// error reading value
}
}
useEffect(() => {
getData();
}, [])
Then you create a touchable component to add to favorites.
<TouchableOpacity onPress={() => addToFavorites('some_id')}>
Add to Favorites
</TouchableOpacity>
Create the add to favorite function
const addToFavorites = async (id) => {
await AsyncStorage.setItem('#storage_key', JSON.stringify([...favorites, id]))
setFavorites([...favorites, id]);
}
More on Asyncstorage usage here - https://react-native-async-storage.github.io/async-storage/docs/usage

React Native - State doesn't update even with 'onChange' function set

So I have this code here. I don't know if it's cause of how I structured it, but the state changes using the 'set' function doesn't change the state till after I change the entry again. So for example, I'll put in text then I push "post" which should update the state, when console.warn prints it doesn't print anything. Then I change the text again and push post and console.warn will output what was there before my most recent change.
import React, {useState, createRef} from 'react';
import LinearGradient from 'react-native-linear-gradient';
import {
StyleSheet,
TextInput,
View,
Text,
ScrollView,
Keyboard,
Button,
TouchableOpacity,
KeyboardAvoidingView,
} from 'react-native';
import DateTimePickerModal from 'react-native-modal-datetime-picker';
import Loader from '../components/Loader';
const GoalRegistrationScreen = ({navigation}) => {
const [isDatePickerVisible, setDatePickerVisibility] = useState(false);
const [goalText, setGoal] = useState('');
const [durationText, setDuration] = useState('');
const [descriptionText, setDescription] = useState('');
const [loading, setLoading] = useState(false);
const [errortext, setErrortext] = useState('');
const durationInputRef = createRef();
const descriptionInputRef = createRef();
function showDatePicker() {
setDatePickerVisibility(true);
}
function hideDatePicker() {
setDatePickerVisibility(false);
}
function handleConfirm(date) {
hideDatePicker();
console.warn(date);
}
function onButtonClick() {
// console.warn(goalText)
console.warn(descriptionText);
}
React.useLayoutEffect(() => {
navigation.setOptions({
title: 'Create Goal',
headerTitleAlign: 'center',
headerRight: () => (
<TouchableOpacity
style={styles.addButtonStyle}
activeOpacity={0.5}
onPress={onButtonClick}>
<LinearGradient
colors={['#FBE049', '#4964FB']}
style={styles.linearGradient}
start={{x: 0, y: 0}}
end={{x: 1, y: 1}}>
<Text style={styles.buttonTextStyle}>Post</Text>
</LinearGradient>
</TouchableOpacity>
),
});
}, [navigation]);
return (
<View style={styles.mainBody}>
<Loader loading={loading} />
<ScrollView keyboardShouldPersistTaps="handled">
<View>
<KeyboardAvoidingView enabled>
<View style={styles.SectionStyle}>
<TextInput
style={styles.inputStyle}
onChangeText={goal => setGoal(goal)}
placeholder="What do I want to accomplish?"
placeholderTextColor="#8b9cb5"
autoCapitalize="none"
returnKeyType="next"
onSubmitEditing={() =>
durationInputRef.current && durationInputRef.current.focus()
}
underlineColorAndroid="#f000"
blurOnSubmit={false}
/>
</View>
<View style={styles.SectionStyle}>
<Button title="Show Date Picker" onPress={showDatePicker} />
<DateTimePickerModal
isVisible={isDatePickerVisible}
mode="date"
onConfirm={handleConfirm}
onCancel={hideDatePicker}
minimumDate={new Date()}
/>
{/* <TextInput
style={styles.inputStyle}
onChangeText={duration => setDuration(duration)}
placeholder="Duration of Goal"
placeholderTextColor="#8b9cb5"
keyboardType="default"
ref={durationInputRef}
onSubmitEditing={() =>
descriptionInputRef.current &&
descriptionInputRef.current.focus()
}
blurOnSubmit={false}
underlineColorAndroid="#f000"
returnKeyType="next"
/> */}
</View>
<View style={styles.SectionStyle}>
<TextInput
style={styles.accomplishmentTextStyle}
onChangeText={description => setDescription(description)}
placeholder="How can I accomplish this goal?"
placeholderTextColor="#8b9cb5"
keyboardType="default"
ref={descriptionInputRef}
onSubmitEditing={Keyboard.dismiss}
blurOnSubmit={false}
underlineColorAndroid="#f000"
returnKeyType="next"
/>
</View>
{errortext != '' ? (
<Text style={styles.errorTextStyle}>{errortext}</Text>
) : null}
</KeyboardAvoidingView>
</View>
</ScrollView>
</View>
);
};
export default GoalRegistrationScreen;
const styles = StyleSheet.create({
mainBody: {
flex: 1,
justifyContent: 'center',
backgroundColor: '#FFFFFF',
alignContent: 'center',
},
SectionStyle: {
flexDirection: 'row',
marginLeft: 35,
marginRight: 35,
margin: 10,
},
buttonTextStyle: {
color: '#000000',
paddingVertical: 10,
fontSize: 16,
paddingLeft: 15,
paddingRight: 15,
borderRadius: 32,
borderColor: '#000000',
},
inputStyle: {
flex: 1,
color: 'black',
paddingLeft: 15,
paddingRight: 15,
borderWidth: 1,
borderRadius: 30,
borderColor: '#000000',
},
accomplishmentTextStyle: {
flex: 1,
color: 'black',
paddingLeft: 15,
paddingRight: 15,
borderWidth: 1,
borderRadius: 30,
borderColor: '#000000',
height: 150,
textAlignVertical: 'top',
},
addButtonStyle: {
paddingRight: 20,
},
linearGradient: {
borderRadius: 32,
},
errorTextStyle: {
color: 'red',
textAlign: 'center',
fontSize: 14,
},
});
Here's an example I had 'testdsdd' in the description and updated to 'test', the onChangeText function should automatically have changed it to 'test', but it didn't. This happens for all states from goalText, descriptionText to setting date.
Try to change the TextInput onChangeText prop to just onChange like this:
onChangeText={ handleInputChange }
and then handle the new input in the handleInputChange:
const handleInputChange = useCallback((ev) => {
const input = ev.nativeEvent.text;
// you can also do error checking here.
setDescription(input);
}, [formatMessage]);
I always suggest using a pattern like this because it allows you to check for errors or call other methods (like auto-search).
In fact, I suggest using the handleInputChange to check for errors and only set the new state for the text-input on an onEndEditing.

Undefined is not an object evaluating title.length React Native

When I submit a form in React Native I get the below error undefined is not an object (evaluating 'title.length'). This problem occurs when I submit a form when the Card.js should be rendering the data from the form. I have checked and its getting the data back fine, seems to be a problem with rendering the data that its reading as undefined. After this error the form actually submits successfully and the Card.js displays the data.
Card.js
import React from "react";
import {
StyleSheet,
View,
Text,
ImageBackground,
TouchableOpacity,
} from "react-native";
const Card = (props) => {
const {
navigation,
title,
address,
homeType,
description,
image,
yearBuilt,
price,
id,
} = props;
return (
<TouchableOpacity
onPress={() => props.navigation.navigate("HomeDetail", { houseId: id })}
>
<View style={styles.card}>
<View style={styles.titleContainer}>
<Text style={styles.title}>
{title.length > 30 ? title.slice(0, 30) + "..." : title}
</Text>
</View>
<View style={styles.imageContainer}>
<ImageBackground source={{ uri: image }} style={styles.image}>
<Text style={styles.price}>${price}</Text>
<View style={styles.year}>
<Text style={styles.yearText}>{yearBuilt}</Text>
</View>
</ImageBackground>
</View>
<View style={styles.description}>
<Text style={styles.descriptionText}>
{description.length > 100
? description.slice(0, 100) + "..."
: description}
</Text>
</View>
</View>
</TouchableOpacity>
);
};
const styles = StyleSheet.create({
card: {
shadowColor: "black",
shadowOpacity: 0.25,
shadowOffset: { width: 0, height: 2 },
shadowRadius: 8,
borderRadius: 10,
backgroundColor: "#ffffff",
elevation: 5,
height: 300,
margin: 10,
},
titleContainer: {
height: "15%",
padding: 10,
},
title: {
fontSize: 18,
fontWeight: "bold",
color: "gray",
},
imageContainer: {
width: "100%",
height: "65%",
overflow: "hidden",
},
image: {
width: "100%",
height: "100%",
flexDirection: "row",
justifyContent: "space-between",
alignItems: "flex-end",
},
price: {
fontSize: 30,
color: "#fff",
margin: 10,
},
year: {
margin: 10,
backgroundColor: "#2652B0",
height: 25,
width: 80,
borderRadius: 5,
},
yearText: {
fontSize: 20,
color: "#fff",
textAlign: "center",
},
description: {
margin: 10,
},
descriptionText: {
fontSize: 16,
color: "gray",
},
});
export default Card;
HomeListScreen.js
import React, { useEffect, useState } from "react";
import {
StyleSheet,
View,
Text,
FlatList,
ActivityIndicator,
} from "react-native";
import { FloatingAction } from "react-native-floating-action";
import { useDispatch, useSelector } from "react-redux";
import Card from "../components/Card";
import * as houseAction from "../redux/actions/houseAction";
const HomeListScreen = (props) => {
const dispatch = useDispatch();
const [isLoading, setIsLoading] = useState(false);
const { houses } = useSelector((state) => state.house);
useEffect(() => {
setIsLoading(true);
dispatch(houseAction.fetchHouses())
.then(() => setIsLoading(false))
.catch(() => setIsLoading(false));
}, [dispatch]);
if (isLoading) {
return (
<View style={styles.centered}>
<ActivityIndicator size="large" />
</View>
);
}
if (houses.length === 0 && !isLoading) {
return (
<View style={styles.centered}>
<Text>No home found. You could add one!</Text>
</View>
);
}
return (
<View style={styles.container}>
<FlatList
data={houses}
keyExtractor={(item) => item._id}
renderItem={({ item }) => (
<Card
navigation={props.navigation}
title={item.title}
address={item.address}
homeType={item.homeType}
description={item.description}
price={item.price}
image={item.image}
yearBuilt={item.yearBuilt}
id={item._id}
/>
)}
/>
<FloatingAction
position="right"
animated={false}
showBackground={false}
onPressMain={() => props.navigation.navigate("AddHome")}
/>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
},
centered: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
});
export default HomeListScreen;
<Text style={styles.title}>
{ title ? (title.length > 30 ? title.slice(0, 30) + "..." : title):true}
</Text>
Make sure that title is not undefined.

React Native Component Exception <Text strings must be rendered within a Text component>

I am trying to build a react native application with the expo, First I try to build this application with my chrome web browser, it was worked without any issue after that I try to test the application with my android device and I'm getting an exception - "Text strings must be rendered within a <Text> component" HomeScreen.js files. I have no idea why this happened. My code as follows,
/*This is an Example of Grid View in React Native*/
// import * as React from "react";
import React from 'react';
import { Image, FlatList, StyleSheet, View, Text, TouchableOpacity, TextInput } from 'react-native';
import { COLORS } from '../../asserts/Colors/Colors';
import { CATEGORIES } from '../../asserts/mocks/itemListData';
import CategoryGridTitle from '../components/HomeGridTile';
import { HeaderButtons, Item } from 'react-navigation-header-buttons';
import { HeaderButton } from '../components/HeaderButton';
import HomeScreenButton from '../components/HomeScreenButton';
//import all the components we will need
const renderTopBarItems = (topBarItems) => {
return (
<TouchableOpacity
style={styles.topBar}>
<Text style={styles.textStyle}> {topBarItems.item.category} </Text>
</TouchableOpacity>
)
}
const HomeScreen = props => {
const renderGridItem = (itemData) => {
return <CategoryGridTitle
title={itemData.item.title}
image={itemData.item.image}
onSelect={() => {
props.navigation.navigate({
routeName: 'PaymentHandlerScreen',
params: {
categoryId: itemData.item.id
}
});
}} />;
}
// const [images, setImages] = React.useState(picsumImages);
return (
<View style={styles.mainBody}>
<View style={styles.searchContainer}>
<TextInput
placeholder='Search'
style={styles.formField}
placeholderTextColor={'#888888'}
/>
<TouchableOpacity onPress={() => props.navigation.navigate('BarCodeScannerScreen')}
style={styles.saveFormField}>
<Image
source={require('../../../images/barcode.png')}
style={{
width: '100%',
height: '30%',
resizeMode: 'contain',
alignContent: 'center',
}}
/> </TouchableOpacity>
</View>
<View style={styles.tabBar}>
<FlatList
horizontal
pagingEnabled={true}
showsHorizontalScrollIndicator={false}
keyExtractor={(item, index) => item.id}
data={CATEGORIES}
renderItem={renderTopBarItems} />
</View>
<FlatList
keyExtractor={(item, index) => item.id}
data={CATEGORIES}
renderItem={renderGridItem}
numColumns={3} />
<HomeScreenButton style={styles.buttonView} />
</View>
);
};
HomeScreen.navigationOptions = navigationData => {
return {
headerTitle: 'Tickets',
headerRight: (
<HeaderButtons HeaderButtonComponent={HeaderButton}>
<Item
title='profile'
iconName='ios-star'
onPress={() => {
console.log('profile clicked');
}} />
<Item
title='more'
iconName='md-more'
onPress={() => {
console.log('more clicked');
}} />
</HeaderButtons>
)
};
};
export default HomeScreen;
const styles = StyleSheet.create({
mainBody: {
flex: 1,
justifyContent: 'center',
backgroundColor: COLORS.background,
paddingTop: '3%',
},
searchContainer: {
flex: 1,
flexDirection: 'row',
},
tabBar: {
paddingBottom: '3%',
},
topBar: {
width: 150,
borderWidth: 1,
borderRadius: 20,
borderColor: COLORS.primary_blue,
padding: '5%',
marginLeft: '5%',
},
textStyle: {
color: COLORS.primary_blue,
textAlign: 'center',
fontWeight: 'bold',
fontSize: 14,
},
formField: {
flex: 4,
borderWidth: 1,
padding: '4%',
marginLeft: '2%',
borderRadius: 10,
borderColor: COLORS.gray,
backgroundColor: COLORS.gray,
fontSize: 15,
height: '35%',
},
saveFormField: {
flex: 0.5,
justifyContent: 'space-between',
alignItems: 'center',
margin: 10,
},
buttonView: {
position: 'absolute',
bottom: 0,
left: 0,
},
});
Thank you.
I ran into this error a couple of times. RN doesn't like extra spaces in tags. try removing the spaces before and after {topBarItems.item.category}
<Text style={styles.textStyle}>{topBarItems.item.category}</Text>

How to render Image and Text inline using React Native?

How can I get the share icon on the same row as the text?
import React, {Component} from 'react';
import {
ActivityIndicator,
AsyncStorage,
Dimensions,
Image,
ScrollView,
Share,
StatusBar,
StyleSheet,
TouchableOpacity,
View
} from 'react-native';
import {Text} from 'react-native-elements';
import {Button} from 'react-native-share';
import Hyperlink from 'react-native-hyperlink'
import Icon from 'react-native-vector-icons/dist/FontAwesome';
const SCREEN_WIDTH = Dimensions.get('window').width;
const SCREEN_HEIGHT = Dimensions.get('window').height;
const IMAGE_SIZE = SCREEN_WIDTH - 80;
class CustomButton extends Component {
constructor() {
super();
this.state = {
selected: false
};
}
componentDidMount() {
const {selected} = this.props;
this.setState({
selected
});
}
render() {
const {title} = this.props;
const {selected} = this.state;
return (
<Button
title={title}
titleStyle={{fontSize: 15, color: 'white'}}
buttonStyle={selected ? {
backgroundColor: 'rgba(213, 100, 140, 1)',
borderRadius: 100,
width: 127
} : {
borderWidth: 1,
borderColor: 'white',
borderRadius: 30,
width: 127,
backgroundColor: 'transparent'
}}
containerStyle={{marginRight: 10}}
onPress={() => this.setState({selected: !selected})}
/>
);
}
}
class Fonts extends Component {
constructor(props) {
super(props);
this.state = {
...this.state,
selectedIndex: 0,
value: 0.5,
dataSource: null,
isLoading: true,
visible: false
};
this.componentDidMount = this.componentDidMount.bind(this);
}
getNavigationParams() {
return this.props.navigation.state.params || {}
}
componentDidMount() {
if (AsyncStorage.getItem('name')) {
this.setState({'name': AsyncStorage.getItem('name')});
}
return fetch('http://www.koolbusiness.com/newvi/' + this.props.navigation.state.params.id + '.json')
.then((response) => response.json())
.then((responseJson) => {
this.setState({
...this.state,
isLoading: false,
dataSource: responseJson,
fontLoaded: true
}, function () {
});
})
.catch((error) => {
console.error(error);
});
}
onCancel() {
console.log("CANCEL")
this.setState({visible: false});
}
onOpen() {
console.log("OPEN")
this.setState({visible: true});
}
render() {
if (this.state.isLoading) {
return (
<View style={{flex: 1, padding: 20}}>
<ActivityIndicator/>
</View>
)
}
let shareOptions = {
message: 'http://www.koolbusiness.com/newvi/' + this.props.navigation.state.params.id + '.html',
url: 'http://bam.tech',
title: this.state.dataSource.title,
subject: this.state.dataSource.title // for email
};
return (
<View style={{flex: 1}}>
<StatusBar
barStyle="light-content"
/>
{this.state.fontLoaded ?
<View style={{flex: 1, backgroundColor: 'rgba(47,44,60,1)'}}>
<View style={styles.statusBar}/>
<View style={styles.navBar}><TouchableOpacity onPress={() => {
Share.share(shareOptions);
}}>
<View>
<Text> <Icon color="white" name="share-alt" size={42}/></Text>
</View>
</TouchableOpacity>
<Text style={styles.nameHeader}>
{this.state.dataSource.title}
</Text>
</View>
<ScrollView style={{flex: 1}}>
<View style={{justifyContent: 'center', alignItems: 'center'}}>
<Image
source={{uri: this.state.dataSource.img ? this.state.dataSource.img : "http://www.koolbusiness.com/_/images/icons/electronics_icon.png"}}
style={{width: IMAGE_SIZE, height: IMAGE_SIZE, borderRadius: 10}}
/>
</View>
<View style={{
flex: 1,
flexDirection: 'row',
marginTop: 20,
marginHorizontal: 40,
justifyContent: 'center',
alignItems: 'center'
}}>
<Text style={{flex: 1, fontSize: 26, color: 'white'}}>
{this.state.dataSource.title}
</Text>
</View>
<View style={{flex: 1, marginTop: 20, width: SCREEN_WIDTH - 80, marginLeft: 40}}>
<Text style={{flex: 1, fontSize: 15, color: 'white'}}>
{this.state.dataSource.date}
</Text>
<Hyperlink linkDefault={true}>
<Text style={{flex: 1, fontSize: 15, color: 'white'}} >
{this.state.dataSource.text}
</Text>
</Hyperlink>
</View>
</ScrollView>
</View> :
<Text>Loading...</Text>
}
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
}, nameHeader: {
color: 'white',
fontSize: 22,
textAlign: 'center'
},
textStyle: {
fontSize: 18,
padding: 10,
},
});
export default Fonts;
It works as expected but I want to render the share icon on the same row as the title text.
Can it be done?
You can use "flexDirection" to 'row' and group share icon along with the title in a view:
<View style={styles.navBar}>
<TouchableOpacity onPress={() => {Share.share(shareOptions); }}>
<View>
<Text>
<Icon color="white" name="share-alt" size={42}/>
</Text>
</View>
</TouchableOpacity>
<Text style={styles.nameHeader}>
{this.state.dataSource.title}
</Text>
</View>
add the below styling to your navBar className :
navBar: {
flex: 1,
flexDirection: 'row'
},
Also, you are using Text and a View in your TouchableOpacity which I think is not needed so I removed those and did a snack on expo similar to your code, please check it here :
https://snack.expo.io/Hk3lrAQ9f

Categories