i want to make my flatlist reusable but i am facing some difficulties while passing props.
code for the reusable component
const ListItemView = function (props) {
console.log(props);
return (
<View>
<FlatList
//data={props.data}
keyExtractor={props.keyp}
renderItem={props.disptext}
/>
</View>
);
};
when i run console.log on the props i get this
{"disptext": undefined, "keyp": [Function anonymous]}
this is how i am passing props from the parent screen
const keyf = () => {
console.log('keyf');
//for the key extractor
return (item => item.index);
};
const rendertext = () => {
console.log('rendertext');
//for rerender function of the flatlist
({ item }) => {
return (
<View>
<Text>holaa</Text>
<Text>{item.name}</Text>
</View>
);
}
};
return (
<View style={style.container}>
<ListItemView
//data={con}
keyp={keyf()}
disptext={rendertext()}
/>
</View>
);
};
please help
You are directly calling the function but you just need to give a reference of it...so that it is called only on some event.
It should be like this:
<ListItemView
//data={con}
keyp={keyf}
disptext={rendertext}
/>
or
<ListItemView
//data={con}
keyp={()=>keyf()}
disptext={()=>rendertext()}
/>
Related
So I have created a state like :
const [inState, setInState] = useState([<View />]);
Then on click of some buttons, I am updating inState
const breakOutClick = () => {
setInState([
...inState,
<>
<StatusBoxComponent
ImageConfigIconCheckOut={true}
status={'Break-Out'}
time={time}
/>
</>,
]);
};
const breakInClick = () => {
setInState([
...inState,
<>
<StatusBoxComponent
ImageConfigIconCheckOut={true}
status={'Break-In'}
time={time}
/>
</>,
]);
};
I am able to display everything stored in inState, on this same screen in this manner:
<View>
{inState}
</View>
I am passing this inState to another screen in the following manner:
props.navigation.navigate(ChartScreen, {
inState: Object.assign({}, inState),
});
Then on this second screen, i.e, ChartSCreen, I did the following:
const ChartScreen = (props: any) => {
const {inState} = props.route.params;
useEffect(() => {
console.log('>>>>>>>>>', inState);
}, []);
return (
<View>
{inState} // getting error here
</View>
);
};
I have console the inState, which looks like this:
{
"0": <ForwardRef />,
"1": <React.Fragment>
<StatusBoxComponent
ImageConfigIconCheckOut={true}
status="Break-In"
time="17:51:40"
/>
</React.Fragment>,
"2": <React.Fragment>
<StatusBoxComponent
ImageConfigIconCheckOut={true}
status="Break-Out"
time="17:51:42"
/>
</React.Fragment>
}
How can I display the multiple StatusBoxComponent on my second screen?
You are displaying initially an array, but by calling Object.assign({}, inState) you're creating an object. Where you're getting the error, you're attempting to render that object, not an array of components. Try using Object.values to get only the values and virtually "restore" your initial array.
const ChartScreen = (props: any) => {
const {inState} = props.route.params;
useEffect(() => {
console.log('>>>>>>>>>', inState);
}, []);
return (
<View>
{Object.values(inState)} // getting error here
</View>
);
};
What I want
I'm trying to use a FlatList to render an array of objects that comes from server(I'm using Apollo and Graphql to get data from server), but I'm getting a message from console:
VirtualizedList: You have a large list that is slow to update - make sure your renderItem function renders components that follow React performance best practices like PureComponent, shouldComponentUpdate, etc. {"contentLength": 2392, "dt": 4527, "prevDt": 2777}
I followed the message and wrapped my renderItem in React.memo, I'm using functional components so I cant use PureComponent or shouldComponentUpdate, but after this the problem persist.
What I have
Component structure
HomeScreen (responsible for obtaining the data from the server)
└── ProductList (FlatList Wrapper)
└── ProductListItem (renderItem)
HomeScreen.js
// HomeScreen.js
function HomeScreen({navigation}) {
const {data} = useHomeScreenProductsQuery({}) // this hook get data from server with apollo useQuery method
// just print something on console but it can do something more complex
const handleOnPressItem = () => console.log('go to product')
return (
<ProductList
horizontal
numColumns={2}
products={data?.collection?.products?.edges.map(e => e.node) ?? []}
onPressItem={handleOnPressItem}
/>
)
}
ProductList.js
// ProductList.js
import React from 'react'
import {Dimensions, FlatList, View} from 'react-native'
import {ProductListItem} from '../ProductListItem'
function ProductList({
products,
horizontal = false,
numColumns = 1,
onPressItem = () => {},
}) {
const getLayoutManager = () => {
const layoutManager = {}
if (horizontal) {
layoutManager.horizontal = true
layoutManager.showsHorizontalScrollIndicator = false
layoutManager.ItemSeparatorComponent = ProductListItemSeparator
layoutManager.contentContainerStyle = {padding: spacing.base}
} else {
layoutManager.numColumns = numColumns
}
return layoutManager
}
const renderProductListItem = ({item}) => {
const columns = horizontal ? 2 : numColumns
return (
<ProductListItem
product={item}
itemContainerStyle={{
width: dimens.common.WINDOW_WIDTH / columns,
}}
columnWrapperStyle={{flexDirection: 'column'}}
onPressItem={onPressItem}
/>
)
}
return (
<FlatList
{...getLayoutManager()}
// Pass a changing value to the `key` prop to update the columns number on the fly
key={numColumns}
data={products}
renderItem={renderProductListItem}
/>
)
}
ProductListItem.js
// ProductListItem.js
function ProductListItem({
product,
itemContainerStyle = {},
columnWrapperStyle = {},
onPressItem = () => {},
}) {
const price = product.pricing?.priceRange?.start
const priceUndiscounted = product.pricing?.priceRangeUndiscounted?.start
const getProductPrice = () => {
if (equal(price, priceUndiscounted)) {
// render a price formated with $ symbol
return <TaxedMoney taxedMoney={price} defaultValue={0} category="h6" />
}
return (
<>
<View style={styles.priceContainer}>
<Text category="c2" appearance="hint">
Antes
</Text>
<TaxedMoney
taxedMoney={priceUndiscounted}
category="c2"
appearance="hint"
style={styles.priceUndiscounted}
/>
</View>
<TaxedMoney taxedMoney={price} category="h5" />
</>
)
}
const renderItemHeader = () => (
<View style={styles.itemHeader}>
// this is only a wrapper of react-native-fast-image library
<Thumbnail source={product} />
</View>
)
return (
<View style={itemContainerStyle}>
// component from #UI-kitten components library
<Card
onPress={onPressItem}
header={renderItemHeader}
style={[styles.row, columnWrapperStyle]}
>
{/*Card Body*/}
<Text
category="c1"
appearance="hint"
ellipsizeMode="tail"
numberOfLines={1}
>
{product.category?.name}
</Text>
<Text category="c1" ellipsizeMode="tail" numberOfLines={2}>
{product.name}
</Text>
{getProductPrice}
</Card>
</View>
)
}
export default React.memo(ProductListItem)
I have a PaymentMethodsScreen screen. On this screen there is a FlatList with PaymentCardItem components inside. And there is a checkbox inside the PaymentCardItem. When this checkbox checked I would like to update selectedCardToken state of PaymentMethodsScreen. But unfortunately I couldn't figure out how to do it. I tried to pass props but I was doing it wrong. Here is my code (without passing props).
How can I achieve that? Thank you very much for your helps.
const PaymentCardItem = ({ family, association, bin_number, token, isSelected }) => (
<View>
<RadioCheckbox
selected={ isSelected }
onPress={ () => this.setSelectedCardToken(token) // Something wrong here }
/>
<Text>{family}, {association}</Text>
<Text>{bin_number}**********</Text>
</View>
);
const PaymentMethodsScreen = ({navigation}) => {
const {state} = useContext(AuthContext);
const [cardList, setCardList] = useState(null) // This stores card list data from API request
const [selectedCardToken, setSelectedCardToken] = useState('test token')
const renderItem = ({ item }) => (
<PaymentCardItem
bin_number={item.bin_number}
family={item.family}
association={item.association}
token={ item.token }
isSelected={ (selectedCardToken == item.token) }
/>
);
return (
<SafeAreaView>
<View>
<FlatList
data={cardList}
renderItem={renderItem}
keyExtractor={item => item.alias}
/>
</View>
</SafeAreaView>
);
};
add onPress prop to PaymentCardItem:
// PaymentMethodsScreen
<PaymentCardItem
onPress={() => setSelectedCardToken(item.token)}
>
I don't know how the PaymentCardItem component is structured, but generally you should add onPress prop on the TouchableOpacity in the component or whatever is your onPress handler:
// PaymentCardItem component
<TouchableOpacity
onPress={() => props.onPress()}
>
You can pass down the handler function which gets called on checkbox being checked or unchecked to your PaymentCardItem component.
You can also pass setSelectedCardToken directly, but in case you have some extra logic before you update state, it's better to have a handler for more readability.
So, the code will be like below.
const PaymentMethodsScreen = ({ navigation }) => {
const { state } = useContext(AuthContext);
const [cardList, setCardList] = useState(null) // This stores card list data from API request
const [selectedCardToken, setSelectedCardToken] = useState('test token')
const handleCardTokenSelection = (isTokenSelected) => {
if(isTokenSelected) {
setSelectedCardToken(); // whatever logic you have
} else {
setSelectedCardToken(); // whatever logic you have
}
}
const renderItem = ({ item }) => (
<PaymentCardItem
bin_number={item.bin_number}
family={item.family}
association={item.association}
token={ item.token }
isSelected={ (selectedCardToken == item.token) }
handleCardTokenSelection={handleCardTokenSelection}
/>
);
return (
<SafeAreaView>
<View>
<FlatList
data={cardList}
renderItem={renderItem}
keyExtractor={item => item.alias}
/>
</View>
</SafeAreaView>
);
};
const PaymentCardItem = ({ family, association, bin_number, token, isSelected, handleCardTokenSelection }) => (
<View>
<RadioCheckbox
selected={ isSelected }
onPress={handleCardTokenSelection}
/>
<Text>{family}, {association}</Text>
<Text>{bin_number}**********</Text>
</View>
);
You need to set the state for PaymentCardItem not for the whole Flatlist, to show the item is selected.
I think you update the PaymentCardItem component to something like the below code(You can update the logic as per requirement)
class PaymentCardItem extends React.Component {
constructor(props) {
super(props);
this.state = {selectedCardToken: "", isSelected: false};
}
setSelectedCardToken=(token)=>{
if(selectedCardToken == token){
this.setState({
selectedCardToken: token,
isSelected: true
})
}
}
render() {
const { family, association, bin_number, token }=this.props;
const { isSelected } = this.state;
return (
<View>
<RadioCheckbox
selected={ isSelected }
onPress={ () => this.setSelectedCardToken(token)
/>
<Text>{family}, {association}</Text>
<Text>{bin_number}**********</Text>
</View>
);
}
}
I am building a react native contact app that generates a random color for every contact number. I want to memoize the returned value of the random color generator function on every iteration so when I am searching the contacts, the color won't be changing on every action.
currently, I am using react's useMemo to memoize the function result, but I am getting an invalid hook because I can not use hooks inside a normal function with a loop.
As my code below the useMemo hook is called only on the first render or when the component re-renders. I am only able to return one value because I can't use useMemo inside the map.
here is my code ⬇. Thanks for helping
const Contacts = () => {
const [contact, setContact] = useState({});
const randomColor = useMemo(() => randomColorGenerator(), []);
useEffect(() => {}, []);
const renderContacts = item => {
return item.phoneNumbers.map(element => (
<TouchableOpacity
activeOpacity={1}
key={element.digits.toString()}
}}
>
<View>
<View>
<Text>{item.firstName}</Text>
<Text>{element.digits}</Text>
</View>
</View>
</TouchableOpacity>
));
};
const renderList = () => {
return (
<FlatList
keyboardShouldPersistTaps="handled"
data={contact}
keyExtractor={(item, index) => index.toString()}
renderItem={({ item }) => {
return <View>{renderContacts(item)}</View>;
}}
/>
);
};
return (
<View>
<Text style={Styles.textStyle}>All Contacts</Text>
{renderList()}
</View>
);
};
Make the function - a React functional component. Then you'll be able to use React.useMemo inside it. Also, this answer and this answer might help clarify better
// define it as a functional component
const RenderContacts = ({ item }) => {
// useMemo inside that
const randomColor = useMemo(() => randomColorGenerator(), []);
// TODO: use randomColor somewhere
return item.phoneNumbers.map(element => (
<TouchableOpacity ...>
...
</TouchableOpacity>
));
)
}
const Contacts = () => {
...
// extract this out into its own component
// const renderContacts = item => {
// return item.phoneNumbers.map(element => (
// <TouchableOpacity
// activeOpacity={1}
// key={element.digits.toString()}
// }}
// >
// <View>
// <View>
// <Text>{item.firstName}</Text>
// <Text>{element.digits}</Text>
// </View>
// </View>
// </TouchableOpacity>
// ));
// };
const renderList = () => {
return (
<FlatList
...
renderItem={({ item }) => {
// return <View>{renderContacts(item)}</View>;
// render the component, don't call it as a function
return <View><RenderContacts item={item} /></View>;
}}
/>
);
};
return (
<View>
<Text style={Styles.textStyle}>All Contacts</Text>
{renderList()}
</View>
);
};
I would just suggest creating your own pure-javascript wrapper around randomColorGenerator that caches values based on a key to give a consistent result. Something like:
const memoizedRandomColorGenerator = function() {
const cache = {};
return function(k) {
if (typeof cache[k] !== 'undefined') {
return cache[k];
}
cache[k] = randomColorGenerator();
return cache[k];
}
}() // note that we are immediately invoking this function to close over the cache
Define that somewhere outside of your component so it is only created once.
Then use it in component
const renderContacts = item => {
return item.phoneNumbers.map(element => (
<TouchableOpacity
activeOpacity={1}
key={element.digits.toString()}
}}
>
<View style={{
backgroundColor: memoizedRandomColorGenerator(element.digits.toString())
}}>
<View>
<Text>{item.firstName}</Text>
<Text>{element.digits}</Text>
</View>
</View>
</TouchableOpacity>
));
};
Color can be either property of the contact, that can be set earlier and stored inside the contact.
Or color can be function of the ${contact.firstName} ${contact.lastName}.
You can get hexadecimal hash from the string, then color from hash.
This way you get persistent assignment between contacts and colors.
useMemo is intended for heavy calculations, that returns the same result, not random.
React Native Expo App:
In render of my default class I map from an array that calls an arrow function outside of the class to return a line of text for each object. I would like to pass the navigation prop through so that on press of that text will navigate to another screen.
My navigation is set up correct as it is called from a touchable opacity elsewhere in render to navigate to another screen.
I have stripped this back quite a bit but hopefully enough to explain:
const CustomDialogContent = ({name, ip, navigate}) => {
return (
<Text onPress={navigate('Webview')}>
{name} ({ip})
</Text>
)
}
export default class HomeScreen extends React.Component {
constructor(props) {
super(props);
this.state ={
devices: [],
name: '',
ip: '',
};
}
static navigationOptions = {
title: 'App',
};
render() {
const {navigate} = this.props.navigation;
return (
<View style={container}>
<Dialog
<DialogContent>
{this.state.devices.map((device) => {
return (
<CustomDialogContent name={device.name} ip={device.ip} navigation={navigate}/>
);}
</DialogContent>
</Dialog>
</View>
);
}
}
Instead of taking me to the screen I get navigate is undefined. I have also tried this which threw undefined is not an object:
const CustomDialogContent = ({name, ip}) => {
const {navigate} = this.props.navigation;
return (
<Text onPress={navigate('Webview')}>
{name} ({ip})
</Text>
)
}
I then tried this which returned navigate is not a function:
const CustomDialogContent = ({name, ip, navigate}) => {
return (
<Text onPress={() => {navigate('Webview')}}>
{name} ({ip})
</Text>
)
}
Apologies if I am missing something quite basic I am a junior and completely new to react
Final solution with credit to #LonelyCpp:
const CustomDialogContent = props => {
return (
<Text onPress={() => {props.navigation('Webview');}}>
{props.name} ({props.ip})
</Text>
);
};
<CustomDialogContent name={device.name} ip={device.ip} navigation={navigate}/>
props are like variable assignments. You've done navigation={navigate} which means in CustomDialogContent gets the function as props.navigation
this should work -
const CustomDialogContent = (props) => {
const navigate = props.navigation; // no {}
return (
<Text onPress={()=>navigate('Webview')}>
{name} ({ip})
</Text>
)
}
edit : removed this since its a functional component