I have a component Home that is called after user get in. There are some data in that screen, and in the header I have a icon button that send the user for a screen where the user can see their profile data and also delete account. So when the icon button is clicked I'm sending data using props.navigation, sending the user to another screen/component.
profile = () => {
const { navigation } = this.props;
const user = navigation.getParam('user', 'erro');
this.props.navigation.navigate('profileScreen', { user: user });
}
Inside the new component, I tried to setState inside the method componentDidMount using that data but it didn't work. I checked using console.log the data is there. How could I setState in this case?
export default class profileScreen extends Component {
static navigationOptions = {
title: "Profile"
};
constructor(props) {
super(props);
this.state = {
user: {}
}
}
componentDidMount() {
const {navigation} = this.props;
const user = navigation.getParam('user', 'Erro2');
this.setState({user: user.user});
console.log(this.state); // Object {"user": Object {},}
console.log("const user");
console.log(user.user); //my actual data is printed
}
render() {
return (
<Text>{this.state.use.name}</Text>
<Text>{this.state.use.age}</Text>
<Text>{this.state.use.email}</Text>
...
...
)
}
}
Result from console.log(this.state)
Object {
"user": Object {},
}
Result from console.log(user)
Object {
"createdAt": "2019-04-27T21:21:36.000Z",
"email": "sd#sd",
"type": "Admin",
"updatedAt": "2019-04-27T21:21:36.000Z",
...
...
}
It seems like you're trying to send an object (user) as a route parameter with react-navigation library. It's not possible.
The proper way of doing such scenarios is to send the user's id userId as route parameter and load user details from your API (or state).
profile = () => {
const user = {id: 10 /*, ... rest of properties */}; // or take it from your state / api
this.props.navigation.navigate('profileScreen', { userId: user.id });
}
componentDidMount() {
const {navigation} = this.props;
const userId = navigation.getParam('userId', 'Erro2');
// const user = read user details from state / api by providing her id
this.setState({user: user});
}
ps: if you are using state management like redux/flux/..., consider setting currentUser in your global state and read that instead of passing userId as a route param.
To make sure component updates when the user got new value in the state render method should be like this:
render() {
const {user} = this.state
return (
<View>
{user && <Text>{user.name}</Text>}
{user && <Text>{user.age}</Text>}
{user && <Text>{user.email}</Text>}
...
...
</View>
)
}
Note 0: const {user} = this.state would save you from repeating this.state
Note 1: it would much more elegant to wrap all those <Text> component inside another <View> to prevent repeating the condition phrase {user && ...}
Related
I have a search screen, contain Input And TopTabs "Songs, Artists",
When I get data from API after a search I make two things
1- I setState to appear the TopTab Component "true/false"
2- dispatch an action to save Songs & Artists Data in redux store.
that works fine.
But in topTab component, as I say before I have tow tabs "songs, artists"
For example, In the Songs component, I want to manipulate the data to achieve my case so in componentDidMount I Map the songs array from redux and push the new data into the component state.
But it's not working fine!
At the first time, I got songs from redux as empty [] although it's saved successfully in redux store when I get data from API
So how can I handle this case to not mutate the data?
Search.js "Main screen"
onSearch = async () => {
const {searchText} = this.state;
if (searchText.length > 0) {
this.setState({onBoarding: false}); // to appear the TopTab Component
try {
let response = await API.post('/search', {
name: searchText,
});
let {
data: {data},
} = response;
let artists = data.artists.data;
let songs = data.traks.data;
this.props.getResult(songs, artists);
}
catch (err) {
console.log(err);
}
}
render(){
<View style={styles.searchHeader}>
<Input
onChangeText={text => this.search(text)}
value={this.state.searchText}
onSubmitEditing={this.onSearch}
returnKeyType="search"
/>
</View>
{this.state.onBoarding ? (
<SearchBoard />
) : (
<SearchTabNavigator /> // TopTabs component
)}
}
SongsTab
...
componentDidMount() {
console.log('props.songs', this.props.songs); // Empty []
let All_tunes = [];
if (this.props.songs?.length > 0) {
console.log('mapping...');
this.props.songs.map(track =>
All_tunes.push({
id: track.id,
name: track.name,
url: URL + track.sounds,
img: URL + track.avatar,
}),
);
this.setState({All_tunes});
}
}
...
const mapStateToProps = state => {
return {
songs: state.searchResult.songs,
};
};
Edit
I fix the issue by using componentDidUpdate() life cycle
If you have any other ways tell me, please!
SongsTab
manipulateSongs = arr => {
let All_tunes = [];
arr.map(track =>
All_tunes.push({
id: track.id,
name: track.name,
url: URL + track.sounds,
img: URL + track.avatar,
}),
);
this.setState({All_tunes});
};
componentDidMount() {
if (this.props.songs?.length > 0) {
this.manipulateSongs(this.props.songs);
console.log('mapping...');
}
}
componentDidUpdate(prevProps) {
if (prevProps.songs !== this.props.songs) {
this.manipulateSongs(this.props.songs);
}
}
The problem you're referring to has to do with the way asynchronous code is handled in JavaScript (and in turn react-redux). When your component initially mounts, your redux store passes its initial state to your SongsTab.js component. That seems to be an empty array.
Any API call is an asynchronous action, and won't update the redux store until the promise has resolved/rejected and data has been successfully fetched. Any HTTP request takes much longer to complete than painting elements to the DOM. So your component loads with default data before being updated with the response from your API call a number of milliseconds later.
The way you've handled it with class-based components is fine. There are probably some optimizations you could add, but it should work as expected. You might even choose to render a Spinner component while you're fetching data from the API as well.
If you want a different approach using more modern React patterns, you can try and use the equivalent version with React hooks.
const Songs = ({ fetchSongs, songs, ...props }) => {
React.useEffect(() => {
// dispatch any redux actions upon mounting
// handle any component did update logic here as well
}, [songs])
// ...the rest of your component
}
Here are the docs for the useEffect hook.
I would like to know how to dispatch action with different params in react redux.
In format.js, queryData with different param is called by callFetch method.
After props dispatch, in the render method, will receive data in props memberData,
I receive the response memberDatawith status Inactive but
How to display for both the Active and Inactive,
// format.js
componentDidMount(){
this.callFetch();
}
callFetch(){
this.props.dispatch(queryData(this.createFetchUrl("Active")));
this.props.dispatch(queryData(this.createFetchUrl("Inactive")));
}
createFetchUrl(status) {
const body ={
position: "civil",
status: status
}
return body;
}
render(){
const { memberData } = this.props.data
return(
<div>
Active
<p>{memberData}</p>
Inactive
<p>{memberData}</p>
</div>
)
}
//actions.js
export const queryDeviceByMember = payload =>
queryApi({
request: CONSTANTS.DATA,
url: `api/details`,
encode: false,
success: CONSTANTS.DATA_SUCCESS,
failure: CONSTANTS.DATA_FAILURE,
...payload
});
//reducer.js
case CONSTANTS.DATA_SUCCESS:
case CONSTANTS.DATA_FAILURE:
return {
...state,
memberData: data,
apiPending: false,
errormsg: errormsg,
servererror: servererror || ""
};
am receiving the same response for both active and inactive states, since it overrides, without overriding how to do
[{
id: "20",
name: "xxx",
device: "ios"
},
{
id: "10",
name: "yyy",
device: "ios"
}]
You are overwriting the data in memberData, consider following these steps:
Split your reducers for active and inactive cases
Use a selector to select the data from your state, i.e:
export const getActiveMembers = state => state.activeMembers;
export const getInactiveMembers= state => state.inactiveMembers;
Import the selectors from step 2 into your components, and use mapStateToProps(state) to get the data from your state:
function mapStateToProps(state) {
return {
activeMembers: getActiveMembers(state),
subscription: getInactiveMembers(state)
};
}
Use the data in render:
render(){
const { memberData } = this.props.data
return(
<div>
Active
<p>{memberData}</p>
Inactive
<p>{memberData}</p>
</div>
)
}
Good Luck
P.S: I would use one API call to get all the members and then classify them to inactive & active in my state using the reducer.
I am new to react, I am getting data from redux, first, I get an object from accounts from redux, then I pass this to the function in redux and set a value in numReg in the reducer.
When I call a function by this.props.fetchAccountDetail(data) in actions its send a request to API and fetch the data from API and save it in reducer or store. When i call function in render by
this.getDataFromAccount(accountDetail.num), it goes in infinite loop.
I want data in a return, it should only run one time.
import React, { Component } from 'react'
import { fetchAccountDetail, } from '../../../actions'
class myclass extends Component {
state = {
num : ''
};
getAccounts = (data) => {
if (!data) { return; }
return data.find(item => item.id == this.props.match.params.id);
}
getDataFromAccount = (data) => {
this.props.fetchAccountDetail(data);
// This is a api , which provide the result agaisnt
// a num and set value in numReg in reducer
}
render() {
const { accounts, numReg } = this.props;
const accountDetail = this.getAccounts(accounts);
// Here i will get a match object like {id :1 , num :12345}
const test=this.getDataFromAccount(accountDetail.num)
// When i call this , it stucks in infinite loop , how can i get data only once when it render
console.log(test)
return (
<div />
);
}
}
const mapStateToProps = state => {
return { accounts : state.accounts.accounts | [{id :1 , num :12345} , {id :2 , num :535234}],
numReg : state.accounts.numReg
//Is a object containg the information of num from accounts
}
}
export default (compose(
withStyles(styles),
connect(mapStateToProps, { fetchAccountDetail,}))(myclass));
It should return data in variable test after fetching data from redux.
You should never call data fetching functions or functions which alter the state within render.
Render may be called multiple times if a parent rerenders or just its internal state changes.
Calling fetchAccountDetails in render updates the redux store. Redux will pass the new but equal data as props into your component.
That Component will rerender because its props changed and will call fetchAccountDetails again => loop. Render should only display data!!
For data fetching, 2 functions exist. componentDidMount which will be called after the component is visible. That would be a good place to call your fetch.
If you need a prop to fetch the data for e.g. an Id of some sort (fetch data for that Id), you would use componentDidUpdate in which you compare the new id and the old id to see if you need to fetch the data again.
You should read the docs and look at some tutorials.
Hope this helps.
Happy coding.
As Domino987 answered, you need to make use of lifecycle methods. Here's an example of how it might look:
componentDidMount() {
const { accounts } = this.props;
const accountDetail = this.getAccounts(accounts);
const accountData = this.getDataFromAccount(accountDetail.num)
this.setState({
account: {
accountDetail: accountDetail,
accountData: accountData
}
})
}
componentDidUpdate() {
const { accounts } = this.props;
const accountDetail = this.getAccounts(accounts);
const accountData = this.getDataFromAccount(accountDetail.num)
if (this.state.account.accountData !== this.getDataFromAccount(accountDetail.num)) {
this.setState({
account: {
accountDetail: accountDetail,
accountData: accountData
}
})
}
}
So in my recipe App, users are able to mark or unmark recipes as their favorite.
The only thing I can't wrap my head around is How to make it instant. my current code supports makes a post call to mark the recipe as favorite but you see the change of icon (i.e the filled one) they have to refresh the page.
I do need some suggestion on how can I make it work on the click.
Here is my code:
class CuisineViewById extends Component {
constructor(props) {
super(props);
this.state = {
user: {},
access_token: '',
};
this.toggleFavorite = this.toggleFavorite.bind(this);
}
componentDidMount() {
this.props.getUser(() => {
this.props.getAccessToken(this.props.user.profile.sub, () => {
console.log(this.props.user);
this.props.getCuisineById(this.props.match.params.id, this.props.accessToken);
this.props.getFavoriteRecipes(this.props.accessToken);
});
});
}
toggleFavorite(userID, recipeID, marked) {
const userpreference = {
userid: userID,
recipe: recipeID,
favorite: marked
};
axios
.post('/api/userpreference', userpreference, {
headers: {'access_token': this.props.access_token}
})
.then(res => console.log(res));
}
displayFavorite = recipeId => {
let favoriteRecipes = this.props.userPreferred;
for (var i = 0; i < favoriteRecipes.length; i++) {
if (favoriteRecipes[i].recipe === recipeId) {
return true;
} else {
}
}
};
render() {
const that = this;
const {user} = this.props;
const {cuisine} = this.props;
return (
<CuisineTileHeading
label={cuisine.label}
totalNoRecipes={cuisine.totalRecipes]}
key={cuisine.id}
>
{cuisine.recipes && cuisine.recipes.map(function(asset, index)
{
let marked = recipe.isFavorite ? 'no' : 'yes';
return (
<RecipesCards
title={recipe.title}
description={recipe.description}
chef={recipe.owner}
lastUpdated={recipe.lastUpdated}
recipeType={recipe.assetType}
key={'RecipesCard' + index}
thumbnail={recipe.thumbnailBase64}
recipeId={recipe.id}
cuisine={cuisine}
favorite={that.displayFavorite(recipe.id)}
toggleFavorite={() =>
that.toggleFavorite(userId, recipe.id, marked)
}
/>
);
})}
</CuisneTileHeading>
)
}
}
const mapStateToProps = state = ({
cuisine : state.cuisine.cuisne,
user: state.user.user,
userPreferred: state.recipe.userPrefered,
accessToken: state.asset.accessToken
)}
In my component did mount, I am calling functions to get user information, then access token and then cuisines and then user favorite recipes.
toggleFavorite is the function that makes a recipe favorite or not favorite.
displayFavorite is a function that return either true or false is recipe id matches to the recipe ID store in userpreference object.
Right now, you compute that "this recipe is favorite" from a function that returns true or false.
ReactJS has no way to automatically trigger a re-rendering of the favorite icon since it is not linked to the recipe's state at all.
If you put "isFavorite" in the recipe's state and toggle that to true or false with the onClick event, which will change the recipe's state value for "isFavorite", React should know to call a re-render on the recipe card's icon ... then you just make sure it outputs the HTML for a filled icon when true and empty icon when false. React will know to re-render all DOM elements linked to that "slice" of the state, "isFavorite recipe" in this case.
TL;DR: leverage React's state concept instead of computing if the recipe is favorited by the user through a function which does not modify the state, since re-renders are done by React when the state changes.
Following the docs: react-navigation params, I'd basically do something like this (parent):
this.props.navigation.navigate('Details', {
itemId: 86,
otherParam: 'anything you want here',
});
And then passing to the child
class DetailsScreen extends React.Component {
render() {
const { navigation } = this.props;
const itemId = navigation.getParam('itemId', 'NO-ID');
const otherParam = navigation.getParam('otherParam', 'some default value');
return ( (..omited for brevity)
EDIT:
My situation is that in one screen/container we fire up a graphql mutation that takes a phoneNumber and retrieves a code to user.
Onto the next screen I need to take this phoneNumber that the user has just inserted to fire another mutation that takes phoneNumber and code to make the authentication. I don't want to use states to do that, since there's api available on react-navigation.
Parent:
signInUser(phoneNumber: string) {
const { navigation, sendCode } = this.props
sendCode({ variables: { phoneNumber } }) // graphql mutation
navigation.navigate('Authenticate', { phoneNumber }) // passing
// phoneNumber as param
}
Child:
export const AuthenticationCodeModal = (props: NavigationScreenProps) => {
const {navigation} = props
const phoneNumber = navigation.getParam(phoneNumber)
// console.log(phoneNumber)
// I need to get phoneNumber here and pass it as props to
// <AuthenticationFormContainer> which will fire up auth mutation
return(
<View style={styles.container} >
<AuthenticationFormContainer/>
</View>
)
}
It better and interesting if you use redux for task like this. Redux has a global state which can be assessed by any component
Turns out it was a simple syntax error of the value that getParam receives, doing this fixed my problem:
navigation.navigate('Authenticate', { phoneNumber: {phoneNumber: phoneNumber} })