I've seen many posts regarding this.props, but none of them seems to answer my question, or at least I couldn't find it.
My component uses this.props as args to get my url
This following code is almost the exact copy of https://reactjs.org/docs/faq-ajax.html
import React, { Component } from 'react';
class MyList extends Component {
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
items: []
};
}
componentDidMount() {
let entityKey = this.props.eKey;
fetch(`some/url/${this.props.eKey}`)
.then(res => res.json())
.then(
(result) => {
this.setState({
isLoaded: true,
items: result
});
},
(error) => {
this.setState({
isLoaded: true,
error
});
}
)
}
render() {
const { error, isLoaded, items } = this.state;
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <div>Loading My List...</div>;
} else {
if (!items) {
return <div>Failed to Load My List</div>;
} else {
return (
<ul>
{items.map(item => (
<li key={item}>
{item}
</li>
))}
</ul>
);
}
}
}
}
export default MyList
Now my script that calls this is simply
class MyFunc extends Component {
return (
<div>
<MyList
eKey={this.props.eKey}
/>
</dev>
);
}
I eliminated other code in MyFunc for simplicity
When I console.log my this.props inside MyList, it returns {eKey: ""}, so I know that the eKey is being passed in. However, as you can see, it's empty.
I don't know if it's because I have this at the componentDidMount cycle which for some reason this.props hasn't arrived when fetching. If that's the case, how do I guarantee the arrival before executing that line?
If it's somewhere else where I have issue, what went wrong?
EDIT:
To add in some more info. This is tied to a reducer. The redux is not exactly my strong suite.....so please correct me on any concept that's wrong.
The reducer takes the initialState and an action. The action can be different things. The only place that eKey is loaded is when action.type='load'
export function reducer(state = initialState, action) {
switch (action.type) {
case LOAD:
return {
...state,
eKey: action.data.eKey,
// and some other stuff
};
// some other cases here
default:
return state;
}
}
Interesting thing is there's a submit button that updates another component, which supposedly get a new eKey and get the eKey's data. At that time, the eKey is always populated, but my myList is not updated accordingly. I think it's another issue I have to figure out, but just wanna put it out here in case it's somehow related.
It seems like the reducer is not called only the component is mounted and componentDidMount is already called. And MyList does not update when the reducer is finally called. I'm not sure how to solve this problem
EDIT 2:
I tried moving what's inside componentDidMount into the render function (with some mod, of course). Does it work? sure, but when I console.log something, it just continues to print out stuff. Seems like this render function is constantly receiving update. I'm not sure if this is normal as my reactjs knowledge is not enough to answer this question. It works, but I'm wondering if this is the right way to do things
I solved my problem by calling another function, so my code looks like
class MyList extends Component {
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
items: []
};
}
componentDidMount() {
this.props.loadMystuff().then(() => {
if (this.state.eKey !== this.props.eKey) {
let eKey = this.props.eKey;
this.fetchSList(eKey);
}
}).catch((error) => toast(error.message));
}
fetchSList(eKey) {
if (eKey !== '') {
fetch(`some_api_url_config/${this.props.entityKey}`)
.then(res => res.json())
.then(
(result) => {
this.setState({
isLoaded: true,
items: result
});
},
(error) => {
this.setState({
isLoaded: true,
error
});
}
);
}
}
render() {
const { error, isLoaded, items } = this.state;
if (items) {
return (
<div>
<h3>S List</h3>
<ul>
{items.map(item => (
<li key={item}>
{item}
</li>
))}
</ul>
</div>
);
} else if (error) {
return <div>Error: List Missing...{error.message}</div>;
} else if (!isLoaded) {
return <div>Loading List...</div>;
} else {
return <div>Not Found</div>;
}
}
}
Note that the loadMyStuff is the one retrieving the eKey, so calling that will effectively get me the eKey to resolve the timing issue
Related
import React, { Component } from 'react'
class Newsgenerator extends Component {
constructor(){
super()
this.state = {
data:{}
}
}
componentDidMount() {
fetch("https://newsapi.org/v2/everything?q=Apple&from=2021-06-24&sortBy=popularity&apiKey={...}")
.then(response => response.json())
.then(news => {
this.setState({
data: news
})
console.log(news)
})
}
render() {
return (
<div>
{this.state.data.discription}
</div>
)
}
}
export default Newsgenerator
You have a few things going on here. First off, this.state.data.discription doesn't exist. When your API call response, you get an object like this which you set to this.state.data:
{
status: "ok",
totalResults: 1588,
articles: [...]
}
So this.state.data.discription is undefined. So what you're seeing is the result of trying to print an undefined variable - which in React is nothing.
My guess is that you want to print the articles, which means your return statement should look more like this:
return (
<div>
{this.state.data.articles.map((article, idx) => {
return (<h2 key={`article-${idx}`}>{article.title}</h2>)
}}
</div>
)
What you have in this code is an array of articles in your state.data, and you're iterating through that to print the article title (title in your api response).
This is the starting point and should set you in the right direction.
I'm getting the above error and I don't know how to handle it.
I got a component. And in the render() i'm looping through an array and placing another component and parsing a value to that component like this:
render() {
let allProducts = this.state.products.map((product, i) => {
return (
<div key={product.article}>
...
<PriceStock value={product.article} />
...
</div>
)
})
}
In the PriceStock component i'm fetching some data with axios like the code below:
export default class PriceStock extends React.Component {
constructor(props) {
super(props);
this.state = ({
buttoprice: ''
})
this.getPriceAndStock = this.getPriceAndStock.bind(this)
}
getPriceAndStock(articleNo) {
return axios.post('LINK_TO_URL', {
articleNo: articleNo
}).then(result => {
return result.data
})
}
async componentDidMount() {
let pricestock;
pricestock = await this.getPriceAndStock(this.props.value)
let bruttoPrice = PRICE_TO_PARSE_TO_THE_STATE;
this.setState({ buttoprice: bruttoPrice })
}
render() {
return (
<div >
{this.state.buttoprice}
</div>
);
}
}
The error seems to happen when I try to setState in the componentDidMount, any suggestions?
this is an error occurs because you are updating state before it gets initialized
perform your loading activities in the constructor it is the right way to do it
getPriceAndStock(orderNumber, articleNo) {
return axios.post('LINK_TO_URL', {
orderNr: orderNumber, vareNr: articleNo
}).then(result => {
return result.data
})
}
constructor() {
this.getPriceAndStock(this.props.value)
.then(pricestock=>{
let bruttoPrice = PRICE_TO_PARSE_TO_THE_STATE;
this.state({ buttoprice: bruttoPrice })
})
.catch(console.log)
}
Found the answear in this question: https://github.com/material-components/material-components-web-react/issues/434
It's remindend me a little bit about the comment with another stackoverflow question.
In my React-App, i use the Firebase SDK. If a user wants to reset his password, he will be redirected to a page within my app. If the code is valid, the component <PWResetConfirmForm /> should be rended. If the code is invalid, the component <PWResetOutdatedForm /> is to be rendered.
My Page Component looks like this:
class PWResetConfirmPage extends Component {
constructor(props) {
super(props);
this.state = {};
this.verfiyResetPassword = this.verfiyResetPassword.bind(this);
}
verfiyResetPassword() {
const params = (new URL(`http://dummy.com${this.props.location.search}`)).searchParams;
const code = params.get("oobCode")
auth.doVerfiyPasswordReset(code)
.then(function () {
return (
<div className="HomePage-Main">
<TopBar></TopBar>
<PWResetConfirmForm></PWResetConfirmForm>
</div>
);
})
.catch(function () {
return (
<div className="HomePage-Main">
<TopBar></TopBar>
<PWResetOutdatedForm></PWResetOutdatedForm>
</div>
);
})
}
render() {
return (
<div>
{this.verfiyResetPassword()}
</div>
);
}
}
export default PWResetConfirmPage
When i try to run, i get a blank page and not error.
Where is my issue and how can i fix that?
Thank you very much for your help and for your time
You will not be able to return JSX from within then()/catch() of auth.doVerfiyPasswordReset() like that. You can instead approach this by taking advantage of React.Component lifecycle method componentDidMount and using setState() to manipulate state properties for conditional rendering. I've added state properties to the component, one to track whether loading (API call has completed) and one to track whether the call was a success (then) or failure (catch). These properties are used to conditionally generate JSX content for rendering. This is assuming that verfiyResetPassword() is intended to run when the component is first mounted, instead of every time render() is called:
class App extends Component {
constructor() {
super();
this.state = {
isResetVerified: null,
loading: true
};
}
componentDidMount() {
this.verfiyResetPassword();
}
verfiyResetPassword() {
const params = (new URL(`http://dummy.com${this.props.location.search}`)).searchParams;
const code = params.get("oobCode")
auth.doVerfiyPasswordReset('foobar')
.then(() => {
this.setState({
...this.state,
isResetVerified: true,
loading: false
});
})
.catch(() => {
this.setState({
...this.state,
isResetVerified: false,
loading: false
});
})
}
getContent() {
if (this.state.loading) {
return (
<div>Loading...</div>
);
} else {
if (this.state.isResetVerified) {
return (
<div className="HomePage-Main">
<TopBar></TopBar>
<PWResetConfirmForm></PWResetConfirmForm>
</div>
);
} else {
return (
<div className="HomePage-Main">
<TopBar></TopBar>
<PWResetOutdatedForm></PWResetOutdatedForm>
</div>
);
}
}
}
Here is a basic example in action.
Also, in the constructor this.verfiyResetPassword = this.verfiyResetPassword.bind(this); would only be needed if verfiyResetPassword() is executed by a DOM event such as button onClick or similar.
Hopefully that helps!
I could still fix the error myself:
class PWResetConfirmPage extends Component {
constructor(props) {
super(props);
this.state = {
isValid: false,
code: "",
};
this.verfiyResetPassword = this.verfiyResetPassword.bind(this);
}
componentDidMount() {
const params = (new URL(`http://dummy.com${this.props.location.search}`)).searchParams;
const code = params.get("oobCode")
this.setState({code:code})
auth.doVerfiyPasswordReset(code)
.then(() => {
this.setState({
...this.state,
isValid: true,
});
})
.catch(() => {
this.setState({
...this.state,
isValid: false,
});
})
}
verfiyResetPassword() {
if (this.state.isValid) {
return (
<div>
<TopBar></TopBar>
<PWResetConfirmForm code={this.state.code}></PWResetConfirmForm>
</div>
);
} else {
return (
<div>
<TopBar></TopBar>
<PWResetOutdatedForm></PWResetOutdatedForm>
</div>
);
}
}
render() {
return (
<div className="HomePage-Main">
{this.verfiyResetPassword()}
</div>
);
}
}
export default PWResetConfirmPage
I want to practice my reactjs skills so I am doing some exercises with api calls to the popular openweathermap api
I am making the api call in the componentDidMount() cycle which following the documentation here, is the better place to make the async call
If I console.log(data) in render I first get undefined then I get the object that I need; but if I try to access said object with {data.city.name} which does exist I get the error "TypeError: Cannot read property 'name' of undefined" on the second re-render (componentDidMount() forces a second render)
I do not know what I am missing, I am not that experienced with lifecycles but it is pretty straightforward, I do not understand why this is happening
Can anyone enlighten me please? Thank you.
import React from 'react';
class WeekForecast extends React.Component {
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
data: {}
};
}
componentDidMount() {
fetch([url])
.then(res => res.json())
.then(
(result) => {
this.setState({
isLoaded: true,
data: result
});
},
(error) => {
this.setState({
isLoaded: true,
error
});
}
)
}
render() {
const { data } = this.state;
return(
<div>
<p>This weeks forecast in {data.city.name}</p>
</div>
)
}
}
export default WeekForecast;
You are seeing this error because you are trying to get a property from an object which is undefined. If you try to log a property which is undefined at that time is not a problem, but if you try to get a property from this undefined object then you get an error.
const data = {};
console.log( "city is", data.city );
console.log( "city name is", data.city.name );
Here is another example. What if we don't define an empty object for data? You are defining one in your state, but what if we don't?
//const data = {};
console.log( "city is", data.city );
console.log( "city name is", data.city.name );
Here since data is not defined, we can't get the city as undefined.
In your situation, data is defined in your state as an empty object. So, trying to log the city returns an undefined, but trying to log city.name returns error.
Because your data is landing in your component after the first render you should check you have it with a conditional rendering. You will use this all the time when you are dealing with the data which is coming from a remote place. There are many ways doing the conditional rendering. Here is one of them:
renderHelper() {
const { data } = this.state;
if ( !data.city ) { return <p>Loading data...</p>}
return <p>This weeks forecast in {data.city.name}</p>
}
render() {
return (
<div>
{ this.renderHelper() }
</div>
)
}
Hope the below snippet helps.
Couple of issues.
The Promise handle was misused.
Handling the data on the firs render (data validation before operating).
class WeekForecast extends React.Component {
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
data: {}
};
}
fetch = () => Promise.resolve({city:{name:"New York City"}})
componentDidMount() {
this.fetch()
.then(
(result) => {
this.setState({
isLoaded: true,
data: result
});
},
(error) => {
this.setState({
isLoaded: true,
error
});
}
)
}
render() {
const { data } = this.state;
return(
<div>
<p>This weeks forecast in {data.city && data.city.name || 'No City'}</p>
</div>
)
}
}
ReactDOM.render(<WeekForecast />, document.querySelector("#app"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id='app'/>
I have a stateful component I'm trying to test. This component serves as an intermediate step before it calls my other function. Kinda works like this
class MyList extends Component {
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
items: []
};
}
componentDidMount() {
this.props.loadMystuff().then(() => {
if (this.state.eKey !== this.props.eKey) {
let eKey = this.props.eKey;
this.fetchSList(eKey);
}
}).catch((error) => toast(error.message));
}
fetchSList(eKey) {
if (eKey !== '') {
fetch(`some_api_url_config/${this.props.entityKey}`)
.then(res => res.json())
.then(
(result) => {
this.setState({
isLoaded: true,
items: result
});
},
(error) => {
this.setState({
isLoaded: true,
error
});
}
);
}
}
render() {
const { error, isLoaded, items } = this.state;
if (items) {
return (
<div>
<h3>S List</h3>
<ul>
{items.map(item => (
<li key={item}>
{item}
</li>
))}
</ul>
</div>
);
} else if (error) {
return <div>Error: List Missing...{error.message}</div>;
} else if (!isLoaded) {
return <div>Loading List...</div>;
} else {
return <div>Not Found</div>;
}
}
}
the loadMystuff essentially gives me an eKey which I can use to call fetchSList, which calls fetch
I want to mock fetch and make it return an array of strings, but I haven't had any luck achieving that.
My test script looks like
describe(<MyList />, () => {
let wrapper;
let eKey = 'some_str';
it("should have some listed items", () => {
wrapper = shallow(<MyList loadMystuff={loadMystuff()}
eKey={eKey}/>
);
expect(wrapper.find("div").find("ul").find("li").length).toBeGreaterThan(0);
})
)
How do I make the fetch command return an array like ['abc', 'def', 'ghi']?
EDIT:
after reading https://medium.com/#ferrannp/unit-testing-with-jest-redux-async-actions-fetch-9054ca28cdcd
I have come up with
it("should have some listed items", () => {
window.fetch = jest.fn().mockImplementation(() =>
Promise.resolve(mockResponse(200, null, '["abc", "def"]')));
return store.dispatch(MyList(loadMystuff=loadMystuff(), eKey=eKey))
.then(() => {
const expectedActions = store.getActions();
console.log('expectedActions', expectedActions);
});
})
but this doesn't seem to work
EDIT2:
I'm now investigating fetch-mock package.
My function is still fetchSList where there's a fetch in it. I'm running the test as
let eKey = 'some_key';
describe(<MyList />, () => {
fetchMock.get('*', '["abc", "def"]');
it("should have some listed items", () => {
wrapper = shallow(<MyList loadMyStuff={loadMyStuff()}
eKey={eKey}/>
);
expect(wrapper.find("div").find("ul").find("li")).toBe('something_something');
and it's returning an object instead of some form of string. jest is nice enough to print out what's in the object and there's nothing I expect, which is ["abc", "def"]
You can mock the responses for HTTP requests with a library such as nock or fetch-mock.