I was having issues with something like this (specifically in the TextInput value attribute):
const Stuff = props => {
const [items, setItems] = useState([]);
const handleNewItem = () => {
setItems([...items, '']);
};
const handleText = (text, index) => {
items[index] = text;
setItems(items);
// this was populating correctly in console.log
// as I type, it will come out like ["somedata", "blah"....] etc...
};
return (
<>
<View style={{marginTop: 20}}>
<View>
{items.map((items, index) => {
return (
<View key={index}>
<Text>{index + 1}</Text>
// issue with setting value attribute
// Text would disappear as I type in the input field
<TextInput value={items} onChangeText={text => handleText(text, index)} />
</View>
);
})}
<TouchableOpacity onPress={e => handleNewItem(e)}>
<Text>Add item</Text>
</TouchableOpacity>
</View>
</View>
</>
);
};
I was able to get console logged out the correct values for items, but on my mobile simulator, when I type something, the text disappears.
When I removed value={items} from the TextInput component, I'm able to type in the simulator input field, without the text disappearing. I always thought we needed a value from reactjs. Do we not need this? or am I doing something wrong?
I would suggest don't directly update your state. Instead use new object to update the state like
const handleText = (text, index) => {
let newItems = [...items];
newItems[index] = text;
setItems(newItems);
};
Related
const [itemsPressed, setItemsPressed] = useState(true);
const changeColor = (x) => {
setItemsPressed(x)
}
If your item has some unique identifying ID/name, you can use that to help you track which one is pressed. (You could also just store the full item itself and do object equality but just a simple id/string is more than enough)
const [pressedItemIds, setPressedItems] = useState({});
const changeColor = useCallback((item) => {
setPressedItemIds({...pressedItemIds, [item.id]: true});
}, []);
<FlatList data={data}
renderItem={({ item }) => (
<TouchableWithoutFeedback onPress={() => changeColor(item)}>
<View
style={backgroundColor: pressedItemIds.hasOwnProperty(item.id) ? '#8b0000': '#ffc6c4'}>
<Text>{data.text}</Text>
</View>
</TouchableWithoutFeedback>
)} />
So basically what I said in the title, I am simply trying to change a prop that im passing to the component of Post if the item currently being rendered is in the viewport.
I am getting double the output like its firing twice, and its not even correct.
im comparing a key to the id (same thing) and if the 'activeVideo !== item.id' (video id's) are the same
the video should play, because i pass 'False' to the 'paused' property.
question is why am i getting double the output and why are both videos being paused when one of them clearly shouldnt>?
Need help fast, its for a school project.
Home.js
const [activeVideo, setActiveVideo] = useState(null);
const onViewRef = React.useRef((viewableItems)=> {
setActiveVideo(viewableItems.changed[0].key)
})
const viewConfigRef = React.useRef({ viewAreaCoveragePercentThreshold: 75 })
return (
<View>
<FlatList
data={posts}
showsVerticalScrollIndicator={false}
snapToInterval={Dimensions.get('window').height - 24}
snapToAlignment={'start'}
decelerationRate={'fast'}
onViewableItemsChanged={onViewRef.current}
viewabilityConfig={viewConfigRef.current}
renderItem={({item}) => <Post
post={item}
paused={activeVideo !== item.id}
/>}
/>
</View>
)}
Post.js
const Post = (props) => {
const [post, setPost] = useState(props.post);
const [paused, setPaused] = useState(props.paused);
console.log(props.paused)
const onPlayPausePress = () => {
setPaused(!paused);
};
const onPlayPausePress2 = () => {
setPaused(!paused);
};
return (
<View style={styles.container}>
<TouchableWithoutFeedback>
<Image
source={{uri: post.poster}}
style={styles.video2}
/>
</TouchableWithoutFeedback>
<TouchableWithoutFeedback onPress={onPlayPausePress}>
<Video
source={post.postUrl}
style={styles.video}
onError={(e)=> console.log(e)}
resizeMode={'cover'}
repeat
paused={paused}
/>
</TouchableWithoutFeedback>
</View>
)}
I'm mapping array like this:
{activeNewsCategories.map((i) => (
<Tags
key={i}
tagName={i}
getNewsByCategory={getNewsByCategory}
/>
))}
This is inside of Tags component:
const Tags = ({tagName, getNewsByCategory}) => {
const [selectedCategory, chooseCategory] = useState(false);
return (
<TouchableOpacity
style={
selectedCategory
? styles.chosenActiveCategoriesButton
: styles.activeCategoriesButton
}
onPress={() => {
getNewsByCategory(tagName);
chooseCategory(!selectedCategory);
}}>
<Text style={styles.activeCategoriesButtonText}>{tagName}</Text>
</TouchableOpacity>
);
};
export default Tags;
In total it renders 5 tags from array.
When I click on one of the tags its state is changing to true how it is supposed to but the problem is, when I click on the another tag the state of previous tag is not being changed to false,Any suggestions on how can I achieve it please?
The solution i would go with is saving wich 'Tag' is selected by their key where you also map them, then add something like 'isSelected' as a prop wich is true for you the one you selected.
Something like:
const [selectedCategory, setSelectedCategory] = useState();
const getNewsByCategory = (tagName) => {
setSelectedCategory(tagName);
//whatever you were doing here before
}
{activeNewsCategories.map((i) => (
<Tags
key={i}
tagName={i}
isSelected={selectedCategory === i}
getNewsByCategory={getNewsByCategory}
/>
))}
And then change the
const [selectedCategory, chooseCategory] = useState(false);
in the Tags to
const [selectedCategory, chooseCategory] = tagName
I am working on a react-native contact app with realtime search functionality. I have a problem with the contact List re-rendering on every keypress. is there a way I can optimize the search function where I can avoid the unnecessary re-renders on contacts Flatlist. Thanks, in advance
here is my contact component:
const Contacts = () => {
const [contact, setContact] = useState({});
useEffect(() => {...read contacts and then save it to contact and inMemoryContact state});
const renderItem = ({ item }) => <RenderContacts item={item} />;
const renderList = () => {
return (
<FlatList
keyboardShouldPersistTaps="handled"
data={contact}
keyExtractor={(item, index) => index.toString()}
renderItem={renderItem}
/>
);
};
return (
<View>
<Text style={Styles.textStyle}>All Contacts</Text>
{renderList()}
</View>
);
};
here is my search component:
const SearchBar = ({ updateContactState }) => {
const { inMemoryContact } = useSelector(state => state.contactReducer);
const searchContacts = value => {
const filteredContact = inMemoryContact.filter(contactToFilter => {
const contactLowerCase = `${contactToFilter.firstName} ${contactToFilter.lastName}`.toLowerCase();
const searchTerm = value.toLowerCase();
return contactLowerCase.indexOf(searchTerm) > -1;
});
updateContactState(filteredContact);
};
return (
<View style={Styles.SectionStyle}>
<MaterialIcons style={Styles.iconStyle} name="search" size={28} />
<TextInput
placeholder="Search Contact"
autoCapitalize="none"
autoCorrect={false}
onChangeText={content => {
searchContacts(content);
}}
/>
</View>
);
};
here is my renderContact component which keeps re-rendering:
const RenderContacts = ({ item }) => {
return item.phoneNumbers.map(element => (
<TouchableOpacity ...>
... list of contacts
</TouchableOpacity>
));
)
}
Your Problem Statement
"contact List re-rendering on every keypress"
Proposed Solution
Use debounce to delay the call. Here is a detailed article on what debounce does. Debounce will delay processing your input until a certain delay period. This will prevent the extra re-renders that can occur while a user is quickly typing in the search bar.
debounce function is readily available in most utils library including lodash.
Two solutions:
Use RenderContact as a function not JSX element
Bring it outside the scope of your main function
Let me know which one works for you.
I use a RootForm as the basic template for form page. There is one field associated with location autocomplete, so I wrap the native autocomplete of react-native and use it in that field. However, the autocomplete dropdown list is blocked by other fields in the form which are rendered behind it. I try to search online but no useful materials. Using modal or zIndex is not the solution here. How could I make the dropdown list on the top of other components even if it renders earlier than other components?
The following two snippets are my rootform and autocomplete render functions.
render() {
const { input } = this.state;
const cities = this.state.cities;
return (
<View style={styles.container}>
<Autocomplete
autoCapitalize="none"
autoCorrect={false}
containerStyle={styles.autocompleteContainer}
data={cities}
defaultValue={input}
onChangeText={text => this.setState({ input: text })}
placeholder="Enter Location"
renderItem={({ cityName, cityId }) => (
<TouchableOpacity style={styles.listStyle} onPress={() => this.setState({ input: cityName, cities: [] })}>
<Text style={styles.itemText}>
{cityName}
</Text>
</TouchableOpacity>
)}
/>
</View>
);
}
render() {
const data = this.props.data;
let fields = [];
let onPress = null;
Object.keys(data).forEach((key, index) => {
let options = data[key].options ?
data[key].options : null
if ("type" in data[key]) {
fields.push(
<View style={styles.formField} key={key}>
<Text style={styles.text}>{data[key].label}</Text>
<AutoComplete />
</View>
)
} else {
let custom = [styles.formField];
if (options) {
fields.push(
<View style={custom} key={key}>
<Text style={styles.text}>{data[key].label}</Text>
<TextInput value={data[key].value} style={styles.input}
readOnly
{...options} />
</View>
)
} else {
fields.push(
<View style={custom} key={key}>
<Text style={styles.text}>{data[key].label}</Text>
<TextInput value={data[key].value} style={styles.input}
onChangeText={(text) => this.props.onFieldChange(key, text)}
{...options} />
</View>
);
}
}
})
return (
<KeyboardAwareScrollView style={styles.container}>
{fields}
</KeyboardAwareScrollView>
)
}
You can just change your style.container to have a higher zIndex than whatever is appearing on top of it. However this will have the other items in the form appear behind the area reserved for the dropdown list, and render them unselectable.
If you want items underneath the Autocomplete component's area to still be interactive/selectable, you can use React.useState in order to have a dynamic zIndex property on your component.
const [componentZIndex, setComponentZIndex] = React.useState(1);
You will want your components behind the area reserved for the list to have a zIndex higher than 2 so that they are interactive.
Then you'll want to render your own input component so that you have access to the onFocus property. Luckily, the library you are using allows you to do this:
renderTextInput={() => (
<View style={styles.input}>
<TextInput
onFocus={() => setComponentZIndex(999)}
value={value}
/>
</View>
)}
This will bring the list to the top whenever the user is using the autocomplete component. The last thing to do is to make sure that you push it to the back once the user is no longer using it.
React.useEffect(() => {
Keyboard.addListener('keyboardDidHide', _keyboardDidHide);
return () => {
Keyboard.removeListener('keyboardDidHide', _keyboardDidHide);
};
}, []);
const _keyboardDidHide = () => {
setComponentZIndex(1)
};