React navigation 5.0 undefined params - react-native

Passing data from one screen to another screen
<TouchableOpacity
onPress={() =>
this.props.navigation.navigate("CourseListBySubject", {
cateoryId: subject.id,
})
}
>
Tried to get param
render() {
//const { categoryId } = this.props.route.params;
console.log(this.props.route);
return <Text>Test</Text>;
}
Param is undefined in console output
Object {
"key": "CourseListBySubject-l0ynFMFcdC5uToYiupsGr",
"name": "CourseListBySubject",
"params": undefined,
}
Can anyone help to get param data in second screen?

Related

How to pass data back to previous screen with React Navigation 6. React Native

I am trying to pass the selected data i'm fetching from my rest api back to the previous screen. I followed the docs, but it's not working. Not sure what I am doing wrong.
I am updating the state w/ the selected data, and know it's working bc i am printing it out in console successfully.
This is the button that calls the function to navigate back, passing the state w/ selected data in Screen B:
<Pressable onPress={() => postSelected(selectedData)} >
<Text style={{fontSize: 13, color: 'white', fontWeight: '700', paddingRight: 5}}>{'Select'}</Text>
</Pressable>
This is the function (Screen B):
const postSelected = (selectedData) => {
navigation.navigate({
name: 'CreatePost',
params: { postData: selectedData },
merge: true
});
}
In Screen A, I have a useEffect that listens for the selected data from Screen B:
useEffect(() => {
if (route.params?.postData) {
console.log('Sent');
}
}, [route.params?.postData]);
But it's not receiving the data.
I was following these docs: https://reactnavigation.org/docs/params/
Appreciate any help!
You can use props.navigation.getParam("postData") Method to get params from navigation
The solution that worked for me and follows the best practices set by react-navigation is as follows
const MainScreen = ({ navigation }) => {
// Here is where the returned data is detected
React.useEffect(() => {
if (route.params?.post) {
// Do something with the data
}
}, [route.params?.post])
const openChildScreen = () => {
navigation.navigate('NewPostScreen')
}
}
// ---------------------
const NewPostScreen = ({ navigation }) => {
const goBackToMain = () => {
// This is the data to be returned to the main screen
const post = {
// ...
}
navigation.navigate('MainScreen', { post });
}
}

How to pass data which is not relevant to render through Flatlist?

I create Flatlist as below.
export default function Comments({ route }) {
const renderComment = ({ item: comments }) => {
return <CommentRow {...comments} />;
};
return (
<Container>
<CommentBox>
<FlatList
keyboardDismissMode={true}
showsVerticalScrollIndicator={false}
data={route.params.comments}
keyExtractor={(comment) => "" + comment.id}
renderItem={renderComment}
/>
</CommentBox>
</Container>
);
}
rendered data set seems like below:
Object {
"key": "Comments-uxnCZKk0KvTmpppxkM8IF",
"name": "Comments",
"params": Object {
"comments": Array [
Object {
"__typename": "Comment",
"createdAt": "1642680008811",
"id": 1,
"isMine": false,
"payload": "등산 너무 좋았겠네~~",
"user": Object {
"__typename": "User",
"avatar": "https://chungchunonuploads.s3.ap-northeast-2.amazonaws.com/avatars/4-1642681358399-%E1%84%80%E1%85%B5%E1%86%B7%E1%84%8C%E1%85%A5%E1%86%BC%E1%84%92%E1%85%A9.png",
"username": "김정호",
},
},
Object {
"__typename": "Comment",
"createdAt": "1642680037676",
"id": 3,
"isMine": false,
"payload": "다음주에 그 산으로 가자 우리도~~",
"user": Object {
"__typename": "User",
"avatar": "https://chungchunonuploads.s3.ap-northeast-2.amazonaws.com/avatars/4-1642681358399-%E1%84%80%E1%85%B5%E1%86%B7%E1%84%8C%E1%85%A5%E1%86%BC%E1%84%92%E1%85%A9.png",
"username": "김정호",
},
},
],
"photoId": 1,
},
"path": undefined,
}
So It successfully works.
I return it to <CommentRow> Screen with this data set.
But I need to pass another data which is photoId.
this photoId exists in this screen but not in CommentRow Screen.
And comments data set doesn't have this photoId information. So I need separately send it to CommentRow Screen with {...comments} data.
But How can I send it through Flatlist?
I tried to make another object as combining comments object with photoId. But It failed :(
or Should I just bring photoId information in CommentRow screen as not passing through Flatlist?
If you need more explanation about my code, I can answer on real time.
please help me.
CommentRow Screen.
PhotoId is used in cache.modify part since it is needed to update my cache.
export default function CommentRow({
id,
user,
payload,
isMine,
createdAt,
updatedAt,
commentNumber,
}) {
const navigation = useNavigation();
const createdDate = new Date(+createdAt);
const date = createdDate.toISOString().substring(0, 10);
const updateDeleteComment = (cache, result) => {
const {
data: {
deleteComment: { ok, error },
},
} = result;
if (ok) {
cache.evict({ id: `Comment:${id}` });
cache.modify({
id: `Photo:${photoId}`,
fields: {
comments(prev) {
return [...prev, newCacheComment];
},
comments(prev) {
return prev +1;
}
}
})
}
};
const [deleteCommentMutation] = useMutation(DELETE_COMMENT_MUTATION, {
variables: {
id,
},
update: updateDeleteComment,
});
const onDeleteClick = () => {
deleteCommentMutation();
};
return (
<Container>
<CommentContainer>
<UserImage source={{ uri: user.avatar }} />
<TextBox>
<Header>
<Username>{user.username}</Username>
<CreatedTime>{date}</CreatedTime>
</Header>
<CommentText>{payload}</CommentText>
</TextBox>
</CommentContainer>
{isMine ? (
<WriteContainer>
<TouchableOpacity>
<WriteComment>수정</WriteComment>
</TouchableOpacity>
<TouchableOpacity onPress={onDeleteClick}>
<WriteComment>삭제</WriteComment>
</TouchableOpacity>
</WriteContainer>
) : null}
</Container>
);
}
Change renderComment as
const renderComment = ({ item: comments }) => {
const photoIdobj = { photoId: `${route.params.photoId}` };
const passedComments = { ...comments, ...photoIdobj };
return <CommentRow {...passedComments} />;
};
and call photoId from CommentRow like
export default function CommentRow({
id,
user,
payload,
isMine,
createdAt,
updatedAt,
commentNumber,
photoId
})
I got an idea from #Thanhal's answer.
I made the new object as combining comments and photoId as below.
then it works.
const renderComment = ({ item: comments }) => {
const photoIdobj = { photoId: `${route.params.photoId}` };
const passedComments = { ...comments, ...photoIdobj };
return <CommentRow {...passedComments} />;
};
return (

How to display the nested array data in react native from API?

I am trying to fetch and display the data from API. Below response i am getting from the API. I want to show the value of "Name" from details array. Kindly help me to resolve this. i have tried below code
{
"Success":1,
"data":[
{
"Date":"2019-11-08",
"Details":[
{
"Name":"Name 1",
"Id":72
},
{
"Name":"Name 2",
"Id":73
}
]
},
{
"Date":"2019-11-09",
"Details":[
{
"Name":"Name 3",
"Id":72
},
{
"Name":"Name 4",
"Id":73
}
]
}
]
}
Javascript map function can be used for nested iteration.
Consider your data is stored in state as data.
If response is received in a variable named response you can set state as
this.setState({ data : response.data })
Then you can use the below code snippet to iterate through nested object values
{
this.state.data.map((dat, index) => { //Iterate through your data
return (
<View style={styles.selectedCh} key={"outer-" + index}>
<Text>{dat.Date}</Text>
{
dat.Details.map((inner, indexInner) => { //Iterate through inner Details
return (
<View style={{ flex: 1 }} key={"inner-" + indexInner} >
<Text>{inner.Id}</Text>
<Text>{inner.Name}</Text>
</View>
)
})
}
</View>
)
})
}

How to add data to redux store just after navigate to component but before it rendered?

I have nested catalog of goods. It has categories which leads you to another categories and categories which leads you to page with set of goods of that "final" category.
I don't know how it is usually done and I decided to do the following. When user clicks that final category (which follows to a set of goods), I navigate him to component . It shows a list of goods for any category id. This component connected to redux store. It gets all the data only from redux store. I have a separate reducer for goods. This reducers' initial state is null when you start app the first time. I don't want to preload the whole the catalog to store, I want to do it partially. After category with goods clicked, user redirects to GoodsList component with category ID param. Then GoodsList downloads from remote server list of goods for this particular category (if it is not in store yet, besides all cards of goods from this category will be displayed based on downloaded data) and as component is connected to store (and data there is updated) component rerendered and the list of goods is shown.
The problem is that the very first first time component is rendered the set of goods is empty, then in its constructor the data is updated -> component rerendered and only then I get data set I can show on screen. How can I populate redux store (goods) partially when user clicks particular category but before component, that displays goods, is mounted?
I can show spinner while the datais loading, but why do I need component to be rendered twice.
Or... any other ideas?
Update.
If the question above is hard to understand, see info below.
CatalogComponent.js
/* necessary imports */
import categories from 'appcontent/categories'; // initial categories
const CatalogComponent = () => {
return (
<ScrollView>
<View style={styles.container}>
// If there is no passed categories for current component, using base set of categories (the whole catalog).
// ReactNavigation is used. Navigation prop is from there.
<CatalogList categories={!!props.navigation.state.params ? props.navigation.state.params.categories : categories}/>
</View>
</ScrollView>
)
}
categories.js
const goods = [
{
"id": 1,
"title": "First cat",
"subs": [
{
"id": 11,
"title": "First subcat",
"subs": [
{
"id": 111,
"title": "first final cat",
"subs": null
},
{
"id": 112,
"title": "first final cat",
"subs": null
},
],
},
],
},
{
"id": 2,
"title": "Second cat",
"subs": null
},
]
export default goods;
CatalogList.js
const CatalogList = (props) => {
const { categories } = props;
return (
<View>
{
// Render each element of current catalog level and passin
categories.map((item) => {
return <CatalogItem key={item.title} categories={item} />
})
}
</View>
);
}
CatalogItem.js
const onPress = (item, navigation) => {
navigation.navigate({
routeName: 'CatalogComponent',
params: { categories: item.subs, title: item.title }, // this categories is props.navigation.state.params.categories in CatalogComponent
key: Math.random() * 10000 // needed for I could open the same Route with new paarmeters
})
};
const CatalogItem = (props) => {
const { id, title, subs } = props.categories;
return (
<TouchableOpacity
onPress={() => {
if (props.categories.subs !== null && props.categories.subs.length > 0) {
// Redirect to the very first route (component) CatalogComponent with new categories parameter (child catalog list for current category)
onPress(props.categories, props.navigation); // props.navigation is for redirect purposes using ReactNavigation
} else {
// Show the list of goods for category of ID equals id
// THE MAIN PART OF QUESTION STARTS FROM HERE
props.navigation.navigate({
routeName: 'GoodsList',
params: { categoryId: 123 },
})
}
//props.categories.subs !== null && props.categories.subs.length>0 && onPress(props.categories, props.navigation)
}}
>
<View style={styles.container}>
<Text style={styles.text}>{title}</Text>
<Icon style={styles.arrow} name="chevron-right" size={20} color="gray" />
</View>
</TouchableOpacity>
);
}
GoodsList.js
/* Necesary imports */
import goodsList from 'appcontent/goods';
class GoodsList extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: true
}
}
componentWillUnmount() {
clearTimeout();
}
componentWillMount() {
// Setting of category to state
this.catId = this.props.navigation.state.params.categoryId;
// Imitate fetching data from remote server by setting timeout
setTimeout(() => {
// "Fetching" data
this.goods = goodsList[this.catId];
// The first thing I do is save data to Redux store. Render method below gets data from Redux.
this.props.dispatch(goodsFill({ goods: this.goods, catId: this.catId })); // the reducer code is below
this.setState((curState) => {
return {
...curState,
loading: 0
}
});
}, 5000);
}
render() {
const { goods } = this.props;
console.log('settingGoodsList', goods.goods); // !!!
return (
<View style={styles.container}>
{
this.state.loading && <Text>Loading</Text>
}
{
goods.goods[this.catId] && goods.goods[this.catId].map((item, index) => {
console.log(item);
return <Text key={item[Object.keys(item)[0]].name}>{item[Object.keys(item)[0]].name}</Text>
})
}
</View>
);
}
}
Reducer
const initReducerDefaultState = {
goods: {}
};
export default (state = initReducerDefaultState, action) => {
switch (action.type) {
case 'GOODS_FILL':
return {
...state, goods: { ...state.goods, [action.catId]: action.goods }
}
default:
return state;
}
}
Order of actions. You enter catalog page CatalogComponent. The very first time you see the whole catalog (its top categories).
After you clicks some of items you get to the next level with its subcategories, etc. Technically you are redirecting to the same route, but this time
with categories that passed from previous. If the category is final (it has no subs - subcategories, it should
follow you to a list of its goods). But, when GoodsList with particular categoryId is firstly opened I want to fetch data (goods for this categoryId)
and save it to redux store first (only if there is no data for this categoryId, there is no such checking in code, but I'll include it). And only after that
component takes data from Redux (not directly from remote server) and renders it. And meanwhile I show spinner to user.
In GoodsList you see console.log('settingGoodsList', goods.goods);
The problem is that it called two times. First time - when component is rendered. Second time - when data is fetched, store changed, and component rerendered automatically.js
Is it a problem, is this the way fetching remote data is working or I do something wrong?
I should firstly fetch data and only then component should render its content. I thought that componentWillMount() is the place that can help me.
And it called before render function. But console.log()
outputs info twice.

React Native Pass data to another component

I am new to React Native and trying to build a Messenger app and I have 2 components Search and Messenger​. I am struggling to pass the data I got from Search to Messenger.
Search component finds user (receiver) and me being sender I want to communicate but after finding user in Search I want to pass that user to Messenger so that I can chat with that specific user that found in <Search> component.
In addition, Search component has Views that will display user calendar etc.. so ideally I don't want to use <Messenger> in render() method of Search as it will include Messenger component features inside the Search component which destroys the purpose of <Search> component.
So my code is :
'use strict';
var Search = React.cerateClasss({
getDefaultProps: function () {
return {
date: new Date(),
singerName:''
};
},
getInitialState: function () {
return {
date: this.props.date,
artistName: '',
artistUserId: 1,
maxNoArtist: 0,
imagePath: '../common/images/1.png',
user: null
}
},
getArtistName: function () {
var artist = [];
var query = new Parse.Query(Parse.User);
query.equalTo('userId', this.state.artistUserId);
return query.first({
success: (result) => {
this.setState({artistName: result.get('name')});
this.props.singerName= result.get('name');
this.setState({imagePath: result.get('image').url()});
},
error: (data, error) => {
console.log('Error occured : ' + error.message())
}
});
},
render: function () {
if (!this.state.user) {
return <View style={styles.container}>
<Text style={styles.label}> Loading.... </Text>
</View>
}
var username = this.state.user.get('username');
return (
<View style={styles.container}>
<ResponsiveImage source={{uri:this.state.imagePath}} initHeight="200" initWidth="400"/>
<Text style={styles.label}>
{this.state.artistName}
</Text>
<View style={styles.innerButtonView}>
<Button text={'Onki'} onPress={this.getPreviousArtistName}/>
<Button text={'Indiki'} onPress={this.getNextArtistName}/>
</View>
<CalendarPicker
selectedDate={this.state.date}
onDateChange={this.onDateChange}
/>
<View style={styles.innerButtonView}>
<Button text={'Cyk'} onPress={this.onLogoutPress}/>
<Button text={'Habarlas'} onPress={this.onPress}/>
</View>
<Messenger singerName={this.props.singerName}></Messenger> // BREAKS SEARCH COMPONENT PURPOSE - INCLUDES MESSENGER FEATURES IN TO SEARCH COMPONENT
</View>
);
},
})
var Messenger = React.createClass({
getInitialState: function () {
return {
greeting: 'Salam',
date: new Date(),
errorMessage: '',
user: null,
olderMessageTextFrom: [],
olderMessageTextTo: [],
olderMessageDateFrom: [],
olderMessageDateTo: [],
earlierMessages: []
}
},
componentWillMount: function () {
Parse.User.currentAsync().then((user) => {
this.setState({user: user})
}
)
},
getMessages() {
return [
{
text: this.state.greeting,
name: this.props.singerName,
image: require('../common/images/1.png'),
position: 'left',
date: new Date()
},
I am late to answer but I did in different way using props.
I have two components.
Splash.js
Home.js
I am passing the data (Let's take String) from Splash.js to Home.js.
First component (Sender)
this.props.navigation.navigate('Home', {user_name: userName})
Second component (Receiver)
this.props.navigation.state.params.user_name
Hope this would help you.
OK, so based on your infos, I think the issue is that you don't get the singerName in the Messenger component.
First, I'd change your getArtistName method to this :
getArtistName: function () {
var artist = [];
var query = new Parse.Query(Parse.User);
query.equalTo('userId', this.state.artistUserId);
return query.first({
success: (result) => {
this.setState({artistName: result.get('name')});
// Removed the this.props.singerName = ...
this.setState({imagePath: result.get('image').url()});
},
error: (data, error) => {
console.log('Error occured : ' + error.message())
}
});
}
then in your render method :
<Messenger singerName={this.state.artistName} />
Inside a component you need to use setState and not change props :
that is to say that this.props.singerName = 'singer' is a wrong way of doing things, you should do this.setState({singerName: 'singer'}); then access it with this.state.singerName
Inside your messenger component, you access it with this.props.singerName