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?