I have this object that I want to render when an error occurs while running a grapqhl query (on Apollo's onError):
export const ErrorContainer: React.FunctionComponent = () => {
console.log('container running')
return (
<View style={styles.errorView}>
<Text style={styles.errorText}>Unable to Load Friends</Text>
</View>
);
};
Now on my main screen, I tried this:
const { data, error } = useGetMyProfileQuery({
onCompleted: () => {
//setUserData(data)
},
onError: ErrorContainer
},
});
I also tried this:
{error && <ErrorContainer />}
return (
<SafeAreaView style={styles.safeView}>
<Container style={styles.container}>
<Text
style={styles.backText}
onPress={() => navigation.navigate('Home')}>
Zurück
</Text>
<View style={styles.listHolder}>
{data &&
<FlatList
data={data.me.friends}
horizontal={false}
renderItem={({ item }) => (
<Friend
friend={item}
//onDeleteFriend={onDeleteFriend}
originatorId={data.me.id}
/>
)}
keyExtractor={(item) => item.id.toString()}
ListEmptyComponent={NoFriendsContainer}
/>
}
{error && ErrorContainer}
</View>
</Container>
</SafeAreaView>
);
but although I see the console logs, i dont see the actual content of the ErrorContainer. How else should I call the component?
Passing component as a callback to the hook as here onError: ErrorContainer makes no sense. Despite, component is a function, passing it as a callback will not magically render it.
You have to render it in the template. Like you did above {error && <ErrorContainer />}
Just try to add additional state to your screen
const [isErrorVisible, setErrorVisible] = useState(false);
And then set it in the callback where you passed component before
const { data, error } = useGetMyProfileQuery({
onCompleted: () => {
//setUserData(data)
},
onError: () => {
setErrorVisible(true); // callback now instead of ErrorContainer
}
});
And then use it in your template
return (
<SafeAreaView style={styles.safeView}>
<Container style={styles.container}>
<Text
style={styles.backText}
onPress={() => navigation.navigate('Home')}>
Zurück
</Text>
<View style={styles.listHolder}>
{data &&
<FlatList
data={data.me.friends}
horizontal={false}
renderItem={({ item }) => (
<Friend
friend={item}
//onDeleteFriend={onDeleteFriend}
originatorId={data.me.id}
/>
)}
keyExtractor={(item) => item.id.toString()}
ListEmptyComponent={NoFriendsContainer}
/>
}
{/* here using the flag and rendering the component in template */}
{isErrorVisible && <ErrorContainer />}
</View>
</Container>
</SafeAreaView>
);
Related
Using react native with typescript and redux toolkit
Hi I'm bothering with render a list of messages via FlatList. By ScrollView everything rendering good but I need to implement infiniti scroll. So I'm doing something like this
const MessagesScreen = () => {
const companyId = useAppSelector(getCompanyId);
const userId = useAppSelector(getUserId);
const {
data: messages,
isLoading,
refetch
} = useGetMessagesQuery({ userId, companyId });
useFocusEffect(refetch);
return (
<FlatList
data={messages}
renderItem={() => {
<Messages messages={messages} />;
}}
/>
);
};
In return() I'm trying to render FlatList with component Messages which is down here:
const Messages = ({ messages }: { messages: Message[] }) => {
const navigation =
useNavigation<RootStackScreenProps<'DrawerNavigator'>['navigation']>();
const { colors } = useTheme();
return (
<View style={styles.container}>
{messages.map(message => {
const createdAt = message.created_at;
const isRead = message.read;
const icon = isRead ? 'email-open-outline' : 'email-outline';
const onClick = () => {
navigation.navigate('Message', {
messageId: message.id
});
};
return (
<TouchableOpacity key={message.id} onPress={onClick}>
<View
style={[styles.message, { borderBottomColor: colors.separator }]}
>
<View style={styles.iconPart}>
<Icon
name={icon}
type="material-community"
style={
isRead
? { color: colors.separator }
: { color: colors.inputFocus }
}
size={24}
></Icon>
</View>
<View style={styles.bodyPart}>
<Text
numberOfLines={1}
style={[isRead ? styles.readSubject : styles.unReadSubject]}
>
{message.subject}
</Text>
<Text
numberOfLines={1}
style={[isRead ? styles.readBody : styles.unReadBody]}
>
{message.body}
</Text>
</View>
<View style={styles.datePart}>
<Text style={{ color: colors.shadow }}>
{dayjs(createdAt).fromNow()}
</Text>
</View>
</View>
</TouchableOpacity>
);
})}
</View>
);
};
Actually behaviour is just rendering white screen with error
Possible Unhandled Promise Rejection (id: 17):
Error: Objects are not valid as a React child (found: object with keys {id, msg_type, created_at, subject, body, author, company_id, read}). If you meant to render a collection of children, use an array instead.
there is problem with your call back function:
you are not returning Messages component
1:Remove curly braces
return (
<FlatList
data={messages}
renderItem={() => <Messages messages={messages}/> }
/>
);
2:Add return statement
return (
<FlatList
data={messages}
renderItem={() => {
return <Messages messages={messages} />;
}}
/>
);
Couple things:
You're using the renderItem callback incorrectly:
<FlatList
data={messages}
renderItem={() => {
// ^ ignoring the renderItem props
return <Messages messages={messages} />;
}}
/>
Here, for each item in the messages array, you're rendering a component and passing all the messages into it. So you'll get repeated elements.
The renderItem callback is passed {item, index} where item is the CURRENT item in the array (index is the index into the array)
See docs here:
https://reactnative.dev/docs/flatlist
The usual thing is the renderItem callback renders ONE item at a time, like this:
<FlatList
data={messages}
renderItem={({item}) => {
return <Message message={item} />;
}}
/>
e.g. I'd make a <Message/> component that renders one item only.
I am generating a flatlist that contains comments. Inside the comment component, I'm using a state isCollpsed to determine if the individual comment is collapsed or not. Pressing on each individual comment does make it collapse. However, I want to manipulate this state from the parent component without affecting every other comment. How could I achieve this?
I tried using the reference hook to access each individual item in the flatlist but it keeps returning 'undefined'. I'm using the react-native-collapsible library to collapse the comments.
My Flatlist:
<FlatList
data={SAMPLE_COMMENTS}
keyExtractor={keyExtractor}
renderItem={({item})=>
<Comment
ref={(el) => {rowRefs.current[item.id] = el} }
onPress={()=>{rowRefs.current[item.id].collapseFunction()}}
body={item.body}
author={item.author}
level={item.level}
createdAt={item.createdAt}
commentId={item.id}
commentChildren={item.replies} />}
/>
Comment Component :
const [isCollapsed, setIsCollapsed] = useState(false);
const collapseFunction = () => {setIsCollapsed(!isCollapsed)};
return (
<Collapsible collapsed={isCollapsed}>
<TouchableWithoutFeedback onPress={onPress}>
<View style={styles.container}>
</View>
</TouchableWithoutFeedback>
</Collapsible>
you can use recursive function
// add this to parent
<MapComments
comments={SAMPLE_COMMENTS}
childClickHandler={onItemClickHandler}
/>
// MapComments component
const MapComments= ({
Comments= [],
childClickHandler,
}) => {
return (
<ScrollView>
<Tree
CommentTree={CommentTree}
childClickHandler={childClickHandler}
/>
</ScrollView>
);
};
const Tree = ({CommentTree= [], childClickHandler}) => {
return (
<View>
{CommentTree.map(tree => (
<TreeNode
key={tree.commentId}
node={tree}
childClickHandler={childClickHandler}
/>
))}
</View>
);
};
const TreeNode = ({node, childClickHandler}) => {
const [childVisible, setChildVisiblity] = useState(false);
const hasChild = node.commentChildren.length > 0 ? true : false;
return (
<View
style={{marginRight: node.Level > 1 ? 40 : null}}>
<TouchableOpacity
onPress={() =>
hasChild ? setChildVisiblity(prev => !prev) : childClickHandler(node)
}>
<Text numberOfLines={1} style={styles.label}>
{node.body}
</Text>
{hasChild ? (
<AntDesign name={childVisible ? 'minus' : 'plus'}
/>
) : (
<FontAwesome name="circle" />
)}
</TouchableOpacity>
{hasChild && childVisible && (
<Tree
childClickHandler={childClickHandler}
knowledgeTree={node.commentChildren}
/>
)}
</View>
);
};
Here I want to setName of parent component's state from child component while onCHangeText but it gives me "TypeError: setName is not a function. (In 'setName(text)', 'setName' is undefined)" error
Here is my Parent Component
const ProfileCreationScreen = () => {
const [name, setName] = useState()
const userSignup = async () => {
try {
firestore().collection('users').doc("androiduser_mobile89").set({
name: name,
});
alert("Succesfully Created")
} catch (error) {
alert(error);
}
};
return (
<SafeAreaView>
<Text>Create your account</Text>
<Button
mode="contained"
onPress={() => userSignup()}>
Sign Up
</Button>
<View style={{ display: "none" }}>
<NameScreen setName={setName} />
</View>
</SafeAreaView>
)
}
Here is my child component
export const NameScreen = ({ setName, navigation }) => {
return (
<KeyboardAvoidingView
enabled
behavior="padding"
style={styles.container}>
<View>
<Text style={styles.text}>Full Name</Text>
<TextInput
style={styles.textField}
label="Enter your Full Name"
underlineColor="#FF0074"
outlineColor="red"
value={text}
onChangeText={(text) => setName(text)}
/>
<Button mode="contained" style={styles.btn} onPress={() => alert(text)}>
Next
</Button>
</View>
</KeyboardAvoidingView>
);
I am using React Native FlatList and React Native Modal.
Upon clicking on the item from the FlatList, I want to view 1 Modal only (containing the details of the item selected).
However, if there are 4 items in the FlatList, selecting 1 item causes
all 4 modals to pop up.
Is there anyway I can display only 1 modal for 1 selected item in the FlatList instead of multiple modal?
Code Snippet below (some lines of code were removed as it's not needed):
constructor(props) {
super(props);
this.state = {
dataSource: [],
isLoading: true,
modalVisible: false,
}
}
setModalVisible = (visible) => {
this.setState({ modalVisible: visible });
}
viewModal(item, price) {
const { modalVisible } = this.state;
return (
<Modal
statusBarTranslucent={true}
animationType={"slide"}
transparent={true}
visible={modalVisible}
onRequestClose={() => {
Alert.alert("Modal has been closed.");
}}
>
<View>
<View>
<View>
<Text>
Appointment Start Time:
</Text>
<Text>
{moment(item.time_start).format('h:mm a')}
</Text>
</View>
<View>
<Text>
Appointment End Time:
</Text>
<Text>
{moment(item.end_start).format('h:mm a')}
</Text>
</View>
<View style={styles.row}>
<Text>
Total:
</Text>
<Text>
{price}
</Text>
</View>
<View>
<View>
<Button
mode="outlined"
onPress={() => {
this.setModalVisible(!modalVisible);
}}
>
{'Cancel'}
</Button>
</View>
<View>
<Button
mode="contained"
onPress={() => {
this.setModalVisible(!modalVisible);
}}
>
{'Accept'}
</Button>
</View>
</View>
</View>
</View>
</Modal>
);
}
viewFreelancerTime() {
return (
<View>
<FlatList
renderItem={({ item }) => {
let totalPrice = (parseFloat(item.service_price) + parseFloat(item.service_deposit)).toFixed(2);
return (
<Container>
{this.viewModal(item, totalPrice)}
<TouchableNativeFeedback
onPress={() => {
this.setModalVisible(true);
}}
>
<View>
<View>
<Text>
{moment(item.time_start).format('h:mm a')}
</Text>
</View>
<View>
<Text>
{totalPrice}
</Text>
</View>
</View>
</TouchableNativeFeedback>
</Container>
);
}}
/>
</View>
);
}
render() {
return (
<>
<View style={{ flex: 1 }}>
{this.viewFreelancerTime()}
</View>
</>
);
};
The poblem is that you are rendering the modal in the renderItem method, so every time you select an item, the modal will open in each rendered item.
To solve that you will have to render a custom Modal component with an absolute position at the same level of your FlatList, and pass the selected item information as props.
UPDATE
Just something like this:
import React, {useState} from "react";
import { Modal } from "react-native";
export default function MyFlatList(props) {
const [selectedItem, setSelectedItem] = useState(null);
const handleOnSelectItem = (item) => {
setSelectedItem(item);
};
const handleOnCloseModal = () => {
setSelectedItem(null);
};
renderItem = ({ item }) => {
return (
<Container>
<TouchableNativeFeedback onPress={() => handleOnSelectItem(item)}>
<View>
<View>
<Text>{moment(item.time_start).format("h:mm a")}</Text>
</View>
<View>
<Text>{totalPrice}</Text>
</View>
</View>
</TouchableNativeFeedback>
</Container>
);
};
return (
<View>
<FlatList renderItem={this.renderItem} />
<CustomModal isVisible={selectedItem} selectedItem={selectedItem} onClose={handleOnCloseModal} />
</View>
);
}
export function CustomModal(props) {
const { isVisible, item, onClose, /*...*/ } = props;
// Play with the item data
let totalPrice = (
parseFloat(item.servicePrice) + parseFloat(item.serviceDeposit)
).toFixed(2);
return <Modal visible={isVisible} onRequestClose={onClose}>{/*...*/}</Modal>; // Render things inside the data
}
I suggest you to do a pagination and play with FlatList native props if you are going to implement an infinite scroll.
Pd: to reduce re-renders because of state updates, I am reusing the selectedItem state, so if it is not null then the modal will be visible
i'd radio button component :
state = {
radioValue: null,
}
render() {
const { options,onPress } = this.props;
const { radioValue } = this.state;
return (
<View style={{flexDirection: 'row'}}>
{options.map(item => {
return (
<View key={item.key} style={styles.buttonContainer}>
<TouchableOpacity
style={styles.circle}
onPress={() => {
this.setState({
radioValue: item.key,
});
}}
>
{radioValue === item.key && <View style={styles.checkedCircle} />}
</TouchableOpacity>
<Text>{item.text}</Text>
</View>
);
})}
</View>
);
then i use this component in an another like this :
<RadioButton options={options} />
How can i use the value of my state in the second component ??
Thx !!
The best way would be to move the state from RadioButton to the outer component:
// RadioButton Component
render() {
const { options, onPress, value } = this.props;
return (
<View style={{flexDirection: 'row'}}>
{options.map(item => {
return (
<View key={item.key} style={styles.buttonContainer}>
<TouchableOpacity
style={styles.circle}
onPress={() => onPress(item.key)} // onPress comes from parent
>
{value === item.key && <View style={styles.checkedCircle} />}
</TouchableOpacity>
<Text>{item.text}</Text>
</View>
);
})}
</View>
);
// Parent component
state = {
radioValue: null,
}
onPress = (value) => {
this.setState({
radioValue: value,
});
}
render() {
// ...
<RadioButton
options={options}
onPresss={this.onPress}
value={radioValue}
/>
// ...
}
If you can't do that, another approach would be using render props. This would let you keep the state in the child, and pass it to the parent. Not recommended, though, as it is more confusing.