i'm working with react to complete the front end of a rest application.
I have json being sent to the front end, and I use fetch .
fetch('/task')
.then(function(data) {
return data.json();
})
.then(function(json) {
json.tasks.forEach(function(task) {
console.log(task.name)
})
});
So i'm able to console.log each task.name, but where to now? How do I get my component to display each task as a ?
Basically, where in a component does this type of logic go? Do i save the fetch request to a variable and then setState = variable?
this is my component:
class Task extends React.Component {
render() {
return <p> hey </p>
}
}
You need to initialize a state object, which you can update when the fetch is complete:
class Task extends React.Component {
constructor () {
super()
this.state {
tasks: null
}
}
componentDidMount () {
fetch('/task')
.then((data) => {
return data.json()
})
.then((json) => {
this.setState({ tasks: json.tasks })
})
}
renderTaskList () {
if (this.state.tasks) {
return (
<ul>
{this.state.tasks.map((task, i) => <li key={i}>{task.name}</li>)}
</ul>
)
}
return <p>Loading tasks...</p>
}
render () {
return (
<div>
<h1>Tasks</h1>
{this.renderTaskList()}
</div>
)
}
}
Edit: Re-reading this answer, I just wanted to note that it is not necessary to initialize the tasks property of the state object in this case. You could also just do something like:
this.state = {}
However, I think there is some value in explicitly naming the various properties of your state object, even if they are initialized as null. This allows you to write components whose state is documented in the constructor, and will prevent you or your teammates from later guessing how a component's state is modeled.
Related
In my React.js app, I am fetching a quote from API and storing it in the state object, When trying to access the properties of the object which is stored in state. Returning null.
Code:
import React, { Component } from "react";
export default class Quotes extends Component {
constructor(props) {
super(props);
this.state = {
quote: null,
};
}
componentDidMount() {
fetch("https://quote-garden.herokuapp.com/api/v2/quotes/random")
.then((response) => response.json())
.then((quote) => this.setState({ quote: quote.quote }))
.catch((error) => console.log(error));
}
render() {
console.log(this.state.quote.quoteText); //This is returning null
console.log(this.state.quote); //This is working fine.
return (
<div>
<p>// Author of Quote</p>
</div>
);
}
}
I am new to React.js, I spining my head for this problem around 2 hourse. I didn't get any solution on web
output showing quote object
When I try to console.log(this.state.quote) it prints out this output, well it is fine.
and when I try console.log(this.state.quote.quoteText) It will return can not read property
output showing can not find property
You must note the you are trying to fetch data asynchronously and also you fetch data in componentDidMount which is run after render, so there will be an initial rendering cycle wherein you will not have the data available to you and this.state.quote will be null
The best way to handle such scenarios is to maintain a loading state and do all processing only after the data is available
import React, { Component } from "react";
export default class Quotes extends Component {
constructor(props) {
super(props);
this.state = {
quote: null,
isLoading: true,
};
}
componentDidMount() {
fetch("https://quote-garden.herokuapp.com/api/v2/quotes/random")
.then((response) => response.json())
.then((quote) => this.setState({ quote: quote.quote, isLoading: false }))
.catch((error) => console.log(error));
}
render() {
if(this.state.isLoading) {
return <div>Loading...</div>
}
console.log(this.state.quote.quoteText); //This is returning proper value
console.log(this.state.quote);
return (
<div>
<p>// Author of Quote</p>
</div>
);
}
}
When you are accessing the quoteText without checking whether the key in the object is present or not it throws an error -
render(){
if(this.state.quote && this.state.quote.hasOwnProperty('quoteText')){
console.log(this.state.quote.quoteText);
}
}
I am fairly new to JavaScript/React/NextJS. I have written a React app and I am now trying to convert it so that all of it renders on the server.
I went through the tutorials on https://nextjs.org/learn/basics/getting-started and then i tried to apply that knowledge to my app.
I have the following requirements in my app
I have a master detail scenario. The main page renders a list of articles, clicking on one of those articles takes the user to articles details.
I want to have clean urls, such as "/article/123" and not "article?id=123".
I am using a 3rd party library to get the data from the server, therefore I have to use promises to get the data
I need to use Class Components not functional components (for other reasons can not move to functional components).
I want all the rendering to occur on the server not the client
I am unclear how in "getInitialProps" i would call my async method in my 3rd party library and return the data. Nothing i try is working and googling hasn't helped.
My project structure is
- components
|- header.js
|- footer.js
|- layout.js
- pages
|- index.js
|- article
|- [id].js
- scripts
|- utils.js
index.js just renders 2 links for 2 articles
export default function Index() {
return (
<div>
<Link href="/article/[id]" as={`/article/1`}>
<a>Article 1</a>
</Link>
<Link href="/article/[id]" as={`/article/2`}>
<a>Article 2</a>
</Link>
</div>
);
}
utils.js has a util method to call the 3rd party library method
export function getDataFromThirdPartyLib(articleId) {
return thirdPartyLib.getMyData({
"articleId" : articleId,
}).then(function (item) {
return item;
});
}
article/[id].js has
function getData(articleId){
console.log("getData")
getDataFromThirdPartyLib(articleId)
.then((item) => {
console.log("got data")
return {
item: item
}
});
}
class ArticleDetails extends React.Component {
static async getInitialProps(ctx) {
const data = await getData(articleId);
// I also tried the following
// const data = getData(articleId);
return{
data : data
}
}
render() {
console.log("IN RENDER")
console.log("this.props.data") // THIS IS UNDEFINED
// my HTML
}
}
Problem :
console logging shows that the data is always undefined in render(). I can see my logging from getting the data and data is obtained, but these render after my render logging, so render is not refreshed once the data is obtained.
Different attempt 1
I tried having "getData" inside my component definition (as apposed to outside it) but this always fails as it says the "getData" is not a function.
class ArticleDetails extends React.Component {
static async getInitialProps(ctx) {
const data = await getData(articleId); // FAILS AS SAYS getData is not a function
return{
data : data
}
}
getData(articleId){
console.log("getData")
getDataFromThirdPartyLib(articleId)
.then((item) => {
return {
item: item
}
});
}
render() {
console.log("IN RENDER")
console.log(this.props.data) // THIS IS UNDEFINED
// my HTML
}
}
Different attempt 2
I tried having the logic in "getData" directly in "getInitialProps" but that also doesn't work as it says getInitialProps must return an object.
class ArticleDetails extends React.Component {
static async getInitialProps(ctx) {
getDataFromThirdPartyLib(articleId)
.then((item) => {
return {
data: data
}
});
}
render() {
console.log("IN RENDER")
console.log(this.props.data) // THIS IS UNDEFINED
// my HTML
}
}
Working version - but not SSR
The only way i can get this to work is to write it like a class component in a React App. But I believe "componentDidMount" is not called on the server, its called in the Client. So this defeats my attempt of getting everything to render in the server.
class ArticleDetails extends React.Component {
state = {
item: null,
componentDidMount() {
this.getData();
}
getData(articleId){
console.log("getData")
getDataFromThirdPartyLib(articleId)
.then((item) => {
self.setState({
item: item
}
});
}
render() {
console.log("IN RENDER")
console.log(this.state.item) // THIS HAS A VALUE
// my HTML
}
}
SOLUTION as provided by Steve Holgado
The problem was that my "getData" method was not returning the promise as Steve said.
However my "getData" method was a little more complex than I put in this post originally which was why when I first tried Steve's solution it did not work.
My getData is nesting two promises. If i put the return on BOTH promises it works perfectly.
function getData(articleId){
return getDataFromThirdPartyLib(articleId)
.then((item) => {
return getSecondBitOfDataFromThirdPartyLib(item)
.then((extraInfo) => {
return {
item: item,
extraInfo : extraInfo
}
});
return {
item: item
}
});
}
You need to return the promise from getData in article/[id].js:
function getData(articleId){
// return promise here
return getDataFromThirdPartyLib(articleId)
.then((item) => {
return {
item: item
}
});
}
...otherwise undefined is returned implicitly.
Also, where are you getting articleId from?
Is it supposed to come from the url?
static async getInitialProps(ctx) {
const articleId = ctx.query.id;
const data = await getData(articleId);
return {
data: data
}
}
Hope this helps.
I'm learning react and it's great, but i've ran into an issue and i'm not sure what the best practice is to solve it.
I'm fetching data from an API in my componentDidMount(), then i'm setting some states with SetState().
Now the problem is that because the first render happens before my states have been set, im sending the initial state values into my components. Right now i'm setting them to empty arrays or empty Objects ({ type: Object, default: () => ({}) }).
Then i'm using ternary operator to check the .length or if the property has a value.
Is this the best practice or is there some other way that i'm unaware of?
I would love to get some help with this, so that i do the basics correctly right from the start.
Thanks!
I think the best practice is to tell the user that your data is still loading, then populate the fields with the real data. This approach has been advocated in various blog-posts. Robin Wieruch has a great write up on how to fetch data, with a specific example on how to handle loading data and errors and I will go through his example here. This approach is generally done in two parts.
Create an isLoading variable. This is a bolean. We initially set it to false, because nothing is loading, then set it to true when we try to fetch the data, and then back to false once the data is loaded.
We have to tell React what to render given the two isLoading states.
1. Setting the isLoading variable
Since you did not provide any code, I'll just follow Wieruch's example.
import React, { Component } from 'react';
class App extends Component {
constructor(props) {
super(props);
this.state = {
dataFromApi: null,
};
}
componentDidMount() {
fetch('https://api.mydomain.com')
.then(response => response.json())
.then(data => this.setState({ dataFromApi: data.dataFromApi }));
}
...
}
export default App;
Here we are using the browser's native fetch() api to get the data when the component mounts via the use of componentDidMount(). This should be quite similar to what you are doing now. Given that the fetch() method is asynchronous, the rest of the page will render and the state will be up dated once the data is received.
In order to tell the user that we are waiting for data to load, we simply add isLoading to our state. so the state becomes:
this.state = {
dataFromApi: null,
isLoading: false,
};
The state for isLoading is initially false because we haven't called fetch() yet. Right before we call fetch() inside componentDidMount() we set the state of isLoading to true, as such:
this.setState({ isLoading: true });
We then need to add a then() method to our fetch() Promise to set the state of isLoading to false, once the data has finished loading.
.then(data => this.setState({ dataFromAPi: data.dataFromApi, isLoading: false }));
The final code looks like this:
class App extends Component {
constructor(props) {
super(props);
this.state = {
dataFromApi: [],
isLoading: false,
};
}
componentDidMount() {
this.setState({ isLoading: true });
fetch('https://api.mydomain.com')
.then(response => response.json())
.then(data => this.setState({ dataFromApi: data.dataFromApi, isLoading: false }));
}
...
}
export default App;
2. Conditional Rendering
React allows for conditional rendering. We can use a simple if statement in our render() method to render the component based on the state of isLoading.
class App extends Component {
...
render() {
const { hits, isLoading } = this.state;
if (isLoading) {
return <p>Loading ...</p>;
}
return (
<ul>
{dataFromApi.map(data =>
<li key={data.objectID}>
<a href={data.url}>{data.title}</a>
</li>
)}
</ul>
);
}
}
Hope this helps.
It Depends.
suppose you are fetching books data from server.
here is how to do that.
state = {
books: null,
}
if, your backend api is correctly setup.
You will get either empty array for no books or array with some length
componentDidMount(){
getBooksFromServer().then(res => {
this.setState({
books: res.data
})
})
}
Now In Your render method
render() {
const { books } = this.state;
let renderData;
if(!books) {
renderData = <Spinner />
} else
if(books.length === 0) {
renderData = <EmptyScreen />
}
else {
renderData = <Books data = { books } />
}
return renderData;
}
If you are using offline data persistence In that case initially you won't have empty array.So This way of handling won't work.
To show the spinner you have to keep a variable loader in state.
and set it true before calling api and make it false when promise resolves or rejects.
finally read upon to state.
const {loader} = this.state;
if(loader) {
renderData = <Spinner />
}
I set initial state in constructor. You can of course set initial state of component as static value - empty array or object. I think better way is to set it using props. Therefore you can use you component like so <App items={[1,2,3]} /> or <App /> (which takes value of items from defaultProps object because you not pass it as prop).
Example:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
class App extends Component {
constructor(props) {
super(props);
this.state = {
items: [], // or items: {...props.items}
};
}
async componentDidMount() {
const res = await this.props.getItems();
this.setState({items: res.data.items})
}
render() {
return <div></div>
}
};
App.defaultProps = {
items: []
}
Can someone help me solve how do I setState inside componentDidUpdate and not have an infinite loop? Some suggestions said to have a conditional statement, but I am not too familiar with how do I set the conditional for my code.
This is what my code looks like:
I have a dashboard component that gets all the companies and projects data from external functions where the fetch happens and then updates the state. The projects are associated with the company's id.
I am able to get the list of all the projects in JSON, but I can't figure out how to update my projects state inside componentDidUpdate once rendered.
CompanyDashboard.js
import { getCompanys } from "../../actions/companyActions";
import { getProjects } from "../../actions/projectActions";
class CompanyDashboard extends Component {
constructor(props) {
super(props);
this.state = {
companies: [],
projects: []
};
}
componentWillMount() {
// get all companies and update state
getCompanys().then(companies => this.setState({ companies }));
}
componentDidUpdate(prevState) {
this.setState({ projects: this.state.projects });
}
render() {
const { companies, projects } = this.state;
{
companies.map(company => {
// get all the projects
return getProjects(company);
});
}
return <div />;
}
}
export default CompanyDashboard;
companyActions.js
import { getUser, getUserToken } from './cognitoActions';
import config from '../../config';
export function getCompanys() {
let url = config.base_url + '/companys';
return fetch(url, {
method: 'GET',
headers: {'token': getUserToken() }
})
.then(res => res.json())
.then(data => { return data })
.catch(err => console.log(err));
}
projectActions.js
import { getUserToken } from './cognitoActions';
import config from '../../config';
export function getProjects(company) {
let url = config.base_url + `/companys/${company._id['$oid']}/projects`;
return fetch(url, {
method: 'GET',
headers: {'token': getUserToken() }
})
.then(res => res.json())
.then(data => { return data })
.catch(err => console.log(err));
}
The following code is not doing anything meaningful. You are setting your state.projects to be equal to your state.projects.
componentDidUpdate() {
this.setState({ projects: this.state.projects })
}
Also, the following code is not doing anything meaningful because you are not saving the result of companies.map anywhere.
{
companies.map((company) => {
return getProjects(company)
})
}
It's hard to tell what you think your code is doing, but my guess is that you think that simply calling "companies.map(....) " inside your render function is going to TRIGGER the componentDidUpdate function. That is not how render works, you should go back to the drawing board on that one. It also looks like you think that using the curly brackets {} inside your render function will display the objects inside your curly brackets. That's also not true, you need to use those curly brackets inside the components. For instance: {projects}
If I had to guess... the following code is how you actually want to write your component
import { getCompanys } from '../../actions/companyActions';
import { getProjects } from '../../actions/projectActions';
class CompanyDashboard extends Component {
constructor(props) {
super(props);
this.state = {
companies: [],
projects: []
}
}
componentWillMount() {
getCompanys().then(companies => {
const projectPromises = companies.map((company) => {
return getProjects(company)
});
Promise.all(projectPromises).then(projects => {
//possibly a flatten operator on projects would go here.
this.setState({ companies, projects });
});
/*
* Alternatively, you could update the state after each project call is returned, and you wouldn't need Promise.all, sometimes redux can be weird about array mutation in the state, so look into forceUpdate if it isn't rerendering with this approach:
* const projectPromises = companies.map((company) => {
* return getProjects(company).then(project => this.setState({projects: this.state.projects.concat(project)}));
* });
*/
)
}
render() {
const { companies, projects } = this.state;
//Not sure how you want to display companies and projects, but you would
// build the display components, below.
return(
<div>
{projects}
</div>
)
}
}
export default CompanyDashboard;
componentDidUpdate has this signature, componentDidUpdate(prevProps, prevState, snapshot)
This means that every time the method gets called you have access to your prevState which you can use to compare to the new data, and then based on that decide if you should update again. As an example it can look something like this.
componentDidUpdate(prevProps, prevState) {
if (!prevState.length){
this.setState({ projects: this.state.projects })
}
}
Of course this is only an example since I don't know your requirements, but this should give you an idea.
When componentDidUpdate() is called, two arguments are passed:
prevProps and prevState. This is the inverse of
componentWillUpdate(). The passed values are what the values were,
and this.props and this.state are the current values.
`componentDidUpdate(prevProps) {
if (this.props.userID !== prevProps.userID) {
this.fetchData(this.props.userID);
}
}`
You must check the state/props if new state/props different from previous one then you can allow to update your component.
You may call setState() immediately in componentDidUpdate() but
note that it must be wrapped in a condition like in the example above,
or you’ll cause an infinite loop. It would also cause an extra
re-rendering which, while not visible to the user, can affect the
component performance. If you’re trying to “mirror” some state to a
prop coming from above, consider using the prop directly instead.
This is because componentDidUpdate is called just after a component takes up somechanges in the state. so when you change state in that method only then it will move to and from from that method and state change process
I am trying to use axios to get data from the api (https://reqres.in/) and display in my react app. Before this I fetched the data from the API using fetch method in javascript. Now I have tried coding this from various resources. How should I do it. Is it the correct method?
My app.js file-
import React, { Component } from 'react';
import './App.css';
import axios from 'axios';
class App extends Component {
constructor(props) {
super(props);
this.successShow = this.successShow.bind(this);
this.errorShow = this.errorShow.bind(this);
}
componentDidMount() {
axios.get('https://reqres.in/api/products/3')
.then(function (response) {
this.successShow(response);
})
.catch(function (error) {
this.errorShow(error);
});
}
successShow(response) {
this.member = <pre>{JSON.stringify(response.data, null, '\t')}</pre> ;
}
errorShow(error) {
this.member = <pre>{JSON.stringify(error.response.data, null, '\t')}</pre>;
}
render() {
return (
<div>
<h2>Welcome to React</h2>
<h3>{JSON.stringify(this.state.person.data)}</h3>
<div>{this.member}</div>
</div>
);
}
}
export default App;
It also gives the error - Unhandled Rejection (TypeError): Cannot read property 'errorShow' of undefined.
Changes:
1. You need to bind this with then and catch callback methods, use arrow functions.
2. You didn't define the initial state and using this.state.person.data it will throw error.
3. Storing the UI in state or global variable is not a good idea, ui part should be inside render method only.
Write it like this:
class App extends Component {
constructor(props) {
super(props);
this.state = {
person: {}
}
//this.successShow = this.successShow.bind(this);
//this.errorShow = this.errorShow.bind(this);
}
componentDidMount() {
axios.get('https://reqres.in/api/products/3')
.then((response) => {
this.successShow(response);
})
.catch((error) => {
this.successShow(error);
});
}
successShow(response) {
this.setState({
person: response.data
});
}
render() {
return (
<div>
<h2>Welcome to React</h2>
<h3>{JSON.stringify(this.state.person.data)}</h3>
<pre>{JSON.stringify(this.state.person.data)}</pre>
<div>{this.member}</div>
</div>
);
}
}
When you call this.errorShow() inside of the function, this is not your component object, but context of function. You should use arrow functions instead, arrow functions do not create its own this so you can access your component this:
componentDidMount() {
axios.get('https://reqres.in/api/products/3')
.then((response) => {
this.successShow(response);
})
.catch(error) => {
this.errorShow(error);
});
}
More info about arrow functions
Try this:
componentDidMount() {
axios.get('https://reqres.in/api/products/3')
.then((response) => {
this.successShow(response);
})
.catch((error) => {
this.errorShow(error);
});
}
Use arrow functions to remain the right scope of this
The problem is that the this in your then and catch callbacks doesn't refer to your class, but to the default (global) scope. You need to bind the right this. You actually already have the appropriate functions set up with this binding, so you can just use them directly:
componentDidMount() {
axios.get('https://reqres.in/api/products/3')
.then(this.successShow)
.catch(this.errorShow);
}
In general, you can also use => function syntax, which inherits the 'this' from the scope the function is declared in, rather than using the global scope. E.g.
componentDidMount() {
axios.get('https://reqres.in/api/products/3')
.then(success => this.successShow(success))
.catch(error => this.errorShow(error));
}
(note the => functions are completely unnecessary here of course).
You have an additional problem, which is the you need to store member in component state (this.state.member), not just as a field, and use the setState function to update it. Otherwise your component won't re-render when you update member.