I am trying to print data from fetched JSON but somehow i am unable to do it.
interface IFetched {
fetchedData: any;
error: any;
}
export default class Testing100 extends React.Component<
ITesting100Props,
IFetched,
{}
> {
constructor(props: ITesting100Props) {
super(props);
this.state = {
error: null,
fetchedData: [],
};
}
public componentDidMount() {
fetch("https://api.randomuser.me/")
.then((res) => res.json())
.then(
(result) => {
this.setState({
fetchedData: result,
});
},
(error) => {
this.setState({
error,
});
}
);
}
public render(): React.ReactElement<ITesting100Props> {
console.log(this.state.fetchedData.results);
return (
<div>
<a>
{this.state.fetchedData.map(function (fetchedDataX) {
return fetchedDataX.results[0].name.first;
})}
</a>
</div>
);
}
}
With console log i am able to print data. But when i change console log from console.log(this.state.fetchedData.results); to console.log(this.state.fetchedData.results[0]); i get nothing. And even that console log is called twice as you can see in console output i dont know why.
But my goal is to print the first name of person into <a> element and I just don't know how. Hope somebody can help me with this. Thanks for your time.
Think about the state of the app before the fetch occurs - the fetchedData array is empty. Then when you fetch, you are converting it into an object, with a results field that is an array.
Your code needs to be able to handle both of these states. Don't try to use or log a field of something that you haven't first verified actually exists, or it will crash.
First step is to clean it up so you directly just update the array in the state -
Your map is not working because fetchedData has an inner results field - try this.setState({fetchedData: result.results});, and then console.log(this.state.fetchedData).
Also you might want to add some guards to the top of your render so that things don't crash when the fetchedData is empty or errored:
if (this.state.fetchedData === []) return <p>"Nothing Loaded"</p>;
if (this.state.error !== null) return <p>{this.state.error.message}</p>;
As for the double output to the console, that is because the render method get run first when the component is mounted, and you see the output where the fetchedData is empty, and then componentDidMount runs (which fetches the data and updates the state) and then React re-renders with the new state, causing the second console log.
Your console log that tries to access the .result[0] fails because it doesn't exist for this first pre-fetch state. Check (with an if) it is there before logging, or log the whole state obj.
Related
I have a task to call an API, save the API data into local storage, and retrieve that same data when a page is reloaded.
However, the API gets called on every F5 and the data it contains gets randomized, (because API returns a bunch of random facts). So i have the Mount() and Update(), the data is in local storage, but i can't get it to stay and retrieve from there.
I've tried some if statements inside Mount(), for example to check length, and the like, but none worked. Help, I don't know what I'm doing wrong. This is the code:
import React from "react"
class LogicContainer extends React.Component {
constructor() {
super()
this.state = {
isLoading: true,
allFacts: []
}
)
}
//Call to API, get facts, put them into local storage.
componentDidMount() {
fetch("https://catfact.ninja/facts?limit=1000")
.then(response => response.json())
.then(response => {
const allFactsApi = response.data
this.setState({
isLoading: false,
allFacts: allFactsApi
})
localStorage.setItem("allFacts", JSON.stringify(allFactsApi))
})
console.log(this.state.allFacts.length)
}
/*check if facts have changed on reload and save them in tempFacts*/
componentDidUpdate(prevProps, prevState) {
if(prevState.allFacts !== this.state.allFacts) {
const tempFacts = JSON.stringify(this.state.allFacts)
localStorage.setItem("allFacts", tempFacts)
}
}
render() {
return(
<div></div>
)
}
}
export default LogicContainer;
You are setting the data into the localStorage properly. But the problem is you are never actually retrieving it. You are just placing it in the state but the state will reset when the lifecycle of your component comes to an end (when F5 comes along for example). This is the code for retrieving data in localStorage:
localStorage.getItem("allFacts")
Bare in mind: When a user refreshes the website, the "componentDidUpdate" is not going to run, given that the page is actually reloading from scratch. What you should actually be doing is adding the snippet of code mentioned above on your "componentDidMount()" to check wether previous data exists in localStorage or if it should be retrieved from the API. If the data exists, then set the state to that. Otherwise, fetch the data from the API as you are doing now:
componentDidMount() {
// Check if localStorage has an assigned value for item "allFacts"
if(localStorage.getItem("allFacts")!== null){
// Set the state to that
this.setState({
isLoading: false,
allFacts: localStorage.getItem("allFacts")
})
// Otherwise, retrieve it from the API and set the state as well as the localStorage
}else{
fetch("https://catfact.ninja/facts?limit=1000")
.then(response => response.json())
.then(response => {
const allFactsApi = response.data
this.setState({
isLoading: false,
allFacts: allFactsApi
})
localStorage.setItem("allFacts", JSON.stringify(allFactsApi))
})
}
console.log(this.state.allFacts.length)
}
I need to fetch the user data and display it. I am getting an error now that says
TypeError: this.unsubscribe is not a function
and when I initialise it as a normal variable like const db, then I get another error
Function CollectionReference.doc() requires its first argument to be of type non-empty string
import React from "react";
import { auth, firestore } from "../../firebase/firebase.utils";
export default class UserPage extends React.Component {
state = {
user: {}
};
unsubscribe = null;
componentDidMount() {
const user = auth.currentUser;
this.unsubscribe = firestore
.collection("users")
.doc(user)
.onSnapshot(doc => {
this.setState = {
user: doc.data()
};
});
}
componentWillMount() {
this.unsubscribe();
}
render() {
return (
<div>
<h1>{this.state.user.name}</h1>
</div>
);
}
}
when I insinalise it as a normal variable like const db
Not quite sure what you mean by this, but if you're getting an error about the type of unsubscribe, I suggest using console.log right before you call it to view its value.
Bear in mind that componentWillMount happens in the lifecycle before componentDidMount (hence the names will and did). I suspect that's one of your problems: you try to call unsubscribe before setting the value.
With regard to your other error about the doc call, it's likely referring to:
...
.collection("users")
.doc(user) <-- this line
.onSnapshot(doc => {
this.setState = {
us
...
As the error output states, that user variable (the first argument of doc) must be a string, and it can't be an empty string.
I don't see user anywhere in your code, so I expect that it's currently the value undefined. You could access this.state.user here, but I'd strongly advise against it since you subsequently set that state in the call (probably cause an infinite loop).
What is your end goal? What have you tried to resolve these two issues? Maybe adding that to your question would help us assist you better.
I work by a tutorial to create a fullstack react web app with backend connected to mongo , now while trying to merge it with my previous code I getting a syntax error..
i try to search in google but none of it help
this is my console error
module build failed(from ./node_modules/babel-loader/lib/index.js);
syntaxError: c:/users/aviram/zofim/client/src/app.js: unexpected token (49:16)
49| getDataFromDb = () => {
this is my code
import React, { Component } from 'react';
import axios from 'axios';
class App extends Component {
// initialize our state
constructor(props){
super(props);
this.state = {
data: [],
id: 0,
message: null,
intervalIsSet: false,
idToDelete: null,
idToUpdate: null,
objectToUpdate: null
};
this.getDataFromDb = this.getDataFromDb.bind(this);
}
// when component mounts, first thing it does is fetch all existing data in our db
// then we incorporate a polling logic so that we can easily see if our db has
// changed and implement those changes into our UI
componentDidMount() {
this.getDataFromDb();
if (!this.state.intervalIsSet) {
let interval = setInterval(this.getDataFromDb, 1000);
this.setState({ intervalIsSet: interval });
}
}
// never let a process live forever
// always kill a process everytime we are done using it
componentWillUnmount() {
if (this.state.intervalIsSet) {
clearInterval(this.state.intervalIsSet);
this.setState({ intervalIsSet: null });
}
}
// just a note, here, in the front end, we use the id key of our data object
// in order to identify which we want to Update or delete.
// for our back end, we use the object id assigned by MongoDB to modify
// data base entries
// our first get method that uses our backend api to
// fetch data from our data base
getDataFromDb = () => {
fetch('http://localhost:3001/api/getData')
.then((data) => data.json())
.then((res) => this.setState({ data: res.data }));
};
I`d like it to compile
To solve your problem change this line getDataFromDb = () => { to getDataFromDb(){.
This is happening because you don't have class properties configured on your build configurations, a plugin for babel: https://babeljs.io/docs/en/babel-plugin-proposal-class-properties
Note that this syntax getDataFromDb = () => { is not approved yet, the proposal and more info on class properties: https://github.com/tc39/proposal-class-fields
Actually the problem is here,
let interval = setInterval(this.getDataFromDb, 1000);
You should call your getDataFromDb function like this,
let interval = setInterval(this.getDataFromDb(), 1000);
Also as you are using arrow function, no need to bind this,
this.getDataFromDb = this.getDataFromDb.bind(this); //You can remove this line
Demo.
Note:: Here fetch call changed to some other URL to get it work.
If you are using arrow function then there is no need to bind. So remove this.getDataFromDb = this.getDataFromDb.bind(this); this line it will work fine.
You forgot to complete closing bracket } at the end. If you are using arrow function then there is no need of bind so remove this line 'this.getDataFromDb = this.getDataFromDb.bind(this);'
I've found myself at a bit of dead end here. I'll try to explain it as best as I can.
I'm using base username routing on my app, meaning that website.com/username will route to username's profile page. Everything works fine apart from one issue that I'll get to below.
This is how I'm doing it (very short version) :
This route looks for a potential username and renders the Distribution component.
<Route exact path="/([0-9a-z_]+)" component={Distribution} />
Distribution component then extracts the potential username from the pathname...
username = {
username: this.props.location.pathname.substring(1)
};
...And then fires that off to my API which checks to see if that username actually exists and belongs to a valid user. If it does it returns a user object, if not it returns an error.
if (username) {
axios
.post(API_URI + '/users/get/profile', JSON.stringify(username))
.then(res => {
this.setState({
ready: true,
data: res.data,
error: ''
});
})
.catch(err => {
this.setState({
ready: true,
data: '',
error: err.response
});
});
}
All of the above is happening inside componentWillMount.
I then pass the relevant state info as props to the relevant child component in the render :
render() {
if (this.state.ready) {
if (this.state.data) {
return <UserProfile profile={this.state.data} />;
}
if (this.state.error) {
return <Redirect to="notfound" />;
}
}
return null;
}
As I mentioned, this all works perfectly when moving between all of the other routes / components, but it fails when the Distribution component is called while already IN the Distribution component. For example, if you are already looking at a valid profile (which is at Distribution > UserProfile), and then try to view another profile, (or any other malformed username route that would throw an error), the API call isn't getting fired again so the state isn't being updated in the Distribution component.
I originally had it all set up with a Redux store but had the exact same problem. I wrongly thought that componentDidMount would be fired every single time the component is called for the first time, and I assumed that throwing a new url at it would cause that but it doesn't.
I've tried a bunch of different ways to make this work (componentWillReceiveProps etc) but I just can't figure it out. Everything I try throws depth errors.
Am I missing a magical piece of the puzzle here or just not seeing something really obvious?
Am I going about this entirely the wrong way?
You on the right path when you tried to use componentWillReceiveProps. I would do something like the following:
componentDidMount() {
this.refresh()
}
componentWillReceiveProps(prevProps) {
if(prevProps.location.pathname !== this.props.location.pathname) {
this.refresh(prevProps)
}
}
refresh = (props) => {
props = props || this.props
// get username from props
// ...
if (username) {
// fetch result from remote
}
}
I have the following problem: I've written a 'detailview' component, that takes a PK from a list and uses it to fetch the right object from the API. As this takes some time it will display 'Loading' by default.
My render looks like this:
render() {
if (!this.store_item) {
return <h2>loading</h2>
}
return(
<div>
<h2>{this.state.store_item.results.title}</h2>
<p>{this.state.store_item.results.description}</p>
</div>
)
}
The 'store_item' is loaded when the component mounts:
componentDidMount() {
this.LoadApi();
}
The 'LoadApi' method has the following code:
LoadApi() {
const new_url = encodeURI(this.state.store_item_api + this.props.detail_pk);
fetch(new_url, {
credentials: 'include',
})
.then(res => res.json())
.then(result =>
this.setState({
store_item: result,
}));
}
However, when I run this code in my browser all I see is 'Loading'. Yet when I check the state in the chromium react extension I can see that 'store_item' has been set (I also verified that the api call went as planned in the network tab). As the state has been set (and is being set in 'LoadApi') my understanding was that this should trigger an re-render.
Does anyone know why, despite all this, the component keeps returning 'loading'?
You have typo, missed state keyword
if (!this.store_item) { ... }
^^^^^^^^^^^^^^^^^^
Should be
if (!this.state.store_item) { ... }
Because it's defined as component state instead of component static property.