Empty items being added to array of refs in React-Native - javascript

Today I tried to delete all the elements that are in my array that I put in a map function, but unfortunately it didn't work.
My code so far -
const inputRefTest = React.useRef([]);
{items[step].mainWord.map((item, index) => {
return (
<Animated.View
key={index}
entering={ZoomIn}
exiting={ZoomOut}
layout={Layout.delay(100)}
onTouchEnd={() => {}}
style={styles.listItem}
>
<SquareGap
id={item}
ref={(el) => (inputRefTest.current[index] = el)}
onPress={() => {}}
/>
</Animated.View>
);
})}
I tried, of course, with
inputRefTest.current = []
but not working. So how can i delete items from ref array? So that when I change the step to add new items via
ref={(el) => (inputRefTest.current[index] = el)}
this line of code. Now when i change the step it has kept the previous items? Why?

Related

React Native: How to render fetch data in FlatList without id

I'm trying to render data from an API but it does not have an id or any unique data to distinguish the item
This is how the data looks like when I do console.log
Array [
Object {
"photo1": "www.picexample.com",
},
Object {
"photo2": "www.picexample.com",
},
]
This is my code:
Home.js
const [portfolio, setPortfolio] = React.useState([]);
const renderItem= ({item}) => {
return (
<View>
<Image source={{uri: ?}} resizeMode="cover" />
</View>
);
}
useEffect (() => {
.then((result) => {
setPortfolio(result.myImage);
console.log(portfolio);
})
});
return (
<ScrollView>
<FlatList
scrollEnabled={false}
data={portfolio}
renderItem={renderItem}
keyExtractor={?}
/>
</ScrollView>
);
UPDATE (Based on Joel Hager)
const keyExtractor = (portfolio, idx) => `${Object.keys(portfolio)}-${idx}`
const renderItem = ({item}) => {
console.log(item);
return (
<View>
<Image source={{uri: item}} resizeMode="cover" />
</View>
);
}
return (
<FlatList
scrollEnabled={false}
data={portfolio}
renderItem={renderItem}
keyExtractor={keyExtractor}
/>
);
Without being able to post working React Native code, this will explain what it's doing conceptually.
It's taking the item instance in the array that's being passed (the 'item')
It's grabbing all keys for it: Object.keys()
It's displaying the first one
There are some caveats: It expects a value. You could always use null coalescence to do something else, but I'd have to know more about the data to cover those cases. If you always get a key, you'll always get a value. You can also add the index value in the extractor to give it some more specificity.
Your react code should look like this:
keyExtractor={(item, idx) => `${Object.keys(item)}-${idx}`}
note: It's always good to extract the function outside of the flatlist so it isn't recreated every render. So it would look something like this:
const keyExtractor = (item, idx) => `${Object.keys(item)}-${idx}`
...
<Flatlist
...
keyExtractor={keyExractor}
...
More info on keyExtractor:
https://reactnative.dev/docs/flatlist#keyextractor
The template literal will put the value of the string of the key and the index with a '-' between them.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals
const payload = [
{
"photo1": "www.picexample.com",
},
{
"photo2": "www.picexample.com",
},
]
payload.map((item, idx) => console.log(Object.keys(item)[0] + `-${idx}`));

Newly created TextInputs in FlatList lose focus after 10th row - initialNumToRender

I'm attempting to build a To-Do list feature for an application; however, I'm struggling to get it right.
Basically, I want a user to be able to create new list items via the "submit" key while typing, or a button (both already done). When the new list item is created, I want the corresponding TextInput within the FlatList to be automatically focused. This works...up to a certain point. Once I create 10 list items, the behavior stops working properly and the focus is no longer on the next, newly created FlatList item.
I've figured out that if I change the "initialNumToRender" property in the FlatList and increase it past 10, it will solve the problem, temporarily, until reaching that cap. However, I don't want to reduce the performance of my FlatList and I want to find a different solution. It seems to me that the entire FlatList is re-rendering past 10 items and the TextInput loses focus, but I haven't been able to grasp yet how to focus on the TextInputs created after the 10th row.
The FlatList in question:
<FlatList
key="flatlist"
ListHeaderComponent={listHeader()}
initialNumToRender={10}
data={listItems}
// TODO: Will need to change to use actual IDs at some point as opposed to just indexing
renderItem={({item, index}) => (
<>
<View style={listStyles.itemView}>
<Pressable onPress={() => radioButtonPressed(index)}>
<MainListRadioButton completed={getRadioButtonState(index)} />
</Pressable>
{listItems[index].text.length > 0 ? (
<TextInput
style={listStyles.itemText}
placeholder={`Item ${index + 1}`}
defaultValue={listItems[index].text}
value={listItems[index].text}
onChangeText={text => listItemTextChanged(text, index)}
ref={index === listItems.length - 1 ? lastListItemRef : null}
onSubmitEditing={() => addListItem()}
/>
) : (
// Text input for "empty" list items so that a backspace can delete the entire row.
<TextInput
style={listStyles.itemText}
placeholder={`Item ${index + 1}`}
defaultValue={listItems[index].text}
value={listItems[index].text}
onChangeText={text => listItemTextChanged(text, index)}
ref={index === listItems.length - 1 ? lastListItemRef : null}
onKeyPress={({nativeEvent}) => {
if (nativeEvent.key === 'Backspace') {
deleteListItem(index);
}
}}
/>
)}
</View>
<View style={styles.divider} />
</>
)}
/>
How I'm implementing the behavior to shift focus to the next row so far:
useEffect(() => {
if (lastListItemRef.current) {
lastListItemRef.current.focus();
}
}, [listItems]);
You'd probably be better off using scrollToIndex() along with useLayoutEffect since you're wanting to trigger the scroll on a visual change.
useLayoutEffect(() => {
flatlistRef.current?.scrollToIndex(listItems.length - 1)
}, [flatlistRef, listItems])
Rather than controlling your TextInput focus at the parent level, you could turn your listItem function into a ListItem component. By doing this, you can store the ref to each new ListItem component within each component instance, and on mount of each component, focus it.
So your DraggableFlatList declaration becomes something like this:
<DraggableFlatList
...
renderItem={({ item, index, drag, isActive }) => (
<ListItem
item={item}
index={index}
drag={drag}
isActive={isActive}
addListItem={addListItem}
deleteListItem={deleteListItem}
completed={getRadioButtonState(index)}
text={listItems[index].text}
onChange={listItemTextChanged}
radioButtonPressed={radioButtonPressed}
/>
)}
...
/>;
And your ListItem component definition looks something like this:
const ListItem = ({
item,
index,
drag,
isActive,
text,
completed,
onChange,
radioButtonPressed,
addListItem,
deleteListItem,
} = props) => {
// Create ref to this TextInput
const textRef = useRef(null);
// On mount, set the TextInput focus
useEffect(() => {
if (textRef.current) {
textRef.current.focus();
}
}, []);
return (
<>
<Pressable style={listStyles.itemView} onLongPress={drag}>
<Pressable onPress={() => radioButtonPressed(index)}>
<MainListRadioButton completed={completed} />
</Pressable>
<TextInput
ref={textRef}
style={listStyles.itemText}
placeholder={`Item ${index + 1}`}
defaultValue={text}
value={text}
onChangeText={(text) => onChange(text, index)}
onSubmitEditing={() => addListItem()}
onKeyPress={({ nativeEvent }) => {
if (nativeEvent.key === 'Backspace') {
deleteListItem(index);
}
}}
/>
</Pressable>
<View style={listStyles.divider} />
</>
);
};
I've updated your snack here as a POC: https://snack.expo.dev/TZk_48CHF
you need to know if listItems is not a empthy array, for that reason you need to create this function isFechet() to evaluate it.
useEffect(() => {
const goToFocus=await ()=>{
if (lastListItemRef.current) {
lastListItemRef.current.focus();
}
}
if(isFechet(listItems)){
goToFocus()
}
}, [listItems]);
Thanks everyone. I actually ended up figuring it out myself. The steps I took were the following:
Completely remove setRef and ref within the TextInput and component.
Add autoFocus=true as a property to the TextInput. Previously I had a typo without capitalizing the F.
Change onTextChange to onEndEditing so the List Items only update after submitting text as opposed to on each text change.
In this case you have to use the flatlist scrollToIndex function along with viewPosition property properly for the selected changes.Also you have to use the onScrollToIndexFailed prop in flatlist along with setTimeout.
viewPosition: 0.5 ---> put the selected item in middle,0 and 1 are other values
const const scrollToIndex = (ref, index = 0) => {
if (ref && ref.current && index > -1) {
ref.current.scrollToIndex({ index, viewPosition: 0.5 });
}
};
const flatListRef = useRef();
useEffect(() => {
const selectedIndex = getSelectedIndex();
setTimeout(() => {
scrollToIndex(flatListRef, selectedIndex);
}, 50);
}, [title, flatListRef]);
<FlatList
ref={flatListRef}
initialNumToRender={categoryFilters.length + 1}
onScrollToIndexFailed={info => {
if (flatListRef !== null && info.index > -1) {
setTimeout(
() =>
flatListRef.current.scrollToIndex({
index: info.index,
animated: true,
viewPosition: 0.5,
}),
50
);
}
}}
refreshing={false}
data={listItems}
listKey={(_, index) => index.toString()}
renderItem={()=>{}}
/>

onPress event only takes last index of array - React Native

Before you mark this question as a duplicate of a closure issue or a event binding issue, know that I have tried all of those and it does not work.
A working demo of the issue. Open the link. Run the project on android and check logs. If you click on any option in the first question, it will log the lastIndex (Here it is 4. Since there are a total of 5 questions) when it should log the firstIndex which is 0.
So I am using javascript map to loop over my questions array and return questions and their respective options. Inside I have another map to loop over options.
The checkbox is a react-native-elements component.
renderCard = (item, index) => {
return (
<Card style={styles.testCard}>
<View key={item.u_question_id}>
<Text style={styles.question}>{index + 1}. {item.question}</Text>
{item.options.map(({ option, checked }, i) => {
return (
<View key={i}>
<CheckBox
containerStyle={{ backgroundColor: "transparent", borderWidth: 0 }}
title={option}
checkedIcon='dot-circle-o'
uncheckedIcon='circle-o'
checked={checked}
onPress={() => this.onSelectOption(index, i)}
/>
</View>
)
})}
</View>
</Card>
)
}
What I am expecting is that onPress of a checkbox should send the questionIndex and the optionIndex into onSelectOption to change the state and view but onPress always sends the last index of the questions array so the options of the last question are getting changed and not the intended one.
My onSelectOption method. Here questionIndex is coming 4 if I have 5 questions even though I am pressing on the first question's options.
onSelectOption = (questionIndex, optionIndex) => {
const selectedLanguageQuestionsCopy = this.state.selectedLanguageQuestions;
selectedLanguageQuestionsCopy[questionIndex].options.forEach(option => {
option.checked = false;
});
selectedLanguageQuestionsCopy[questionIndex].options[optionIndex].checked = true;
this.setState({ assessmentData: selectedLanguageQuestionsCopy });
}
I have tried using:
onPress={() => this.onSelectOption(index, i)}
onPress={this.onSelectOption.bind(this, index, i)} and changing onSelectOption to a normal method instead of an array function.
But it does not work. I am always getting the last index of the questions array.
The place where I am calling the renderCard method. selectedLanguageQuestions is an array of objects.
<Swipe
data={selectedLanguageQuestions}
activeQuestion={activeQuestion}
renderCard={(item, i) => this.renderCard(item, i)}
onSwipeLeft={(ques, i) => this.setState({ activeQuestion: i })}
onSwipeRight={(ques, i) => this.setState({ activeQuestion: i })}
/>
Render method of Swipe:
render() {
return (
<View>
{this.renderCards()}
</View>
);
}
renderCards() {
return this.props.data.map((item, i) => {
if (i === this.state.index) {
return (
<Animated.View
key={i}
style={[this.getCardStyle(), styles.cardStyle]}
{...this.panResponder.panHandlers}
>
{this.props.renderCard(item, i)}
</Animated.View>
);
}
return (
<Animated.View
key={i}
style={[styles.cardStyle, { opacity: 0 }]}
{...this.panResponder.panHandlers}
>
{this.props.renderCard(item, i)}
</Animated.View>
)
});
}
It seems like on Swipe.js line 137
return (
<Animated.View
key={i}
style={[styles.cardStyle, { opacity: 0 }]}
{...this.panResponder.panHandlers}
>
{this.props.renderCard(item, i)}
</Animated.View>
)
You are keeping the answers above each others, so what's happening exactly is that you are clicking on the answers that have 0 opacity, but because you don't see them, you think the visible answers are those getting the event. So what I suggest is that you disable the events on the answers that have 0 opacity, like so:
return (
<Animated.View
key={i}
pointerEvents="none"
style={[styles.cardStyle, { opacity: 0 }]}
{...this.panResponder.panHandlers}
>
{this.props.renderCard(item, i)}
</Animated.View>
)
Just added pointerEvents="none" attribute; to see how the questions were interrupting your "press", set the opacity to something above 0.5 and you will see the problem.
I hope this solves your issue.

How to toggle the state of an item inside a map funtion

I'm trying to make a tag selection, the problem is, I don't know how to make a state for each item in the map, right now I have just one state, that, of course, will change all items.
That's the state and the function to toggle the state
const [selectedActivity, setSelectedActivity] = useState(false);
const toggleSelectedActivity = () => {
setSelectedActivity(!selectedActivity);
};
and that's the map function
<View style={styles.tags}>
{activitiesObject.map((data, i) => (
<TouchableOpacity
key={data.activity}
onPress={() => toggleSelectedActivity(i)}
>
<Text style={selectedActivity ? styles.selectedTag : styles.tagsText}>
{data.activity}
</Text>
</TouchableOpacity>
))}
</View>;
the image below shows what I expect to happen every time the user selects a tag
Here is the full code: https://snack.expo.io/KIiRsDPQv
You can do one of following options
change state to an array
const [selectedActivity, setSelectedActivity] = useState(Array.from({ length: activitiesObject.length }, _ => false))
const toggleSelectedActivity = (index) =>
setSelectedActivity(prev => prev.map((bool, i) => i == index ? !bool : bool))
while passing the index to function, and use selectedActivity[i] ? ...
extract
<TouchableOpacity key={data.activity} onPress={() => toggleSelectedActivity(i)}>
<Text style={selectedActivity ? styles.selectedTag : styles.tagsText}>{data.activity}</Text>
</TouchableOpacity>
to its own component, and inside it declare the state
{activitiesObject.map((data, i) => <MyComp data={data} i={i} />
const MyComp = ({ data, i }) => {
const [selectedActivity, setSelectedActivity] = useState(false)
return <TouchableOpacity key={data.activity} onPress={() => setSelectedActivity(prev => !prev)}>
<Text style={selectedActivity ? styles.selectedTag : styles.tagsText}>{data.activity}</Text>
</TouchableOpacity>
}

How to add radio button in react native?

I want to add radio button in react native, which is pretty simple and I've done it. But the thing is I want to loop through the radio button upon the contents fetching from a server.So the list depends on the number of items fetching which is changing.The data I'm fetching is as follows
json data
{
"data": [{
"content": "question",
"selection": [
"(a).option1",
"(b).option2 ",
"(c).option3 ",
"(d).option4 "
],
"correct": "4",
}]
}
Well, I'm displaying content inside a CardSection component.I want to loop through the selection array with corresponding radio buttons.I'm using map method for rendering the data. So for selection what should I use?A for loop is okay for that?But I don't know how to do it please do help.Following is the react part.
updated
const formatArray = (arr) => {
let newArr = []
arr.map((item, index) => newArr.push({'label': index, value: item}))
return newArr
}
class QASection extends Component{
render() {
_setActive = (active) => this.setState({this.setState({selected:value})})
return(
{this.state.details.map(a =>
<Card>
<CardSection>
<Text>{a.content}</Text>
</CardSection>
<CardSection>
<RadioForm
radio_props={radio_props}
initial={0}
itemShowKey="label"
itemRealKey="value"
dataSource={formatArray(data)}
onPress={onPress}
/>
<CardSection data={data} key={index} onPress={this._setActive} />
</Card>
)}
);
}
}
By rendering selection[0] it will give me the first value in selection array. But how do I loop through this with radio buttons for each item in an array?
You can loop in the following way
I assume that you are using react-native-radio-form
const formatArray = (arr) => {
let newArr = []
arr.map((item, index) => newArr.push({'label': index, value: item}))
return newArr
}
const YourCardComponent = ({data, onPress}) => {
return (
<Card>
<CardSection>
<Text>{data.content}</Text>
</CardSection>
<CardSection>
<RadioForm
radio_props={radio_props}
itemShowKey="label"
itemRealKey="value"
dataSource={formatArray(data.selection)}
onPress={onPress}
// ... other props as required
/>
</CardSection>
</Card>
)
}
// Inside the class
_setActive = (active) => this.setState({selected:value})
render () {
const {details} = this.state
return (
<View>
{details.map((data, index) => (
<YourCardComponent data={data} key={index} onPress={this._setActive} />
))}
</View>
)
}

Categories

Resources