Function components in react native - javascript

I am trying to change view of ListItem by pressing on it.
In My screen which is normal React component i have functional List component and selectedItemState (only 1 or no items will be selected).
In List there are few also functional ListItem components.
The problem is lack of re-render ability for item.
I've tried memo as official React page says but with no results. Changing components to normal ones gave the same result.
Screen Component:
export default class myScreen extends Component {
constructor () {
super ()
this.state = {
data: [], // <-- there are my objects
isDataEmpty: false,
selectedItemId: ''
}
}
// ... some code
render () {
return (
<View style={styles.container}>
<List
itemList={this.state.data}
onItemPress={ /* go to next screen */}
onItemLongPress={id => {
this.setState({ selectedItemId: this.state.selectedItemId === id ? '' : id })
}}
selectedItemId={this.state.selectedItemId}
/>
</View>
)
}
}
List Component:
const List = props => {
return (
<FlatList
style={style.itemList}
data={props.itemList}
renderItem={info => (
<ListItem
item={info.item}
selectedItemId={props.selectedItemId}
onItemPress={id => props.onItemPress(id)}
onItemLongPress={id => props.onItemLongPress(id)}
/>
)}
/>
)
}
const areEqual = (previous, next) => {
return next.selectedItemId !== '' && (previous.selectedItemId === next.selectedItemId)
}
export default React.memo(List, areEqual)
List Item Component:
const ListItem = props => {
return (
<TouchableWithoutFeedback
onPress={() => props.onItemPress(props.item.id)}
onLongPress={() => {
props.onItemLongPress(props.item.id)
} }>
<View style={style.listItem}>
<Image resizeMode='cover' source={props.item.image} style={style.image} />
<Text>{props.selectedItemId === props.item.id ? 'XXX' : props.item.name}</Text>
</View>
</TouchableWithoutFeedback>
)
}
const areEqual = (previous, next) => {
return next.selectedItemId && (next.selectedItemId === next.item.id)
}
export default React.memo(ListItem, areEqual)
After pressing on any item i want it name to change to 'XXX'. If item will be pressed twice all items should be in normal state

As long as there are no changes on the item itself there will be no rerender of the according listitem.
You could try to force a rerender of the list by changing the value of the extraData flatlist prop though.

Related

How to access a single element from Touchable Opacity inside a map function?

I am running an map function on my array which returns JSX in which I have a touchable opacity and inside that some text. So that touchable opacity is applied to each element of the array.
array.map((item, index) => {
<TouchableOpacity onPress={someFunction} >
<View>
<Text>{item.data}</Text>
</View>
</TouchableOpacity>
)}
Consider I have 4 array elements, I want to click on one and change the background color of only one (the selected) or the selected plus another touchableopacity. How can I achieve this?
You have to create a ref for each element and then set the style on click. Here is a working demo on snack : Dynamic ref with functional component
I worked with a functional compoennt, but if you are using a class, here a link to show you how to implements it : Dynamic ref with Class component
And in case Snack doesn't works, here is the code :
import * as React from 'react';
import { Text, View, StyleSheet, TouchableOpacity } from 'react-native';
export default function App() {
const myRefs = React.useRef([]);
const items = [
{
id:0,
name:"Item1"
},
{
id:1,
name:"Item2"
},
{
id:2,
name:"Item3"
}
];
const buildView = () => {
return items.map(item =>{
return(
<TouchableOpacity onPress={() => highlight(item.id)}>
<View ref={el => myRefs.current[item.id] = el}>
<Text>{item.name}</Text>
</View>
</TouchableOpacity>
)
});
}
const highlight = (itemId) => {
myRefs.current[itemId].setNativeProps({style: {backgroundColor:'#FF0000'}});
}
const resetColors = () => {
myRefs.current.forEach(ref =>
ref.setNativeProps({style:{backgroundColor:'transparent'}})
);
}
return (
<View>
{buildView()}
<Button title="Next question" onPress={resetColors} />
</View>
);
}
I create a ref fow each view and onPress, I just change its style. Do whatever you want in the highlight method.

How to pass an array from a parent component to child component using props in React Native?

I want to paas "subjects" array from SubjectsScreen to MarkAttendanceScreen and display the array items as a FlatList.
Parent Component
export default class SubjectsScreen extends Component {
state = {
subjects: ["A", "B"]
};
render() {
return (
...
<MarkAttendanceScreen subjectsArray={this.state.subjects} />
);
}
}
Child Component
export default class MarkAttendanceScreen extends Component {
constructor(props) {
super(props);
this.state = {
subjects: []
};
}
componentDidMount() {
this.setState({ subjects: this.props.subjectsArray });
}
render() {
return (
<FlatList>
{ this.props.subjects.map((item, key)=>(
<Text key={key}> { item } </Text>)
)}
</FlatList>
);
}
}
Using props was giving error when using FlatList with map.
Works fine when extracting value directly from AsyncStorage.
export default class MarkAttendanceScreen extends Component {
state = {
subjects: [],
text: ""
}
componentDidMount() {
Subjects.all(subjects => this.setState({ subjects: subjects || [] }));
}
render() {
return (
<View>
<FlatList
data={ this.state.subjects}
renderItem={({item}) => {
return (
<View>
<Text> { item.text } </Text>
</View>
)
}}
keyExtractor={ (item, index) => index.toString()}
/>
</View>
);
}
}
let Subjects = {
convertToArrayOfObject(subjects, callback) {
return callback(
subjects ? subjects.split("\n").map((subject, i) => ({ key: i, text: subject })) : []
);
},
convertToStringWithSeparators(subjects) {
return subjects.map(subject => subject.text).join("\n");
},
all(callback) {
return AsyncStorage.getItem("SUBJECTS", (err, subjects) =>
this.convertToArrayOfObject(subjects, callback)
);
},
};
this.props.subjects does not exist, but you did set the state in componentDidMount. In the FlatList use this.state.subject.map.
render() {
return (
<FlatList>
{ this.state.subjects.map((item, key)=>(
// ^here
<Text key={key}> { item } </Text>)
)}
</FlatList>
);
}
You must use the same key name that you used while passing down data to child component e.g. in your case you used key subjectsArray here and You don't need to store this first in state and then use unless you want to update it later.
<MarkAttendanceScreen subjectsArray={this.state.subjects} />
So in your child component, it will be
<FlatList>
{this.props.subjectsArray.map((item, key)=>(
<Text key={key}> { item } </Text>
))}
</FlatList>
D. Smith is correct, you need to change that line to this.state.subjects.map But could also just remove the state variable from the Child Component and use the array directly from props.
export default class MarkAttendanceScreen extends Component {
constructor(props) {
super(props);
}
render() {
return (
<FlatList>
{ this.props.subjectsArray.map((item, key)=>(
<Text key={key}> { item } </Text>)
)}
</FlatList>
);
}
}
Update:
Flatlists need to be defined like this:
<FlatList
data={ this.props.subjectsArray }
renderItem={({item}) => {
return (
<Text> { item } </Text>)
)
}}
keyExtractor={(item, index) => index}
/>
or you can use it the way you have it and remove the flatlist like:
return this.props.subjectsArray.map((item, key)=>(
<Text key={key}> { item } </Text>)
)}

Can't update the item on change - React Native

I have a Post component imported as a module and a flatlist on my Home screen. When I click on the button on Post component, I want the flatlist on homepage to get the change and update the selected index. But I guess something is missing.
Post.js //
shouldComponentUpdate(nextProps) {
if(nextProps.data.is_bookmarked != this.state.data.is_bookmarked) {
this.setState({data: nextProps.data})
return true
}
else {
return false
}
}
...
<TouchableOpacity onPress={() => {
if(this.props.onConfirmed != undefined) {
this.props.onConfirmed(!this.state.data.is_bookmarked);
}
}}>
Home Page //
renderItem = ({ item, index }) => {
return (
<Post
onConfirmed={(index) => {
this.setState((prevstate) => {
prevstate.is_bookmarked = !item.is_bookmarked
return prevstate
})
}}
key = {item.id + '-' + item.is_bookmarked}
data={item}
></Post>
);
}
...
<FlatList
data={this.state.data}
renderItem={this.renderItem}
keyExtractor={item => item.id}
/>

View not re-rendering after onPress

I'm trying to change the backgroundColor of a React Native Card component when onPress event is triggered. Although I'm seeing the change of the state on componentDidUpdate, I'm not visualising it.
I'm changing the value of the itemsPressed array when the onPress event is triggered. If the pressed item id is already in the array it removes it else it adds it into the array.
export default class Popular extends Component {
constructor(props) {
super(props);
this.togglePressed = this.togglePressed.bind(this);
this.state = {
categories: [],
itemsPressed: []
}
}
togglePressed = item => {
const id = item.id;
this.setState(({ itemsPressed }) => ({
itemsPressed: this.isItemPressed(item)
? itemsPressed.filter(a => a != id)
: [...itemsPressed, id],
}))
};
isItemPressed = item => {
const id = item.id;
return this.state.itemsPressed.includes(id);
};
componentDidMount() {
this.setState({
categories:this.props.categories,
});
}
componentDidUpdate(){
console.log(this.state.itemsPressed);
}
renderTabItem = ({ item,index }) => (
<TouchableOpacity
style={styles.category}
key={index}
onPress={() => this.togglePressed(item)}
>
<Card center
style={[styles.card,{backgroundColor:
this.isItemPressed(item)
? item.color
: 'gray'
}]}>
<Image source={item.icon} style={styles.categoryIcon}/>
</Card>
<Text size={12} center style={styles.categoryName}
medium color='black'
>
{item.name.toLowerCase()}
</Text>
</TouchableOpacity>
);
renderTab(){
const {categories} = this.state;
return (
<FlatList
horizontal = {true}
pagingEnabled = {true}
scrollEnabled = {true}
showsHorizontalScrollIndicator={false}
scrollEventThrottle={16}
snapToAlignment='center'
data={categories}
keyExtractor={(item) => `${item.id}`}
renderItem={this.renderTabItem}
/>
)
}
render() {
return (
<ScrollView>
{this.renderTab()}
</ScrollView>
);
}
}
I expected a visual change but I couldn't re render the renderTab().
Thank you!
Your FlatList has the property category as data source, so it only re-renders the cells if it detects a change in the category property. Your code however is only changing itemsPressed, so no cell is re-rendered.
You can tell the FlatList to listen for changes state.itemsPressed by specifying it in the extraData property:
extraData={this.state.itemsPressed}

Why are my items not being sorted (re-rendered) in React?

I have a button that when clicked calls a function that sorts the products by case amount. I am updating the products array so I assumed this would trigger a re-render of the products being mapped in the code below but it is not. Does anyone know how to get this to trigger the products.map to be re-rendered again thus displaying the new sorted products?
render() {
const {products} = this.props;
const cartIcon = (<Icon name="shopping-cart" style={styles.cartIcon} />);
sortCartItems = () => {
products.sort((a, b) => a.cases > b.cases);
}
return (
<View style={styles.itemsInCartContent}>
<View style={styles.cartHeader}>
<TouchableOpacity onPress={sortCartItems}>
{cartIcon}
<Text>Sort</Text>
</TouchableOpacity>
</View>
{products.map((product, index) =>
<CartProductItem
ref="childCartProductItem"
key={product.id}
product={product}
index={index}
activeIndex={this.state.active}
triggerParentUpdate={() => this.collapseAllOtherProducts}
/>
)}
</View>
);
}
A component should not mutate it's own props. If your data changes during the lifetime of a component you need to use state.
Your inline arrow function sortCartItems tries to mutate the products that come from props. Your need to store the products in the components state instead and call setState to change them which will trigger a re-render.
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
products: props.products,
}
}
sortCartItems = () => {
this.setState(prevState => ({products: prevState.products.sort((a, b) => a.cases > b.cases);}))
}
render() {...}
}
Note that you need to use a callback in setState whenever you are updating the state based on the previous state. The callback receives the old state as a parameter and returns the new state.
I used a combination of messerbill's and trixn's answers to come up with the following which is now working. And I added a products property to state which receives its data from props.products
render() {
const cartIcon = (<Icon name="shopping-cart" style={styles.cartIcon} />);
sortCartItems = () => {
this.setState({
products: this.state.products.sort((a, b) => a.cases > b.cases)
});
}
return (
<View style={styles.itemsInCartContent}>
<View style={styles.cartHeader}>
<TouchableOpacity onPress={sortCartItems}>
{cartIcon}
<Text>Sort</Text>
</TouchableOpacity>
</View>
{this.state.products.map((product, index) =>
<CartProductItem
ref="childCartProductItem"
key={product.id}
product={product}
index={index}
activeIndex={this.state.active}
triggerParentUpdate={() => this.collapseAllOtherProducts}
/>
)}
</View>
);
}

Categories

Resources