Function is not being called when state changes - javascript

This is my function for rendering items in a flatlist
renderItem = ({ item }) => {
var integer = Number(item.key)
return (
<View>
<Text style={styles.row}>
{item.text}
</Text>
<View style={{flexDirection:'row'}}>
{this.createButtonYes(integer)}
{this.createButtonNo(integer)}
{this.answer(this.state.buttonStates[integer])}
</View>
<Text >__</Text>
</View>
)
}
And the problem I am facing is the function this.answer is not being called when the state of buttonStates changes
answer = (val) => {
if(val){
return(
<Text style={{fontSize:20}}>YES</Text>
)
}else if(val==false){
return(
<Text style={{fontSize:20}}>NO</Text>
)
}else{
return(
<Text style={{fontSize:20}}>Not Answered</Text>
)
}
}
I assumed that every time the state changes the function would be called but that does not seem to be the case, so does anyone have a solution? What I want is whenever the buttons are pressed the state will change and then this.answer will take the changed state and display what it has to accordingly.
Thanks
EDIT:
Code for the button:
buttonYesHelp = num =>{
const newItems = [...this.state.buttonStates];
newItems[num] = true;
return newItems
}
createButtonYes = (num) => {
return(
<TouchableOpacity style={styles.buttonYes}
onPress =
{
()=> {{this.setState({ buttonStates:this.buttonYesHelp(num) })}}
}>
<Text style={styles.buttonTextStyle}>YES</Text>
</TouchableOpacity>
)
}
num is the index of the thing I want to change in the list
EDIT:
I have tried multiple different things but the problem I keep running into is that when I render the button I want it to react to a state variable but it never seems to change based on the state even when the state is changing.
For example, in this.answer I assumed that it would return the text based on the state of buttonStates but it seems to only account for the initial state and nothing after
I was able to achieve this in a different piece of code with identical syntax but for some reason this is not working

Related

Reusing Component State issue - State always retained for last reference loaded

I tried creating a reusable component in React. Which has a textInput and secure text entry handled in the state of the reusable component. But the state is not getting maintained differently when reusing always the last state is updated,
Issue: If i call the reusable const two times on a single screen or on the next screen in stack. The toggle for secure entry keeps changing for the last field loaded and earlier loaded fields state is lost.
i.e., when i click on toggle of Password, text change from hidden to visible or vice-versa happens for Confirm password field.
This is how i call
<View style={styles.inputContainerView}>
<InputTextFieldView
enteredText={passwordEntered}
setEnteredText={setPasswordEntered}
title={Constants.registerPasswordPlaceholder} icon={lockIcon}
isSecureEntry={true}
placeholder={Constants.registerPasswordPlaceholder} />
</View>
<View style={styles.inputContainerView}>
<InputTextFieldView
enteredText={confirmPasswordEntered}
setEnteredText={setConfirmPasswordEntered}
title={Constants.registerConfirmPasswordPlaceholder} icon={lockIcon}
isSecureEntry={true}
placeholder={Constants.registerConfirmPasswordPlaceholder} />
</View>
My component:
const InputTextFieldView = ({ enteredText, setEnteredText, title, icon, isSecureEntry, placeholder }) => {
const [isSecureEntryEnabled, setIsSecureEntryEnabled] = useState(isSecureEntry)
const eyeOpenIcon = require('../../../assets/visibility.png')
const eyeCloseIcon = require('../../../assets/close-eye.png')
useEffect(() => {
console.log('called')
}, [])
toggleSecureTextEntry = () => {
console.log('title', title)
setIsSecureEntryEnabled(!isSecureEntryEnabled)
}
return (
<View style={styles.fieldsContainerView}>
<Text style={styles.titleStyle}>{title}</Text>
<View style={[styles.fieldInputContainerView, {padding: Platform.OS === 'ios' ? 12 : 0}]}>
<Image source={icon} style={styles.fieldIconView} />
<TextInput secureTextEntry={isSecureEntryEnabled} style={{ width: isSecureEntry ? '75%' : '85%' }} onChange={() => setEnteredText()} value={enteredText} placeholder={placeholder} />
{isSecureEntry &&
<TouchableWithoutFeedback onPress={() => toggleSecureTextEntry()}>
<Image source={isSecureEntryEnabled ? eyeOpenIcon : eyeCloseIcon} style={styles.fieldIconView} />
</TouchableWithoutFeedback>
}
</View>
</View>
)
}
I'm guessing that you are using isSecureEntry as the hook to toggle the password fields? If so, it looks like you are passing the same state to both
the password field and the confirm password field. Right now, you essentially have one light switch that controls two different lamps. So you are going to want to have separate separate useState hooks for the password field and confirm password field. Then pass each one to the correct component.
const [passwordSecure, togglePasswordSecure] = useState(true);
const [confirmPasswordSecure, toggleConfirmPasswordSecure] = useState(true);
const togglePasswordField = () => {
togglePasswordSecure(!passwordSecure)
};
const toggleConfirmPasswordField = () => {
toggleConfirmPasswordSecure(!confirmPasswordSecure)
};
Issue was happening due to TouchableWithoutFeedback. Now used TouchableOpacity and it started to work. Not sure why but it may help someone

Maximum update depth exceeded new state React-Native

const [number,setNum] = useState(0); I get this error when I want to add and change it(setNum(number+1)). My Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops. What can i to solve this?
const App = ()=>{
const [text,setText] = useState('');
const [todo,setToDo] = useState([]);
const [number,setNum] = useState(0);
const renderToDoCard = ({item})=>{
setNum(number+1)
return(
<TouchableHighlight
onLongPress={() => handleLongPress(item)}>
<ToDoCard todo={item} number={number}/>
</TouchableHighlight>
)
}
const handleLongPress = item => {
setToDo(todo.filter(i => i !== item));
return Alert.alert('Silindi');
};
return(
<SafeAreaView style={styles.container}>
<StatusBar backgroundColor='#102027'/>
<View style={styles.head_container}>
<Text style={styles.title}>Yapılacaklar</Text>
<Text style={styles.title}>{todo.length}</Text>
</View>
<View style={styles.body_container}>
<FlatList data={todo} renderItem={renderToDoCard} />
</View>
<View style={styles.bottom_container}>
<ToDoInput todo={todo} setToDo={setToDo} text={text} setText={setText}/>
</View>
</SafeAreaView>
)
}
You've created an infinite update loop.
The problem is in how you're updating your number state inside renderToDoCard
const renderToDoCard = ({item}) => {
setNum(number + 1); // This is the problem, remove this line
return (
<TouchableHighlight onLongPress={() => handleLongPress(item)}>
<ToDoCard todo={item} number={number} />
</TouchableHighlight>
);
};
When renderToDoCard renders you update the state of your App component so it rerenders App which renders renderToDoCard which updates the state of your App component so it rerenders App which renders renderToDoCard...
This process repeats until the max update depth is reached.
Simply remove setNum(number + 1); and that problem is fixed.
It seems to me from your code that all you use your number state for is to keep track of the current item index so you can pass this to the ToDoCard component. The FlatList's renderItem also provides access to the current item index which you could pass to the number prop of ToDoCard
renderItem({ item, index, separators });
https://reactnative.dev/docs/flatlist#required-renderitem
So you could instead do something like this
const renderToDoCard = ({item, index}) => {
return (
<TouchableHighlight onLongPress={() => handleLongPress(item)}>
<ToDoCard todo={item} number={index} />
</TouchableHighlight>
);
};
Alternative you can add a key to each item in todo and use that instead of the index.

React native change state loop

I'm building a react native app where a post has comments. I only want to
show the comments when the user clicks on load comments.... The problem
is how do I handle the state for each post (there are multiple posts). I tried
this but it's not working (renderPost is a loop):
const renderPost = ({ item, index}) => {
let fetchComments = false;
return (
<View style={[t.mB6]}>
<View style={[t.roundedLg, t.overflowHidden, t.shadow, t.bgWhite, t.hAuto]}>
<TouchableOpacity
key={item.id}
onPress={() => {
fetchComments = true;
}}>
<Text style={[t.fontBold, t.textBlack, t.mT2, t.mL4, t.w1_2]}>
load comments...
</Text>
</TouchableOpacity>
</View>
{ fetchComments ? <Comments postId={item.id}/> : null }
</View>
)
}
In the code above I set let fetchComments to true when the user clicks on load comments....
renderPost is a functional component that doesn't have its own render and its own state, you may resolve this passing a function that changes state through renderPost props in its Father React.Component.
Example:
//imports
class FatherComponentWithState extends React.component{
state={
fetchComments:false,
//(...OTHERSTUFFS)
}
setFetchComments = () =>{
this.setState({fetchComments:true})
}
render(){
return(
//(...FatherComponentStuffs)
{new renderPost({
setFetchComments: this.setFetchComments,
fetchComments:this.state.fetchComments,
//(...renderPostOtherStuffs like item, index)
})}
//(...FatherComponentStuffs)
)}}
The renderPost function will receive it with something like this:
const renderPost = (props) =>{
let fetchComments = props.fetchComments;
let setFetchComments = props.setFetchComments;
let item = props.item
let index = props.index
//...renderPost return remains the same
}
P.S.: If you have multiple renderPosts, you can use fetchComments as an array of booleans and set the state to true passing an index as parameter of the setFetchComments function.

React Native: In an array of Text Inputs which are looped then displayed, how to get nth element and modify those elements separately?

In this module I am trying to create a survey module similar to the one in twitter.
at first, color of text input borders are grey and when I focus (click) the text input, only one of them (clicked one) must be blue. Same idea when I type a text, they all shouldn't get the same value. I should be able to get each text input value that I created by clicking plus icon, as a String
Should I use a flatlist or a listview rather than a for loop ?
React-Native Listview, press row and change that row style
I also tried to solve it according to this example.
I change this example a little bit, I was able to change border color of clicked one. but still, I couldn't get the values...
Any solution suggestion ? Thank you.
screenshot 1
screenshot 2
This is my code;
changeInputBorderColor = () => {
const newinputBorderColor = cloneDeep(this.state.inputBorderColor);
newinputBorderColor.bar = '#04A5F5';
this.setState({inputBorderColor: {bar: newinputBorderColor.bar}});
};
changeInputBorderColor2 = () => {
this.setState({
inputBorderColor: {
bar: 'grey'
}
})
};
incrementInputCount = () => {
if (this.state.inputCounter < 5) {
this.setState(prevState => {
return {inputCounter: prevState.inputCounter + 1}
});
console.log(this.state.inputCounter);
}
else {
this.setState(prevState => {
return {inputCounter: prevState.inputCounter}
});
alert("Maximum soru sayısına ulaştınız");
}
};
render() {
let surveyOptions = [];
for (let i = 0; i < this.state.inputCounter; i++) {
console.log(this.state.inputCounter);
surveyOptions.push(
<View key={i}>
<View>
<TextInput
style={[styles._surveyTextInput, {borderColor: this.state.inputBorderColor.bar}]}
onChangeText={(text) => this.setState({text})}
value={this.state.text}
onFocus={this.changeInputBorderColor}
onBlur={this.changeInputBorderColor2}
placeholder={"Secenek " + (i + 1)}
/>
</View>
</View>
)
}
return (
<View style={styles._surveyMainContainer}>
<View style={{flex: 0.8}}>
{surveyOptions}
<TouchableOpacity style={{position: 'absolute', right: 5, top: 5}}>
<Ionicons name={"ios-close-circle"}
size={30}
color={'black'}
/>
</TouchableOpacity>
<TouchableOpacity style={{position: 'absolute', right: 5, top: 45}}
onPress={this.incrementInputCount}>
<Ionicons name={"ios-add-circle"}
size={30}
color={'blue'}
/>
</TouchableOpacity>
</View>
<View style={{flex: 0.2}}>
<View
style={styles.renderSeparator}
/>
<Text style={{fontWeight: 'bold', margin: 5}}>Anket süresi</Text>
</View>
</View>
);
}
You can do it with a .map however you have to set it up correctly so that each TextInput has its own value in state. Currently what you are doing is setting the same value in state for each TextInput this results in every TextInput having the same value. Clearly not what you want.
Create an initial array in state (textArray) that has all values as empty strings, this will be used to store the values from each TextInput.
Set the focusedIndex to be null in state
Create a function that uses the previous state value to update the current state.
Create a function to handle the changing of the box color, it will just compare the TextInput index with the current focusedIndex
Iterate over the textArray and create the TextInput components. Make sure each TextInput has its own value in state.
Make sure we set the value of the focusedIndex in the onFocus and onBlur in the TextInput. When it blurs we should set the value to null so that it removes the border color when the keyboard is dismissed.
So we could do something like the following
export default class App extends React.Component {
constructor(props) {
super(props);
// construct an array with the number of textInputs we require,
// each value an empty string
// set this array in state
// set the focusedIndex to null
let textArray = Array(6).fill('');
this.state = {
textArray: textArray,
focusedIndex: null
}
}
// this function will handle setting of the state when each TextInput changes
onChangeText = (text, index) => {
// as there are going to be a lot of setState calls
// we need access the prevState before we set the next state.
this.setState(prevState => {
prevState.textArray[index] = text
return {
textArray: prevState.textArray
}
}, () => console.log(this.state.textArray))
}
// handle the border color
handleBorderColor = (index) => {
return index === this.state.focusedIndex ? 'red' : 'grey'
}
render() {
// here we map the items in the `this.state.textArray`
// notice that each TextInput is give a specific value in state
// that will stop the overlap
return (
<View style={styles.container}>
{this.state.textArray.map((text, index) => {
return <TextInput
style={{height: 40, marginVertical: 10, borderColor: this.handleBorderColor(index), borderWidth: 1}}
onChangeText={text => this.onChangeText(text, index)}
value={this.state.textArray[index]}
placeholder={`placeholder for ${index}`}
onFocus={() => this.setState({focusedIndex: index})}
onBlur={() => this.setState({focusedIndex: null})}
/>
})}
</View>
);
}
}
If you then want to access a specific value for a TextInput you can do so like this
let value = this.state.textArray[index]; // where the index is the value you want
Here is an example snack showing the code working
https://snack.expo.io/#andypandy/map-multiple-textinputs
It is definitely worthwhile looking at the following articles about state, as I have used these properties in this example.
https://medium.learnreact.com/setstate-is-asynchronous-52ead919a3f0
https://medium.learnreact.com/setstate-takes-a-callback-1f71ad5d2296
https://medium.learnreact.com/setstate-takes-a-function-56eb940f84b6

How would I dynamically append duplicate components in react, react-native

I am confused about how to properly dynamically add/create same components on button press for react native. I have used .map(()=>{}) on existing info to create components and then display the results.
Would I have to save each new component into a setstate array, then map that?
I looked a little into refs, but wasn't sure how that was better than just a setstate. The problem I see is if I want to update the value for each component, how would I go about that if their all originally duplicates?
Something along the lines of this:
class SingleExercise extends Component {
constructor(props) {
super(props);
this.state = {
objInfo: this.props.navigation.getParam("obj"),
currentSetSelected: 0
};
this.addSet = this.addSet.bind(this);
}
addSet = () => {
return (
<addSet />
)
}
render() {
const { navigation } = this.props;
return (
<View style={{ flex: 1 }}>
<View style={{ height: 80 }}>
<addSet />
<View>
<Button //here
large={false}
onPress={() => {
this.addSet();
}}
title={"add more"}
/>
</View>
</View>
</View>
);
}
}
const addSet = () => {
return (
<TouchableHighlight>
<View>
<TextInput
style={{height: 40, borderColor: 'gray', borderWidth: 1}}
defaultValue={'test'}
onChangeText={(text) => this.setState({text})}
/>
</View>
</TouchableHighlight>
);
}
Here is what I would do:
Every click on addSet button should increment the AddSets counter like this:
<Button
large={false}
onPress={() => {
this.setState({addSetsCounter: this.state.addSetsCounter});
}}
title={"add more"}
/>
After every state update your component will be re-rendered. So now, all you need to do is to forLoop in through that counter and return as many AddSets components as needed. A simple for loop with .push() inside would do.
Inside render, before return place something like that:
let sets =[];
for(let i =0;i<this.state.addSetsCounter;i++){
sets.push(<AddSets key="AddSets-{i}"/>);
}
Then simply render your {sets}.
I cannot fully test that right now, I wrote that from the top of my head, just play with the above, at least I hope it points you in a right direction.

Categories