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.
Related
I'm trying to create an app that once a user logs in (enters a partyID), it directs them to a new page where it pulls back all the users data. Once they 'Log In; the new page isn't pulling the data as I expected.
However when I write to console the data is says undefined but the fetch URL does work when i go to it locally on my browser.
enter image description here
Here is my code
class CalcForm extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
};
}
componentDidMount() {
this.setState({ isLoading: true });
const serachByCustomerId = this.props.location.state;
const url =
"<MYURL>/api/Customer/" +
serachByCustomerId;
console.log("URL Being used ", url);
fetch(url)
.then((res) => res.json())
.then((data) => this.setState({ data: data }))
if (!this.state.isLoading) {
console.log("data after search", this.state.data);
}
}
// renders to display on page
render() {
const { data, isLoading } = this.state;
// if page is loading displays loading text and spinner to make user awear
if (isLoading) {
return (
<div className="pageLoading">
<p>Loading...</p>
<FadeLoader size={150} color={"#2d8259"} loading={isLoading} />
</div>
);
}
return (
<div> hi </div>
);
}
}
export default CalcForm;
I was expected the data returned to be printed into the console but upon looking I get undefined and there is also an error I don't understand
setState is asynchronous, so if you want to console.log the data, it must be within a callback:
this.setState({key: value}, () => {console.log(value})
This is what your componentDidMount() would look like:
componentDidMount() {
this.setState({isLoading: true });
const searchByCustomerId = this.props.location.state;
const url = "<MYURL>/api/Customer/" + searchByCustomerId;
console.log("URL Being used ", url);
fetch(url)
.then((res) => res.json())
.then((data) => this.setState({data: data },
() => {
console.log("data after search", data);
this.setState({isLoading: false})
}
))
}
PLUS: you had a typo (search not serach)
Hope this helps :)
Why not go down the hooks approach? Its far more nicer and easier to do things:
psuedo code to get you going. It has an await function so you should be able to derive your data once you pass in your url.
export default function CalcForm() {
const [isLoading, setLoading] = React.useState(true);
const [data, setData] = React.useState(false);
const getData = async () => {
setLoading(true);
const response = await fetch(url);
setData(response.json());
setLoading(false);
};
React.useEffect(() => {
getData();
}, []);
if (isLoading) {
return (
<div className="pageLoading">
<p>Loading...</p>
<FadeLoader size={150} color="#2d8259" loading={isLoading} />
</div>
);
}
return <div className="pageLoading">hi</div>;
}
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!
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>
)
}
I am trying to test a async/await function which does an api call using axios to get users. I am new to testing React applications and using JEST (trying first time), unable to get the test running.
I have tried using mock function in JEST. My Code is as follows:
// component --users
export default class Users extends React.Component {
constructor(props) {
super(props);
this.state = {};
this.getUsers = this.getUsers.bind(this);
}
/*does an api request to get the users*/
/*async await used to handle the asynchronous behaviour*/
async getUsers() {
// Promise is resolved and value is inside of the resp const.
try {
const resp = await axios.get(
"https://jsonplaceholder.typicode.com/users"
);
if (resp.status === 200) {
const users = resp.data;
/* mapped user data to just get id and username*/
const userdata = users.map(user => {
var userObj = {};
userObj["id"] = user.id;
userObj["username"] = user.username;
return userObj;
});
return userdata;
}
} catch (err) {
console.error(err);
}
}
componentDidMount() {
this.getUsers().then(users => this.setState({ users: users }));
}
/*****************************************************************/
//props(userid ,username) are passed so that api call is made for
//getting posts of s psrticular user .
/*****************************************************************/
render() {
if (!this.state.users) {
return (
<div className="usersWrapper">
<img className="loading" src="/loading.gif" alt="Loading.." />
</div>
);
}
return (
<div className="usersWrapper">
{this.state.users.map(user => (
<div key={user.id}>
<Posts username={user.username} userid={user.id} />
</div>
))}
</div>
);
}
}
//axios.js-mockaxios
export default {
get: jest.fn(() => Promise.resolve({ data: {} }))
};
//users.test.js
describe("Users", () => {
describe("componentDidMount", () => {
it("sets the state componentDidMount", async () => {
mockAxios.get.mockImplementationOnce(
() =>
Promise.resolve({
users: [
{
id: 1,
username: "Bret"
}
]
}) //promise
);
const renderedComponent = await shallow(<Users />);
await renderedComponent.update();
expect(renderedComponent.find("users").length).toEqual(1);
});
});
});
the test fails -
FAIL src/components/users.test.js (7.437s) ● Users ›
componentDidMount › sets the state componentDidMount
expect(received).toEqual(expected)
Expected value to equal:
1
Received:
0
Please help me figure out the problem. i am totally new to testing reactapps
It looks like similar to this one.
The problem is that test if finished earlier then async fetchUsers and then setState (it's also async operation). To fix it you can pass done callback to test, and put the last expectation into setTimeout(fn, 0) - so expect will be called after all async operations done:
it("sets the state componentDidMount", (done) => {
...
setTimeout(() => {
expect(renderedComponent.find("users").length).toEqual(1);
done();
}, 0);
});
As mentioned in comment, it's hacky fix, I hope here will be other answers with more jest way to fix it.
As far as I understood, what you are trying to do, is to wait for the resolution of a promise, which is 'hidden' inside of your componentDidMount() method.
expect(renderedComponent.find("users").length).toEqual(1);
will not work in my view in this context because find() is trying to select DOM Elements. If you want to check the state, you need to use state():
expect(renderedComponent.state("users").length).toEqual(1);
Still, you will have to find the right way to wait for the resolution of the promise:
To refer to the previous posts and comments, I don't see any effect in using async/await in combination with any of the enzyme methods (update, instance or whatsoever. Enzyme docs also give no hint in that direction, since the promise resolution is not the job of enzyme).
The only robust, clean and (jest-default) way forward is somehow a mixture of the different approaches. Here comes your code slightly changed:
// component --users
export default class Users extends React.Component {
constructor(props) {
super(props);
this.state = {};
this.getUsers = this.getUsers.bind(this);
}
/*does an api request to get the users*/
/*async await used to handle the asynchronous behaviour*/
async getUsers() {
// Promise is resolved and value is inside of the resp const.
try {
const resp = await axios.get(
"https://jsonplaceholder.typicode.com/users"
);
if (resp.status === 200) {
const users = resp.data;
/* mapped user data to just get id and username*/
const userdata = users.map(user => {
var userObj = {};
userObj["id"] = user.id;
userObj["username"] = user.username;
return userObj;
});
return userdata;
}
} catch (err) {
console.error(err);
}
}
componentDidMount() {
// store the promise in a new field, not the state since setState will trigger a rerender
this.loadingPromise = this.getUsers().then(users => this.setState({users: users}));
}
/*****************************************************************/
//props(userid ,username) are passed so that api call is made for
//getting posts of s psrticular user .
/*****************************************************************/
render() {
if (!this.state.users) {
return (
<div className="usersWrapper">
<img className="loading" src="/loading.gif" alt="Loading.."/>
</div>
);
}
return (
<div className="usersWrapper">
{this.state.users.map(user => (
<div key={user.id}>
<Posts username={user.username} userid={user.id}/>
</div>
))}
</div>
);
}
}
//axios.js-mockaxios
export default {
get: jest.fn(() => Promise.resolve({data: {}}))
};
//users.test.js
describe("Users", () => {
describe("componentDidMount", () => {
it("sets the state componentDidMount",() => {
mockAxios.get.mockImplementationOnce(
() =>
Promise.resolve({
users: [
{
id: 1,
username: "Bret"
}
]
}) //promise
);
const renderedComponent = shallow(<Users/>);
//Jest will wait for the returned promise to resolve.
return renderedComponent.instance().loadingPromise.then(() => {
expect(renderedComponent.state("users").length).toEqual(1);
});
});
});
});
While doing some research, I came across this post: To be honest I don't know whether it is a good idea, but it works in my async tests, and it avoids the additional reference to the promise in your component. So feel free to try this out also!
When I try to access a state variable which is set in ComponentDidMount, react throws an undefined error. This is because I believe when I'm calling the fetch api and setState in ComponentDidMount, the value isn't ready yet (async stuff). Is there a proper way to either delay the render until the setState call is done or some other way to get the state updated fully before render is called?
I think the code below will give you a basic idea how fetch data and render work.
class App extends Component {
state = {
data:{},
loading:true,
error:null,
}
componentDidMount = () => {
fetch('https://example.com/api/article')
.then((response) => {
return response.json();
})
.then((json) => {
this.setState({
data:json,
loading:false,
})
.catch(error => {
this.setState({
error,
loading:false,
})
});
});
}
render() {
const {data,error,loading} = this.state;
if(loading){
return "Loading ..."
}
if(error){
return "Something went wrong."
}
return 'your actual render component or data';
}
}
export default App;