I am a noob of ReactJS and I created a Homepage for Users to view after logging in or registering . I am getting the data from my custom API(NodeJS) but once I fetch the data and attempt to setState it appears that it is not updating the state variables.
Here is my code.
export class SideMenu extends Component {
constructor(props) {
super(props)
this.state = {
user: {},
};
}
componentDidMount() {
this.fetchData();
console.log(this.user);
}
fetchData = () => {
axios.get("http://localhost:3001/returningusers").then((response) => {
const data = response.data.User[0]
console.log(data)
this.setState({
user: data
})
})
}
I really need help with this, it has slowed down my momentum and I have no idea how to fix it. Thanks.
I'm not so sure that's the correct syntax for an ES6 class method. How about trying the below instead of the arrow function:
fetchData() {
axios.get("http://localhost:3001/returningusers").then((response) => {
const data = response.data.User[0]
console.log(data)
this.setState({
user: data
})
})
}
Also you need to return Promises and either await or then them. For example:
async componentDidMount() {
await this.fetchData(); // you need to wait until this finishes
console.log(this.state.user);
}
// and you need to return the Promise here
fetchData() => {
return axios.get("http://localhost:3001/returningusers").then((response) => {
const data = response.data.User[0]
console.log(data)
this.setState({
user: data
})
})
}
Related
My code below works, I used optional chaining to load information from the API. I was trying to get it to work by checking the loading state or checking if the array was empty, but I didn't have success. When I searched for "React API optional chaining", I didn't find much information specifically for these cases. The code below works, but is the way I did this considered okay? What other ways can I use to do this? What is the best way?
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
loading: true
}
}
componentDidMount() {
fetch('https://restcountries.eu/rest/v2/all')
.then(response => response.json())
.then(json => this.setState({data: json}));
this.setState({loading: false});
}
render() {
return (
<div>
<h1>{this.state.data?.length > 0 && this.state.data[0]["name"]}</h1>
</div>
)
}
}
export default App;
I think in actual workplace, the way your team decided to use is the best way, so it would be case by case.
But personally, based on your code here I prefer to write like this:
// use React Hooks
import React, { useState, useEffect } from 'react';
// Functional Components
const App = () => {
const [data, setData] = useState([]);
// useEffect is pretty much the equivalent of componentDidMount if you pass an empty array as the second argument.
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch('Your API Url.');
const json = await response.json();
setData(json);
} catch (error) {
console.error(error);
}
}
fetchData();
}, []);
// Conditional Operator and Optional Chaining to help conditional render.
return (
<div>
{ data?.length > 0
? <h1>{data[0]['name']}</h1>
: <div>{`Loading...`}</div>
}
</div>
)
};
export default App;
You could also read more about how to use async await syntax inside useEffect here.
Hope this will help a bit.
I have a react component. If I user clicks on a Link To on the parent component they land on this child componented just fine. However if they refresh the page, or go to the link directly, their is no data, so I need to make an api call again myself for that unique id.
When I make the api call (when issue is undefined), it works, but I get a promise back that has fulfilled, with no data. How do I get the object?
class Issue extends React.Component {
getIssue = async (id) => {
try {
const endpoint = `https://api.github.com/repos/facebook/create-react-app/issues/${id}`;
const response = await fetch(endpoint);
const data = await response.json();
return data;
} catch (error) {
console.log(error);
}
}
// }
render(){
let { issue } = this.props.location;
console.log(issue);
if(issue === undefined){
console.log('No Data');
issue = this.getIssue(this.props.match.params.id);
console.log(issue);
} else {
console.log('Data');
}
return (
<h1>ff</h1>
)
}
}
Rewrite your component to use state, rewrite getIssue to use Promise (it fits better here) and move it to componentDidMount method.
class Issue extends React.Component {
constructor(props) {
super(props);
this.state = {
data: null
};
}
componentDidMount() {
const id = this.props.match.params.id;
const endpoint = `https://api.github.com/repos/facebook/create-react-app/issues/${id}`;
fetch(endpoint)
.then(response => response.json())
.then(data => this.setState({ data }))
.catch(err => console.log(err));
}
render() {
return <h1>{this.state.data ? this.state.data.title : "Loading..."}</h1>;
}
}
The reason is because your getIssue() is async function and it will return a promise which you have to handle later on. In your render() method you are not doing it, you have to use getIssue() with then() chained where you can get your data from promise:
render() {
let { issue } = this.props.location;
console.log(issue);
if(issue === undefined) {
console.log('No Data');
this.getIssue(this.props.match.params.id)
.then(response => {
console.log(response);
});
} else {
console.log('Data');
}
return (
<h1>ff</h1>
)
}
You cannot call async functions during render()
You should do async calls on a useEffect hook (if using hooks) or componentDidMount or one of the lifecycles of a React class.
An example:
class Issue extends React.Component {
constructor() {
this.state = {
issue: null
}
componentDidMount() {
const { issue } = this.props.location;
if (!issue) {
getIssue(this.props.match.params.id).then(data => {
this.setState({ issue: data });
}
}
getIssue = async (id) => {
try {
const endpoint = `https://api.github.com/repos/facebook/create-react-app/issues/${id}`;
const response = await fetch(endpoint);
const data = await response.json();
return data;
} catch (error) {
console.log(error);
}
}
// }
render(){
let { issue } = this.state;
console.log(issue);
return (
<h1>ff</h1>
)
}
}
Two issues :
Fetch usage - Just use the direct result and not the data as follows:
const url = 'https://api.github.com/repos/facebook/create-react-app/issues/20'
const getData = async () => {
const data = await fetch(url);
console.log(data)
}
getData();
Using async in useEffect or componentDidMount - To use in useEffect you can refer this: How to call an async function inside a UseEffect() in React?
Beginner here.
Trying to fetch some data from a server and display it in my react component once its fetched.
However, I am having trouble integrating the async function into my react component.
import React, { useState } from "react";
import { request } from "graphql-request";
async function fetchData() {
const endpoint = "https://localhost:3090/graphql"
const query = `
query getItems($id: ID) {
item(id: $id) {
title
}
}
`;
const variables = {
id: "123123123"
};
const data = await request(endpoint, query, variables);
// console.log(JSON.stringify(data, undefined, 2));
return data;
}
const TestingGraphQL = () => {
const data = fetchData().catch((error) => console.error(error));
return (
<div>
{data.item.title}
</div>
);
};
export default TestingGraphQL;
I'd like to simply show a spinner or something while waiting, but I tried this & it seems because a promise is returned I cannot do this.
Here you would need to use the useEffect hook to call the API.
The data returned from the API, I am storing here in a state, as well as a loading state to indicate when the call is being made.
Follow along the comments added in between the code below -
CODE
import React, { useState, useEffect } from "react"; // importing useEffect here
import Layout from "#layouts/default";
import ContentContainer from "#components/ContentContainer";
import { request } from "graphql-request";
async function fetchData() {
const endpoint = "https://localhost:3090/graphql"
const query = `
query getItems($id: ID) {
item(id: $id) {
title
}
}
`;
const variables = {
id: "123123123"
};
const data = await request(endpoint, query, variables);
// console.log(JSON.stringify(data, undefined, 2));
return data;
}
const TestingGraphQL = () => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
// useEffect with an empty dependency array works the same way as componentDidMount
useEffect(async () => {
try {
// set loading to true before calling API
setLoading(true);
const data = await fetchData();
setData(data);
// switch loading to false after fetch is complete
setLoading(false);
} catch (error) {
// add error handling here
setLoading(false);
console.log(error);
}
}, []);
// return a Spinner when loading is true
if(loading) return (
<span>Loading</span>
);
// data will be null when fetch call fails
if (!data) return (
<span>Data not available</span>
);
// when data is available, title is shown
return (
<Layout>
{data.item.title}
</Layout>
);
};
since fetchData() returns a promise you need to handle it in TestingGraphQL. I recommend onComponentMount do your data call. Setting the data retrieved into the state var, for react to keep track of and re-rendering when your data call is finished.
I added a loading state var. If loading is true, then it shows 'loading' otherwise it shows the data. You can go about changing those to components later to suit your needs.
See the example below, switched from hooks to a class, but you should be able to make it work! :)
class TestingGraphQL extends Component {
constructor() {
super();
this.state = { data: {}, loading: true};
}
//when the component is added to the screen. fetch data
componentDidMount() {
fetchData()
.then(json => { this.setState({ data: json, loading: false }) })
.catch(error => console.error(error));
}
render() {
return (
{this.state.loading ? <div>Loading Spinner here</div> : <div>{this.state.data.item.title}</div>}
);
}
};
Data is appending next to the datatable, not inside.
I am fetching data (array of records) from an API in actions of vuex and returning state (array) from getters to the components where datatables have been used.
import axios from "../../assets/constants";
import router from '../../router'
const state = {
users: []
}
const getters = {
users: state => state.users,
blockedUsers: state => state.blockedUsers,
user: state => state.user
}
const actions = {
async getUsers({ commit }) {
await axios.get(`user`)
.then(res => {
commit('setGetUsers', res.data)
})
.catch(err => console.log(err.response.data.message));
})
},
const mutations = {
setGetUsers: (state, newUsers) => (state.users = newUsers),
}
export default {
state,
getters,
actions,
mutations
}
<script>
import { mapGetters, mapActions } from "vuex";
export default {
methods: {
...mapActions(["getUsers"])
},
computed: mapGetters(["users"]),
created() {
this.getUsers();
$(".zero-configuration").DataTable();
}
};
</script>
Result should be as that data that I am fetching from API must show inside datatable.
As far I understand, issue that has been causing here is that
$(".zero-configuration").DataTable();
this is executing before
this.getUsers()
which shouldn't be correct explanation because I have used await with axios.
Can anyone explain why is this happening?
It turns out when I commit mutation after I get response from axios, it takes time to set the state. Since I am not using promise here, while the state is being mutate,
$(".zero-configuration").DataTable();
takes control from
this.getUsers()
and get executed before it finishes.
I encountered this problem by using promise in getUsers action
getUsers({ commit }) {
return new Promise(async (resolve) => {
await axios.get(`user`)
.then(async res => {
await commit('setGetUsers', res.data)
resolve()
})
.catch(err => console.log(err.response.data.message));
})
},
Now it works like a charm!
How can I pass data I receive from a get request pass over to a component? Whatever I tried wouldn't work but my thinking was as the code below shows..
Thanks!
export function data() {
axios.get('www.example.de')
.then(function(res) {
return res.data
})
.then(function(data) {
this.setState({
list: data
})
})
}
import {data} from './api.js';
class Test extends React.Component {
constructor(props) {
super(props);
this.state = {
list: ""
};
}
componentWillMount() {
data();
}
render() {
return <p > this.state.list < /p>
}
}
You call this.setState inside of data()->then callback, so this is context of the then callback function. Instead you should use arrow functions (it does not have its own context) and pass component's this to data function using call
export function data() {
axios.get('www.example.de')
.then(res => res.data)
.then(data => {
this.setState({
list: data
})
})
}
import {data} from './api.js';
class Test extends React.Component {
constructor(props) {
super(props);
this.state = {
list: ""
};
}
componentWillMount() {
data.call(this);
}
render() {
return <p > this.state.list < /p>
}
}
However, your data services must not know about setState and, event more, expect passing this from react component. Your data service must be responsible for retrieving data from server, but not for changing component state, see Single responsibility principle. Also, your data service can be called from another data service. So your data service should return promise instead, that can be used by component for calling setState.
export function data() {
return axios.get('www.example.de')
.then(res => res.data)
}
and then
componentWillMount() {
data().then(data=>{
this.setState({
list: data
})
});
}
your api shouldn't know anything about your component, you can easily do this with callback, like so -
export function data(callback) {
axios.get('www.example.de')
.then(res => callback({ data: res.data }))
.catch(err => callback({ error: err }));
}
By doing this you can easily unit test your api
So in your Test component, you simply do -
componentWillMount() {
data(result => {
const { data, error } = result;
if (error) {
// Handle error
return;
}
if (data) {
this.setState({ list: data });
}
});
}
Your request is a promise so you can simply return that from the imported function and use the eventual returned result of that within the component. You only want to be changing the state of the component from within the component.
export function getData(endpoint) {
return axios.get(endpoint);
}
Note I've changed the name of the function to something more "actiony".
import { getData } from './api.js';
class Test extends React.Component {
constructor(props) {
super(props);
// Your state is going to be an array of things, so
// initialise it with an array to spare confusion
this.state = { list: [] };
}
// I use ComponentDidMount for fetch requests
// https://daveceddia.com/where-fetch-data-componentwillmount-vs-componentdidmount/
componentDidMount() {
// We've returned a promise from `getData` so we can still use the
// promise API to extract the JSON, and store the parsed object as the
// component state
getData('www.example.de')
.then(res => res.data)
.then(list => this.setState({ list }))
}
}
Your external function doesn't have the correct context of this, so you'll need to call it with the correct context from within the component:
componentWillMount() {
data.call(this);
}
However, inside the API call, it still won't have the correct this context, so you can set a variable to point to this inside the data() function:
export function data() {
let that = this;
axios('http://www.url.com')
.then(function(res) {
return res.data
})
.then(function(data) {
that.setState({
list: data
})
})
}
Details of the this keyword
However, it's generally considered better practice to only handle your state manipulation from with the component itself, but this will involve handling the asynchronous nature of the GET request, perhaps by passing in a callback to the data() function.
EDIT: Updated with asynchronous code
//api.js
data(callback){
axios.get('www.url.com')
.then(res => callback(res));
}
//component.jsx
componentWillMount(){
data(res => this.setState({list: res}));
}