Setting fetched data from API in state won't work - javascript

I'm trying to build a news/article website for education purposes using Django and ReactJS.
Currently, I've created an article model in Django and set up an API for ReactJS to talk to. Every article has a headline, image, content, featured and quickreads properties. featured and quickreads are boolean values. I've successfully set up my ReactJS component to fetch all the articles however, I'm having trouble filtering the articles that have article.featured as true and article.quickreads also as true. Currently my component has three states: articles, featured and quickreads. This is how it currently looks:
class Api extends React.Component{
constructor(){
super();
this.state = {
articles: null,
featured: null,
quickreads: null
}
}
componentDidMount(){
fetch("http://127.0.0.1:8000/articles/articlesapi/").then(request => request.json()).then(response => this.setState({articles: response}))
var featured = this.state.articles.filter(article => article.featured === true)
var quickreads = this.state.articles.filter(article => article.quickreads === true)
this.setState({featured: featured, quickreads: quickreads})
}
render(){
return (
<p>Hello World</p>
)
}
}
Although the component gets all the articles it fails to update featured and quickreads. I get the following error:
Uncaught TypeError: Cannot read property 'articles' of undefined at componentDidMount (eval at <anonymous>)...
Why is this happening?

fetch is asynchronous, and thus articles is not set (and is null) when you try to filter it to set state. Instead, wait until the data is fetched:
fetch("http://127.0.0.1:8000/articles/articlesapi/")
.then(request => request.json())
.then(response => {
this.setState({
articles: response
featured: response.filter(article => article.featured === true),
quickreads: response.filter(article => article.quickreads === true)
});
});
And filter and set state along with setting articles after the data is fetched. I would, though, only store articles in state, and filtering when you need to do you don't end up having to sync up all the arrays to make sure they have the same data.

Related

React Class passing stale props to Child stateless component

My Parent Component represents a form.
The users filling in the form have access to information in the form that is updated in real time as they update certain fields.
The Issue I am running into is. On one of these updates when we fetch the new data and pass it to the child randomly sometimes the child is receiving stale props. From the previous request.
The structure is something like this.
export class Form extends React.Component<Props, State> {
fetchUpdates = async (payload) => {
this.setState({ isLoadingUpdates: true })
await Service.getUpdates(payload)
.then(response => {
this.setState({ isLoadingUpdates: false, updates: response.data })
})
.catch(({ data: errors }) => this.setState({ isLoadingUpdates: false }))
}
}
render () {
const {
updates,
isLoadingUpdates,
} = this.state
<FormCombobox
onChange={this.fetchUpdates}
md={10}
name="field"
id="field"
label="Field"
onMenuOpen={() => forceCheck()}
openMenuOnClick
selectRef={this.itemSelect}
value={values.item}
options={itemOptions || []}
/>
<Info
data={updates}
errorMessage={this.state.updatesError}
/>
}
}
It doesn't occur every time but randomly either when the form is first updated or on one of the following updates the < Info > container recieves the previous requests response data. How can I stop the parent from passing stale data?
The problem here is that when fetchUpdates is called multiple times it gets out of order due to network delay. Let's say fetchUpdates is called three times, and let's say the request takes 5, 2 and 4 seconds respectively to complete. In this case, you can see that the second request calls setState before the first request. As a result, the info component gets passed the first value after the second value. This is the reason why it is intermittent.
Using await here won't help, because the fetchUpdates function calls are independent of each other.
One more thing, I noticed that you have isLoadingUpdates. But it's not being used anywhere in the code. And also doing,
if (!this.state. isLoadingUpdates) {
await Service.getUpdates(payload)
.then(response => {
this.setState({ isLoadingUpdates: false, updates: response.data })
})
.catch(({ data: errors }) => this.setState({ isLoadingUpdates: false }))
}
won't work because then it means you will miss keypresses when the network call is ongoing.
I would suggest using a debounce for the inputs. You can find how to do debounce here: Perform debounce in React.js

React Firebase read element

I'm trying to get the value stored in 'Nombre', but it doesn't work at all, am I doing something wrong?
class Appp extends React.Component {
constructor() {
super()
this.state = { name: 'hey' }
}
componentDidMount() {
const nameRef = firebase.database().ref().child('Anonimos').child('wx3czBh22dMQUTNDwD9l').child('Nombre')
nameRef.on('value', snapshot => {
this.setState({
name: snapshot.val()
})
})
}
render() {
return <h1>{this.state.name}</h1>
}
}
Your code uses the API for the Firebase Realtime Database. But the screenshot shows data in Cloud Firestore. While both databases are part of Firebase, they are completely separate and the API for one can't access the data in the other.
To access the data in Cloud Firestore, follow the documentation for that database: https://firebase.google.com/docs/firestore/quickstart
Something like:
componentDidMount() {
const nameRef = firebase.firestore().collection('Anonimos').doc('wx3czBh22dMQUTNDwD9l')
nameRef.onSnapshot(doc => {
this.setState({
name: doc.data().Nombre
})
})
}
Aside from the new syntax, the biggest change here is that this code reads the entire document, instead of just the Nombre field, and then sets just the name in the state. So while the end result is the same, this loads more data than your Realtime Database example would, since that API allow the loading of any node, while Cloud Firestore always loads complete documents.

JSON Object Property not returnable - "Cannot Read Property of Undefined"

Ok, so I'm building out a custom API in React. When I make the calls, I'm getting JSON data back and store that into local storage with JSON.Stringify:
localStorage.setItem('user', JSON.stringify(response.data))
Later, I call this item onto the Homepage to return some of that data once the user is logged in using:
var user = JSON.parse([localStorage.getItem('user')])
This returns the object:
{
"OrderId":0,
"IsLoggedIn":true,
"ModeOfSaleId":64,
"OriginalModeOfSaleId":64,
"SourceId":8580,
"LoginInfo":{"ConstituentId":190554,"OriginalConstituentId":190554,"UserId":"test#email.org","Status":"P","FailedAttempts":0,"LockedDate":null,"ElectronicAddress":"test#email.org"},
"CartInfo":{"PerformanceCount":0,"PackageCount":0,"ContributionCount":0,"MembershipCount":0,"UserDefinedFeeCount":0,"GiftCertificateCount":0,"PaymentCount":0,"FirstSeatAddedDateTime":null},
"BusinessFacing":false,
"IsGuest":false,
"CheckoutStatus":{"Status":"No Checkout","Date":null},
"HasLockedSeats":false,
"SeatsExpired":false
}
The Issue:
Un-nested properties return normally {user.OrderId} or {user.ModeOfSaleId} However, trying to return the nested values like {user.LoginInfo.ConstituentID} result in the error:
Uncaught TypeError: Cannot read property 'ConstituentId' of undefined
Returning {user.LoginInfo} actually returns an object, but obviously, can't print that to a string. Returning {user.LoginInfo["ConstituentId"]} results in the error:
Uncaught TypeError: Cannot read property 'ConstituentId' of undefined
So yeah, I'm stumped, I don't know how I'm returning this incorrectly. Any help is appreciated.
This code works for me:
localStorage.setItem("user", JSON.stringify({
"OrderId":0,
"IsLoggedIn":true,
"ModeOfSaleId":64,
"OriginalModeOfSaleId":64,
"SourceId":8580,
"LoginInfo":{"ConstituentId":190554,"OriginalConstituentId":190554,"UserId":"test#email.org","Status":"P","FailedAttempts":0,"LockedDate":null,"ElectronicAddress":"test#email.org"},
"CartInfo":{"PerformanceCount":0,"PackageCount":0,"ContributionCount":0,"MembershipCount":0,"UserDefinedFeeCount":0,"GiftCertificateCount":0,"PaymentCount":0,"FirstSeatAddedDateTime":null},
"BusinessFacing":false,
"IsGuest":false,
"CheckoutStatus":{"Status":"No Checkout","Date":null},
"HasLockedSeats":false,
"SeatsExpired":false
}));
const user = JSON.parse(localStorage.getItem("user"));
console.log(user.LoginInfo.OriginalConstituentId);
What about using spread operator to get what you want?
const user = JSON.parse(localStorage.getItem('user'))
const { ConstituentId, UserId } = user.LoginInfo
console.log(ConstituentId) // 190554
Ok, so the way I'm returning these values seems to be an "issue" because of the way React handles it's Render event. When I'm pulling in the data on the componentDidMount() event, a Render event still fires just before this.
componentDidMount() {
this.setState({
user: JSON.parse([localStorage.getItem('user')]),
users: { loading: true }
});
}
So in the Render event:
render() {
const { user, users, loading } = this.state;
var { ConstituentId, UserId } = user.LoginInfo
return (
<div className="col-md-6 col-md-offset-3">
<h1>Hi!</h1>
<p>{UserId}</p>
<p>You're logged in with React & Basic HTTP Authentication!!</p>
<h3>Users from secure api end point:</h3>
<p>
<Link to="/login">Logout</Link>
</p>
</div>
);
}
It fires TWICE, once BEFORE the state.user is set by componentDidMount() and once again after. So, my code was erroring out because of the first firing of Render, when nothing was set, hence the undefined message. I figured out how to bypass this with checking the login info object is returning as typeof object. This is in my render event:
var result = (typeof user.loginInfo === 'object');
if (result && loading) {
console.log(result)
console.log(user.LoginInfo.ConstituentId)
var { ConstituentId, UserId } = user.LoginInfo
}
But that's not very elegant. So, ultimately I re-wrote how I was handling unloaded information in componentDidMount() by creating a state prop called 'loading':
this.state = {
loading: true,
user: {}
};
In componentDidMount() I'm doing this:
this.setState({
user: JSON.parse(localStorage.getItem('user')),
loading: false
});
And in render():
const { loading, user } = this.state;
if (!loading) {
var { ConstituentId, UserId } = user.LoginInfo
}
console.log(ConstituentId)
It works great!
Basically, I'm just waiting for componentDidMount() to fire using the loading state by setting it to false in the function. Then we know it's loaded and can successfully render the data.

React JS this.props.state is not a function?

Don't understand what is wrong with my statement here, I keep looking up the error message on google but i really don't understand the problem.
componentDidMount()
{
fetch('http://192.168.1.33:8080/getprojects/')
.then(response => response.json())
.then(data => {
this.props.state({
projects: data.name
});
});
}
according to chrome's console it says that
Uncaught (in promise) TypeError: _this2.props.state is not a function
and points to this:
this.props.state({
projects: data.name
});
I am lost here. New to React JS trying to create a website that fetches data constantly (here i'm trying to fill a list under the format ul li by getting names on my Node Express server)
EDIT
here is the complete code before the Return inside the Render function :
class ProjectList extends Component {
constructor (props) {
super (props);
this.state = {
projects: [],
};
}
componentDidMount()
{
fetch('http://192.168.1.33:8080/getprojects/')
.then(response => response.json())
.then(data => {
this.setState({projects: data.name})
});
}
render () {
let projects = this.state.projects;
let liItems = projects.map((project) =>
<li key={project.name}>{project.name}</li>
);
after that it's just basic HTML
You are getting property undefined, because the render function runs before the componentDidMount function. So when it first try to run, the first thing it encounters is this: let projects = this.state.projects; and you defined it initially, it's an empty array. So within your render function, remove the line above and replace with:
if (this.state.projects.length === 0){
return <div>Loading</div>
}
So when initially your component will run it will render for a split second the Loading div, and when your array is populated your component with your array will be rendered.
let liItems = projects.length && projects.map((project) =>
<li key={project.name}>{project.name}</li>
);
Change like this and in render
render(){
return(
<ul>{liItems}</ul>
)
}
If you dont have any projects it renders empty.

Double rendering in React with asynchronous call in componentDidMount causing error

I'm building a blog application that has an articles index page, and from there you can click on an article to see the article or edit the article.
If you're going from the index page to the edit page, it works just fine because I already have all the articles in state. But if I refresh after I've gone to the edit-article page, I no longer have all the articles in state.
This is a problem because I'm making an asynchronous recieveSingleArticle call in the componentDidMount of my edit-article page, then I setState so my form is prepopulated. There's a double render which causes an "Uncaught TypeError: Cannot read property 'title' of undefined" error, presumably during the first render before the article has been received into state.
class ArticleEdit extends React.Component {
constructor(props) {
super(props);
this.state = {title: "", body: "", imageFile: ""};
this.handleChange = this.handleChange.bind(this);
this.handlePublish = this.handlePublish.bind(this);
this.handleFile = this.handleFile.bind(this);
this.handleCancel = this.handleCancel.bind(this);
}
componentDidMount() {
const { article, requestSingleArticle } = this.props;
requestSingleArticle(this.props.match.params.articleID)
.then(() => {
this.setState({
title: article.title,
body: article.body,
imageFile: article.imageFile
});
});
}
...
I tried wrapping my async calls inside of an "if (this.props.article)" but that didn't work. Is there a best way of dealing with this type of problem? Any advice greatly appreciated!
UPDATE:
Another solution that works is to have a componentDidUpdate in addition to componentDidMount. check in componentDidMount if this.props.article exists and if so, setState. And in componentDidUpdate, wrap the setState in the following conditional:
if (!prevProps.article && this.props.article)
Just check if the article is present in the props before calling async action
componentDidMount() {
const { article, requestSingleArticle } = this.props;
if (!(article && requestSingleArticle)) return; // this line
requestSingleArticle(this.props.match.params.articleID)
.then(() => {
this.setState({
title: article.title,
body: article.body,
imageFile: article.imageFile
});
});
}
Since you are not getting any render from this method , it means that the props are not yet obtained in the life cycle method componnetDidMount. So instead you can use componentWillReceiveProps like this
componentWillReceiveProps(nextProp) {
// this line here will check the article props' status so that
// we will not use setState each time we get a prop
if (this.props.article === nextProp.article) return;
// rest is just the same code from above
const { article, requestSingleArticle } = nextProp;
if (!(article && requestSingleArticle)) return; // this line
requestSingleArticle(this.props.match.params.articleID)
.then(() => {
this.setState({
title: article.title,
body: article.body,
imageFile: article.imageFile
});
});
}

Categories

Resources