Trouble passing props to imported component - reactjs

I have a "main" component which imports two other components, which contain a form each, and I'm having trouble passing some values which I get from an API call in the main component.
This is an example of what's happening on the "main" component with one of the imported forms:
user_id = null;
email = "";
componentDidMount() {
get(`user-data/`, this.token).then((response) => {
this.user_id = response.user_id;
this.email = response.email;
// Doing a console.log at this point shows both values are assigned properly
// And that they exist
}).catch((error) => notify.notifyError(error.message));
}
<UserForm
email={this.email}
id={this.user_id}
></UserForm>;
Edit: I'm including the API call logic since I believe it has something to do with the issue.
export const get = async (url, authToken) => {
try {
const response = await axiosInstance.get(`${url}`, {
headers: {
"Content-Type": "application/json",
Authorization: `${BEARER} ${authToken}`,
},
});
return response;
} catch (error) {
throw new Error();
}
};
On the UserForm component:
id = null;
email = "";
constructor(props) {
super(props);
this.id = props.id;
this.email = props.email;
// Doing a console.log here shows both props are empty
// Trying to use either of them from here on out breaks the page
}
I assume the issue has to do with the components rendering before the value gets assigned, but I'm not entirely sure about it.
Why aren't the props received properly on the imported form, and how can I make sure they are?
Edit 2: Waiting for props to be set on componentDidUpdate works, but operating the way I need to creates and endless loop of execution of componentDidUpdate
componentDidUpdate() {
if (this.props.id) {
console.log("props are set!");
this.getData();
}
}
getData() {
get(`user-data/${this.props.id}`, this.token)
.then((user) => {
// This creates and endless loop of updating
this.setState({
...user.data
});
})
.catch((error) => notify.notifyError(error.message));
}

in your main component do like this
state = {
email: '',
user_id: null
}
componentDidMount() {
get(`user-data/`, this.token).then((response) => {
this.setState({
email: response.email,
user_id: response.user_id
})
// Doing a console.log at this point shows both values are assigned properly
// And that they exist
}).catch((error) => notify.notifyError(error.message));
}
and in your userform print your props like
componentDidUpdate(prevProps){
console.log('this.props', this.props);
console.log('prevProps', prevProps)
}

Since this two values are not managed by state, when the values varies, it won't triggered re-render. Hence, the props are not getting the up-to-date value. I suggest using state to handle those 2 values
constructor(props){
this.state = {
user_id = null;
email = "";
}
}
componentDidMount() {
get(`user-data/`, this.token).then((response) => {
console.log('response works fine?', response)
this.setState = {
user_id = response.user_id;
email = response.email;
}
// Doing a console.log at this point shows both values are assigned properly
// And that they exist
}).catch((error) => notify.notifyError(error.message));
}
render(){
<UserForm
email={this.state.email}
id={this.state.user_id}
></UserForm>;
}
In your UseForm component, there is no need to place it in state unless you want to do further modification. I suggest directly test them in render method.
constructor(props) {
super(props);
}
render(){
const {id, email} = this.props
return (
<div>
{id} and {email}
</div>
)
}

Related

React - Trying to update Fetch call in Parent Component based off of User Input in Child Component

So I am still very much a beginner when it comes to React. I am trying to build an application where the user inputs their location, which would then dynamically update the URL within my fetch call. Based off the results of the fist fetch call, I would then dynamically update a second fetch call, to a different API, providing me with the information needed.
As it stands right now, both Fetch calls are properly working, when provided the right information. The problem I am currently running into is, I don't believe my parent component is re-rendering with the update information. I am trying to console log one of my states but it keeps coming back as blank. The weird part is, the other state that is being created within the child component, is coming back with the right information. Any help would be greatly appreciated!
APP.JS
import Home from './Home/Home';
import Userinput from './UserInput/Userinput';
const url =
'https://api.openuv.io/api/v1/uv?lat=-33.34&lng=115.342&dt=2018-01-24T10:50:52.283Z';
const url1 = `http://www.mapquestapi.com/geocoding/v1/address?key=${process.env.REACT_APP_MAP_API_KEY}`;
class App extends Component {
constructor(props) {
super(props);
this.state = {
uvIndex: '',
lat: '',
long: '',
inputValue: '',
};
this.handleInputValue = this.handleInputValue.bind(this);
}
handleInputValue(val) {
this.setState({ inputValue: val });
}
componentDidMount() {
let newVal = this.state.inputValue;
fetch(`${url1}&location=${newVal}`, {
method: 'GET',
headers: {
'content-type': 'application/json',
},
})
.then((res) => res.json())
.then((res) => {
this.setState({
lat: res.results[0].locations[0].latLng.lat,
long: res.results[0].locations[0].latLng.lng,
});
// this.setState({ uvIndex: res });
})
.catch((err) => {
console.error(err);
});
fetch(url, {
method: 'GET',
headers: {
'content-type': 'application/json',
'x-access-token': `${process.env.REACT_APP_UV_API_KEY}`,
},
})
.then((res) => res.json())
.then((res) => {
console.log(res);
this.setState({ uvIndex: res });
})
.catch((err) => {
console.error(err);
});
}
render() {
console.log(this.state.lat); #this state comes back as blank
console.log(`${url1}&location=${this.state.inputValue}`); # this state comes back with the update userinput
return (
<div>
<header>
<Home />
</header>
<div>
<Userinput handleInput={this.handleInputValue} />
</div>
</div>
);
}
}
export default App;
Userinput.js
class Userinput extends Component {
constructor(props) {
super(props);
this.state = {
inputVal: '',
};
this.onInputChange = this.onInputChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
// handle input change event
onInputChange(e) {
this.setState({ inputVal: e.target.value });
}
// handle button click event and pass data in parent
handleSubmit() {
this.props.handleInput(this.state.inputVal);
}
render() {
return (
<div>
<input value={this.state.inputVal} onChange={this.onInputChange} />
<input type='button' value='Submit' onClick={this.handleSubmit} />
</div>
);
}
}
export default Userinput;
Consider this.setState in the App Class. The first fetch() is writing your state like so:
this.setState({
lat: res.results[0].locations[0].latLng.lat,
long: res.results[0].locations[0].latLng.lng,
});
The second fetch() call sets a new state object, which removes the result from the first fetch() call, or whatever fetch() resolves faster:
.then((res) => {
console.log(res);
this.setState({ uvIndex: res });
})
You can fix this issue with object spread operator:
this.setState({...this.state, uvIndex: res });
This will keep a copy of your state, and only overwrite uvIndex (if it was set before)
Also consider the handleInputVal Method in App.js. I think same problem here. the state is overwritten with a new Object (setState doesn't update the state, it creates a new State), which means that lat and long are being set to undefined 🤔
handleInputValue(val) {
//this.setState({ inputValue: val }); sets lat and long to undefined
this.setState({...this.state, inputValue: val }); //keep lat and long values, and update inputValue with val
}

A lot of unnecessary requests

I have Posts component:
class ProfilePosts extends React.Component {
constructor(props){
super(props);
this.state = {
posts: []
}
}
getData = async (url) => {
const res = await fetch(url, {
method: 'GET',
headers: {
'Authorization' : `${window.localStorage.getItem('token')}`
}
});
return await res.json();
}
getPosts = async () => {
if (window.location.pathname === '/profile/undefined%7D')
{
window.location.pathname = `/profile/${window.localStorage.getItem('id')}`
}
await this.getData(`https://api.com/posts/${window.location.pathname.slice(9)}`)
.then(data => {
this.setState({posts: data}
);
})
}
componentDidMount(){
if (window.localStorage.getItem('id') != ''){
this.getPosts();
}
}
componentDidUpdate(){
if (window.localStorage.getItem('id') != ''){
this.getPosts();
}
}
renderItems(posts){
return Object.values(posts).map(post => {
return (
<ProfilePost likes={post.likes} comments={post.comments} userId={window.localStorage.getItem('id')} token={window.localStorage.getItem('token')} Postid={post.id} key={post.id} sender={post.sender} content={post.content} time={post.sent_time}/>
)
});
}
render() {
const {posts} = this.state;
const items = this.renderItems(posts);
return(
<div className="profile-posts">
{items}
</div>
);}
}
export default ProfilePosts;
I don't understand what is the reasons for re-rendering this component so many times (I don't know the actual number but it's huge). Therefore, a lot of requests to the database, and so on. How can I fix this?
I tried using another state but that didn't work for me. If you press the button, the state changed and then I checked if the state changed then I call this.getPosts() and then set the state to false again. It caused Error: Maximum update depth exceeded.
Your componentDidUpdate runs every time the component updates, after render, so it's calling this.getPosts();, which in turn calls setState, which results in another re-render and update, and so on.
Remove the componentDidUpdate entirely and let the componentDidMount method (which implements the same logic) alone take care of things.

Unable to setState regardless of making setState synchronous

I am learning about how to use synchronous setState but it is not working for my project. I want to update the state after I get the listingInfo from Axios but it does not work, the res.data, however, is working fine
class ListingItem extends Component {
constructor(props) {
super(props);
this.state = {
listingInfo: {},
open: false,
};
this.getListingData(this.props.itemId);
}
setStateSynchronous(stateUpdate) {
return new Promise((resolve) => {
this.setState(stateUpdate, () => resolve());
});
}
getListingData = async (item_id) => {
try {
const res = await axios.get(`http://localhost:5000/api/items/${item_id}`);
console.log(res.data);//it's working
await this.setStateSynchronous({ listingInfo: res.data });
// this.setState({
// listingInfo: res.data,
// });
console.log(this.state.listingInfo);//no result
} catch (err) {
setAlert('Fail to obtain listings', 'error');
}
};
I would be really grateful for your help!
Thanks to #PrathapReddy! I used conditional rendering to prevent the data from rendering before the setState is done. I added this line of code on the rendering part:
render() {
if (Object.keys(this.state.listingInfo).length === 0) {
return (
<div>
Loading
</div>
);
} else {
return //put what you want to initially render here
}
}
Also, there is no need to modify the setState, the normal setState will do. Hope this is useful!

React componentDidMount and working with Promises?

Getting really fed up now! I am trying to get a Spinner element to appear while 3 functions run in the componentDidMount function.
From what I gather the render comes before componentDidMount, so I am running the Spinner in the render, while:
a cookie value is retrieved from this.getValidToken()
then an axios post request sets state of isLoggedin (using above value as payload)
then the logic() function runs a simple if statement to either log user in or redirect to
error page.
I keep getting errors about Promises, I feel there is a better way to do this?
constructor(props){
super(props);
this.state = {
isLoggedIn: false
}
}
componentDidMount() {
const post =
axios.post(//api post request here)
.then(function(response) {
this.setState({ isLoggedIn: true });
})
.catch(function(error) {
this.setState({ isLoggedIn: false });
})
const LoggedIn = this.state.isLoggedIn;
const logic = () => {
if (LoggedIn) {
//log user in
} else {
//redirect user to another page
}
};
this.getValidToken()
.then(post)
.then(logic);
//getValidToken firstly gets a cookie value which is then a payload for the post function
}
render() {
return <Spinner />;
}
Firstly, you assign axios post to a variable, it is executed immediately and not after the getValidToken promise is resoved
Secondly the state update in react is async so you cannot have loggedIn logic based on state in promise resolver
You could handle the above scenario something like
constructor(props){
super(props);
this.state = {
isLoggedIn: false
}
}
componentDidMount() {
const post = () => axios.post(//api post request here)
.then(function(response) {
this.setState({ isLoggedIn: true });
return true;
})
.catch(function(error) {
this.setState({ isLoggedIn: false });
return false;
})
const logic = (isLoggedIn) => { // use promise chaining here
if (isLoggedIn) {
//log user in
} else {
//redirect user to another page
}
};
this.getValidToken()
.then(post)
.then(logic);
//getValidToken firstly gets a cookie value which is then a payload for the post function
}
render() {
return <Spinner />;
}

componentDidUpdate keeps getting called

componentDidUpdate () {
this.showPosts();
}
showPosts = async () => {
var userID = await AsyncStorage.getItem('userID');
fetch(strings.baseUri+"getPostWithUserID", {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
"user_id": userID
})
})
.then((response) => response.json())
.then((responseJson) => {
let jsonObj = JSON.parse(JSON.stringify(responseJson));
if (jsonObj.status=="true") {
this.setState({
data: responseJson.data,
imageSrc: responseJson.data.imgSrc,
});
}
else {
this.setState({show: false});
}
})
}
I'm calling showPosts function from componentDidUpdate to show my updated Flatlist. But componentDidUpdate keeps getting called. Should I use shouldComponentUpdate ?
========================== UPDATED CODE ============================
This is from Home Screen
async componentDidMount () {
this._isMounted = true;
await this.showPosts();
}
componentDidUpdate () {
this.showPosts();
}
showPosts = async () => {
try {
var userID = await AsyncStorage.getItem('userID');
fetch(strings.baseUri+"getPostWithUserID", {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
"user_id": userID
})
})
.then((response) => response.json())
.then((responseJson) => {
let jsonObj = JSON.parse(JSON.stringify(responseJson));
if (jsonObj.status=="true") {
this.setState({
data: responseJson.data,
imageSrc: responseJson.data.imgSrc,
});
}
else {
if (this._isMounted) {
this.setState({show: false});
}
}
})
.catch((error) => {
console.error(error);
});
}
catch (err) {
console.warn(err);
}
}
componentWillUnmount () {
this._isMounted = false;
}
This is Image Descrpiption screen from where I'll navigate back to Home Screen
postData = async () => {
this.setState({loading: true});
var location = await AsyncStorage.getItem('location');
var path = await AsyncStorage.getItem('path');
var post_type = await AsyncStorage.getItem('post_type');
var userId = await AsyncStorage.getItem('userID');
var newPath = path.split("/");
var imageName = newPath[newPath.length-1];
const formData = new FormData();
var media = {
uri: path,
name: imageName,
type: 'image/jpg',
};
formData.append('image', media);
formData.append('user', userId);
formData.append('description',this.state.description);
formData.append('location',"usa");
formData.append('post_type',post_type);
formData.append('userprofile_picture',imageName);
fetch(strings.baseUri+"addPosts",{
method: 'POST',
headers: { 'Content-Type': 'multipart/form-data' },
body: formData,
})
.then((response) => response.json())
.then((responseJson) => {
let jsonObj = JSON.parse(JSON.stringify(responseJson));
if (jsonObj.status=="true") {
this.props.navigation.popToTop()
&& this.props.navigation.navigate('Home'); // This navigates me to the HomeScreen
}
else {
}
})
.catch((error) => {
console.error(error);
});
}
ComponentDidUpdate is an update LifeCycle Hook, this will get triggered when there is something is changed in the component State or Props.
Coming to your code:
You are calling a handler showPosts to setState, that will again trigger the update lifecycle.
This will lead to an infinite loop.
Solution
If you want to load the posts only in the first time, then move to Creational Life Cycle hook ( componentDidMount ).
componentDidMount() { // This just gets called once in creational lifecycle //
this.showPosts(); }
if you want this to always have the latest data, then there are two ways
Updating component is in the same component tree branch:, In this case , it's easy to achieve this you can pass the state from the updating component down to child component has props, your job is done OR if they are siblings then do a level up you can move the state one level up and have it coming in has props.
Updating component is in the different component tree branch: I recommend using REDUX, this is the main use of redux.
shouldComponentUpdate Yes definitely you can use this to verify the data and the load if needed, but be careful by using this your components update depends on the code in this.
Please check https://reactjs.org/docs/react-component.html#shouldcomponentupdate
You just need to call this in that way if you do in ComponentDidUpdate and update state in the method call by ComponentDidUpdate then a infinite loop start.
componentDidMount () {
this.showPosts();
}
================EDITED=======================
If you want to use only ComponentDidUpdate then you can use it like.
componentDidUpdate(prevProps, prevState) {
// only update if not match I don't know what's your data is so add a
// simple check like we use for strings.
if (prevState.data !== this.state.data) {
this.showPosts();
}
}
Just use prevState to match.
You can do this too
Common parent component
Create a new component say Posts.
import React, { Component } from 'react';
import HomeScreen from '../../HomeScreen';
import ImageDescription from '../../ImageDescription';
class Posts extends Component {
constructor(){
super();
this.state = {
dataEditted: false;
}
}
newDataHandler = () =>{
this.setState({dataEditted:true}); // this is flag to identify that there is change in data //
}
resetnewDataHandler = () =>{
this.setState({dataEditted:false}); // this handler is used to reset the falg back to initial //
}
render () {
const homeScreen = <HomeScreen editted={this.state.editted} resetHandler={this.resetnewDataHandler}/>;
const imageDescription = <ImageDescription newdataHandler={this.resetnewDataHandler}/>
return (
<div>
{homeScreen}
{imageDescription}
</div>
)
}
}
export default Posts;
This component is going to serve as a bridge to move data between.
Whenever there is fresh data in ImageDescription Component use the newDataHandler passed has props to update the common parent, then the dataEditted will be updated and passed has props to homeScreen Component, now in this componentDidUpdate of homeScreen check whether its true, then call this.showPosts() and also call resetnewDataHandler.
componentDidUpdate() is called when the state is changed (calling setState()) and if you do it inside the showPosts that is also inside the componentDidUpdate() you are creating an infinite state updating.

Resources