Below is my code. How do I get my potion count to update in real time while also avoiding the "More hooks than in previous render" error.
I know there is a way to do this, but I’m struggling to understand how it works. If someone could explain it well, that would be great because I will need this to happen alot in what I’m building.
import React, { useState } from 'react';
import { View, Text, Image, StyleSheet } from 'react-native';
import CustomButton from './CustomButton';
import { useFonts } from 'expo-font';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import player from './Player';
import usePotion from './UsePotion';
import { useNavigation } from '@react-navigation/native';
function addPotion() {
player.potions ++;
}
function BattleScreen() {
const [fontsLoaded, error] = useFonts({
'Valorax': require('./Fonts/Valorax.otf'),
});
const navigation = useNavigation()
const [potionMessage, setPotionMessage] = useState('');
if (!fontsLoaded) {
return <Text>Loading...</Text>;
}
return (
<View style={styles.container}>
<Text style={styles.text}>{player.potions}</Text>
{potionMessage && <Text style={styles.text}>{potionMessage}</Text>}
<View style={styles.topHalf}>
</View>
<View style={styles.bottomHalf}>
<View style={styles.buttonRow}>
<CustomButton onPress={() => handleAttack()} title='Attack'></CustomButton>
<CustomButton onPress={() => handleMagic()} title='Magic'></CustomButton>
</View>
<View style={styles.buttonRow}>
<CustomButton onPress={() => usePotion(setPotionMessage)} title='Use Potion'></CustomButton>
<CustomButton onPress={() => handleRun()} title='Run'></CustomButton>
<CustomButton onPress={() => navigation.navigate('Home')} title="Home"></CustomButton>
<CustomButton onPress={() => addPotion()} title="Add Potion"></CustomButton>
</View>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#000000',
},
topHalf: {
flex: 1,
color: 'white',
},
bottomHalf: {
flex: 1,
flexDirection: 'row',
flexWrap: 'wrap'
},
buttonRow: {
flex: 1,
flexDirection: 'row',
justifyContent: 'space-evenly',
flexWrap: 'wrap'
},
text: {
fontSize: 40,
color: 'white',
fontFamily: "Valorax",
}
});
export default BattleScreen;
2
Answers
I think that the errors you are seeing is caused by calling the
usePotion
hook in your JSX. Hooks can only be called in the top level of other other hooks, or the top level of other components. I’m not sure whatusePotion
does withsetPotionMessage
, but it needs to be called at the top level of the component. If there’s some event you want to be triggerable fromusePotion
you need to return it fromusePotion
:Now you have a hook that returns some state about potions, and allows you trigger other state to update:
Finally you need to get player into react lifecycle. You could either convert Player.js into a hook, or you could you could put player into state:
In order solve the potion count not updating, the short answer is that you will have to utilize a
useState
hook. The short reasoning for this is because react will only rerender when state is changed and it is smart enough to only update the components that have been impacted by the state change.An example of this in your code is when the
setPotionMessage
method is called and thepotionMessage
value is updated, the only update react makes is to the JSX{potionMessage && <Text style={styles.text}>{potionMessage}</Text>}
.The long reason for why this data needs to be stored in state is the following:
When react initially renders, it compiles a DOM tree of all of your returned components. This DOM tree will look pretty similar to the JSX you have written, with additional parent tags and other stuff. When and only when there has been a change in state, React will compile a new DOM tree, called the
Virtual DOM
with the newly changed data. Then it will compare the existing DOM tree to the new Virtual DOM tree to find the components that have changed. Finally, react will update the original DOM tree but it will only update those components that have changed.Continuing with the
potionMessage
example I used above. WhenBattleScreen
initially renders, the Virtual DOM will render the JSX like this (pulled from thereturn
statement):However, once the
setPotionMessage
is called and thepotionMessage
value changes from''
to a message, react recompiles the Virtual DOM to determine what has changed. It will recompile to something like this:Then it compares the original DOM tree with the Virtual DOM tree to figure out what is different. When it finds what is different, it rerenders only the part of the tree that has changed on the actual DOM tree.
In terms of the code modifications needed, PhantomSpooks outlined the required changes. As they mentioned, getting the player into the react lifecycle will be key.