Using FlatList when rendering anything more than a single "<Text>" causes error - react-native

I'm using FlatList as follows:
<FlatList
data={nodes}
renderItem={({ item }) => <Goal goal={item} />}
keyExtractor={item => item.id}
/>
I'm getting this error:
Error: Text strings must be rendered within a component.
when following component is passed to renderItem
const Goal = (props: { goal: GoalProps }) => {
const listTags = props.goal.tags.map(tag =>
<li key={tag.id}> {tag.name} </li>
);
return <>
<Text>goal title: {props.goal.title}</Text>
<ul>{listTags}</ul>
</>
}
When I simply return just one <Text> then it works without error.
This works:
const Goal = (props: { goal: GoalProps }) => {
const listTags = props.goal.tags.map(tag =>
<li key={tag.id}> {tag.name} </li>
);
return <Text>goal title: {props.goal.title}</Text>
}
How can I pass a more complex component to FlatList? Or maybe there is another list component I can use? I'm new to React and React Native.

Related

Navigate from a function call React native

Hi guys I am having trouble finding a solution on my problem because I want to navigate to another page but here's the problem
function NewOrderPopUp({id, services, name, rating, accepted, destinationPlaceName, userPlaceName, driverName, driverContactNumber, driverRating, driverTrackingNumber})
{
async function toggleAcceptBooking()
{
await firestore()
.collection('userBookingRequest')
.doc(id)
.update({
accepted : !accepted,
driverName: 'Sample Driver',
driverContactNumber: '09123456789',
driverRating: '4.9',
driverPlateNumber: 'NFT-2020',
driverTrackingNumber: GenerateTrackingNumber(),
})
CheckIfBookingAccepted();
}
}
return (
<View>....</View>
);
export default NewOrderPopUp;
And I am calling the NewOrderPopUp Page in another file.
like this
import NewOrderPopUp from "../../components/NewOrderPopUp";
const HomeScreen = () => {
//... codes here
return (
<View>
<FlatList
horizontal
contentContainerStyle={{paddingHorizontal: 30}}
data={userBookingData}
keyExtractor={(item) => item.id}
renderItem={({item}) => <NewOrderPopUp {...item}/>} />
</View>
);
}
export default HomeScreen;
What I wanted is that if I click the toggleAcceptBooking it will nagivate to another page like
navigation.navigate('BookingPage');
Can someone enlighten me please . Thank you.
Do it by passing navigation down as a prop.
do the following steps.
handle navigation prop in HomeScreen
const HomeScreen = ({ navigation }) => {
}
pass navigation as a prop to NewOrderPopUp
<FlatList
...
renderItem={({item}) => <NewOrderPopUp navigation={navigation} {...item}/>} />
handle navigation prop in NewOrderPopUp and use it to navigate.
function NewOrderPopUp( {navigation, ...} ){
async function toggleAcceptBooking(){
await ...
navigation.navigate('BookingPage');
}
}

React Native Chat App, Flatlist useRef is null

Im building a chat app with React Native using Expo and I use a Flatlist as child of KeyboardAvoidingView to render the list of messages, the problem is I want to scroll to the bottom when the keyboard is triggered
So I used the Flatlist method ( scrollToEnd ) with useRef hook and my code looks like this :
const ChatBody = ({ messages }) => {
const listRef = useRef(null);
useEffect(() => {
Keyboard.addListener("keyboardWillShow", () => {
setTimeout(() => listRef.current.scrollToEnd({ animated: true }), 100);
});
return () => Keyboard.removeListener("keyboardWillShow");
}, []);
return (
<FlatList
ref={listRef}
keyboardDismissMode="on-drag"
data={messages}
keyExtractor={(item) => item.id || String(Math.random())}
renderItem={({ item }) => <Message {...item} />}
/>
}
The code works just fine at the first render, but when I leave the screen and get back again and trigger the keyboard I get this error :
TypeError : null in not an object (evaluating 'listRef.current.scrollToEnd')
*The reason I added setTimout was because the scrollToEnd for some reason does not work when the keyboard event is triggered. adding setTimeout solved that issue.
The component tree is kinda like this :
StackNavigatorScreen => KeyboardAvoidingView => FlatList
You need to pass your event handler as a second parameter to Keyboard.removeListener. Since you're only passing in the first argument, your handler is run anyway and before your ref could be set.
const ChatBody = ({ messages }) => {
const listRef = useRef(null);
useEffect(() => {
Keyboard.addListener("keyboardWillShow", onKeyboardWillShow);
return () => Keyboard.removeListener("keyboardWillShow", onKeyboardWillShow);
}, []);
function onKeyboardWillShow() {
setTimeout(() => {
listRef.current.scrollToEnd();
}, 100);
}
return (
<FlatList
ref={listRef}
keyboardDismissMode="on-drag"
data={messages}
keyExtractor={(item) => item.id || String(Math.random())}
renderItem={({ item }) => <Message {...item} />}
/>
)
}
const listRef = useRef(null); <-- this line is the one causing the problem.
You need to assign an object, null in this case cannot be put there as it is not an object.

How can I render 2 items in a FlatList in React-native and Redux

I need to render two items "clientcalendarscreenred" and "nutritiondata" in a FlatList
PS: I am getting the two data "clientcalendarscreenred" and "nutritiondata" from a reducer through mapStateToProps
<FlatList
data={this.props.clientcalendarscreenred }
keyExtractor={this._keyExtractor}
renderItem={this._renderItem}
ItemSeparatorComponent={this._renderSeparator}
refreshing={this.state.refreshing}
onRefresh={this._onRefresh}
/>
===========
GETTING The DATA
===============
const mapStateToProps = ({clientcalendarscreenred, maincalendarreducer}) => {
const { client_id, workout_date, display_date } = maincalendarreducer;
return {
clientcalendarscreenred: clientcalendarscreenred.data,
nutritiondata: clientcalendarscreenred.nutrition,
};
};
You can use section list for this scenario.
You can also render the list heterogeneous or homogeneous i.e if you wish to render your sections differently
<SectionList renderSectionHeader={({section}) => this._renderHeader(section.data)}
sections={[
{
key: 'RedData',
data: clientcalendarscreenred.data,
renderItem: ({item}) => this.renderRedData(item)
},
{
key: 'NutritionData',
data: clientcalendarscreenred.nutrition,
renderItem: ({item}) => this.renderNutrition(item, index, section)
},
]}
keyExtractor={(item, index) => index}
/>
Something like below should work for you to print items from data prop in FlatList.Similarly you can pass nutritiondata to FlatList and use it to display.
const FlatList=(props)=>{
const {data, ...rest}=props;
const toRender= data&&data.length>0?data.map(item=><div>{item.something}</div>:<div/>
return toRender
}

Using function enum to render components in React-native app

I am following this link to render components using enum instead of several if-else conditions. My code looks like-
render(){
const _renderHelper = (scene, index) => ({
'ComponentType1': <Text>Test</Text>,
'ComponentType2': <Text>Test</Text>,
});
return (
<View>
{
this.props.scene.components.map((scene, sceneIndex) => {
_renderHelper(scene, sceneIndex)[scene.componentType]
})
}
</View>
)
But nothing renders in the app.

How to read props from a React Native touchable event currentTarget?

I have the following React Native code that runs the press() method when a user taps an image. I want to get the itemIndex prop from the event object. I set a break point in the press method and added some expressions to the Watch. From the Watch I determined that the target (event origination) from the event is the Image which is correct. The itemIndex prop is also available. The element being processed is the currentTarget, the Watch sees it's a "RCTView" and I was expecting a TouchableOpacity, so maybe underneath TouchableOpacity is a View? The currentTarget itemIndex prop is undefined, why? How can I get the props from the currentTarget?
I want to do it this way to avoid creating addition methods for each rendered item.
FYI,
ref={(c) => this._input = c} will not work because it's being run in a loop.
onPress={(e) => this.press(e, i)} creates a new function which I'm trying to avoid.
Watch
target._currentElement.props.itemIndex: 2
target._currentElement.type.displayName: "RCTImageView"
currentTarget._currentElement.props.itemIndex: undefined
currentTarget._currentElement.type.displayName: "RCTView"
press: function(event){
var currentTarget = ReactNativeComponentTree.getInstanceFromNode(event.currentTarget);
var target = ReactNativeComponentTree.getInstanceFromNode(event.target);
var currentTargetIndex = currentTarget._currentElement.props.itemIndex;
var targetIndex = target._currentElement.props.itemIndex;
var url = this.state.data.items[currentTargetIndex].url;
Linking.openURL(url).catch(err => console.error('An error occurred', err));
},
render: function() {
return (
<ScrollView horizontal={true} showsHorizontalScrollIndicator={false} style={styles.galleryView}>
{
this.state.data.items.map((data, i) =>
<TouchableOpacity itemIndex={i} key={i} activeOpacity={0.5} onPress={this.press} >
<Image itemIndex={i} key={i} source={{uri:data.previewImageUri}} style={styles.galleryImage} />
</TouchableOpacity>
)
}
</ScrollView>
);
}
I actually came across this same issue recently, I found two different ways you could approach this. The easier way of doing it is altering your onPress to pass an index to your press function, this is the 2nd way of doing it:
press: function(event, index){
var url = this.state.data.items[index].url;
Linking.openURL(url).catch(err => console.error('An error occurred', err));
},
render: function() {
return (
<ScrollView
horizontal={true}
showsHorizontalScrollIndicator={false}
style={styles.galleryView}
>
{
this.state.data.items.map((data, i) =>
<Images data={data} key={i} index={i} press={this.press} />
)
}
</ScrollView>
);
}
const Images = (props) => {
const imageClicked = (e) => {
props.press(e, props.index);
}
return (
<TouchableOpacity activeOpacity={0.5} onPress={imageClicked} >
<Image source={{uri:props.data.previewImageUri}} style={styles.galleryImage} />
</TouchableOpacity>
)
}
You could make your event handler a curried function that accepts extra parameters.
//Curried function for onPress event handler
handleOnPress = (someId, someProp) => event => {
//USE someProp ABOVE TO ACCESS PASSED PROP, WHICH WOULD BE undefined IN THIS CASE
//Use event parameter above to access event object if you need
console.log(someProp)
this.setState({
touchedId: someId
})
}
Checkout the working snack below
https://snack.expo.io/#prashand/accessing-props-from-react-native-touch-event
Binding the needed information to a callback and assigning one to each child avoids recreating the callback on every render of children.
class Hello extends React.Component{
state = { names: this.props.names.map((name, i) => {
return Object.assign({
onClick: this._onClick.bind(this, i, this.props),
}, name)
}),
};
_onClick(ind, _props, e) {
alert('props:' + JSON.stringify(_props));
}
render() {
const { names } = this.state;
return (
<div>
{ names.map((name, i) => (
<div key={i}>Name: <input value={ name.first } onClick={ name.onClick } /></div>
))}
</div>
)}
}
ReactDOM.render(
<Hello names={[{first:'aaa'},{first:'bbb'},{first:'ccc'}]}/>,
document.getElementById('container')
);
JS Fiddle