I'm struggling to get my data from a fetch request into the state of my container
My fetch request is stored in api.js and looks like this - it retrieves the key from a constant which is fine:-
import { openWeatherKey } from './constants';
const getWeather = async() => {
const base = "https://api.openweathermap.org/data/2.5/onecall";
const query = `?lat=52.6&lon=-2.2&exclude=hourly,daily&appid=${openWeatherKey}`;
const response = await fetch(base + query);
const data = await response.json();
return data;
}
export { getWeather };
My container looks like this:-
import React, { Component } from "react";
import './weather.css';
import { getWeather } from './api';
class Spy extends Component {
constructor() {
super()
this.state = {test(){return "this is a test"}}
}
render() {
return (
<div id="spy-weather" className="app-border">
<h3 className="spy-name">Weather at { this.props.location } {this.state.test()}</h3>
</div>
)
}
}
(() => {
getWeather().then(data => {
console.log(data);
})
})();
export { Spy as Weather };
I have an IIFE which makes the request and prints the results to the console. You can see that between the class declaration and the export statement above.
Here are the results from the console - the request works fine
{lat: 52.6, lon: -2.2, timezone: "Europe/London", timezone_offset: 3600, current: {…}}
current: {dt: 1594401262, sunrise: 1594353486, sunset: 1594412995, temp: 289.05, feels_like: 286.49, …}
lat: 52.6
lon: -2.2
timezone: "Europe/London"
timezone_offset: 3600
__proto__: Object
What I can't manage to do is set the state with the data from the resolved promise. I've tried various things, including some solutions I've seen which didn't work.
How do I place and run the function within the container and then update state with the data?
I'm pretty new to React as you can probably tell.
With sincere thanks,
Phil
In class based components, lifecycle method known as componentDidMount is used to do something after component has mounted. In your case, move the code in IIFE in the componentDidMount method.
Make a property in state object which will hold the weather data. Optionally, you can also make a property in state object to hold any error message that might occur during the fetching of data from the API.
this.state = {
weatherData: null,
error: ''
};
and then call getWeather() function from componentDidMount() lifecycle method
componentDidMount() {
getWeather()
.then(data => {
this.setState({ weatherData: data });
})
.catch(error => this.setState({ error: error.message }));
}
In functional components, useEffect hook is used to perform any side-effect like fetching data from an API. State in functional components is saved using useState hook.
If you use a functional component, then your code will look like this:
const [weatherData, setWeatherData] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
getWeather()
.then(data => {
setWeatherData(data);
})
.catch(error => setError(error.message));
}, []);
this.state = {test(){return "this is a test"}}
This is invalid structure for state managment, right way
getWeather().then(data => {
console.log(data);
this.setState({ weatherData: data });
})
state structure too
state = {
someProperty: value,
someArray: [],
weatherData: {}
}
Related
I am trying to use the search api of Open Library.
Usually, if you are going to use a functional component, you will do it like this on your api file:
import axios from 'axios';
export default axios.create({
baseuRL: 'http://openlibrary.org/search.json'
})
And then you will import this on a file where you will fetch the data:
import booksAPI from '../apis/books';
const books = () => {
useEffect(() => {
books()
}, [])
const books = async() => {
const res = await booksAPI.get('?author=tolkien');
console.log(res.data);
}
}
This is expected to console.log the data on your terminal. However, using the class component with axios and componentDidMount to fetch the data.
class BookList extends Component {
constructor(props) {
super(props)
this.state = {
books: []
}
}
componentDidMount(){
const booksResponse = async() => {
const response = await booksAPI.get('?author=tolkien');
console.log(response.data)
}
}
This is complaining about the await keyword and doesn't console.log the data. Also, I am not sure how I can convert the useEffect to a class component so it can perform side effects?
const BookList = () => {
// State variable where you can store the data
const [books, setBooks] = useState([]);
// Effect, which would be called on component mount because of empty array of dependencies -> [] (look for useEffect lifecycles) and set the data to state variable. After this, component will re-render.
useEffect(() => {
const fetchBooks = async () => {
const response = await booksAPI.get('?author=tolkien');
setBooks(response.data);
console.log(response.data);
}
fetchBooks();
}, []);
return ...
}
Also, make sure that you're adding query/mutation/subscription to your GraphQL document right before the name of query/mutation/subscription, as someones told you in the comment.
In case you're looking for class component realization, your code should look like this:
class BookList extends Component {
constructor(props) {
super(props)
this.state = {
books: []
}
}
fetchBooks = () => {
const response = booksAPI.get('?author=tolkien');
this.setState({ books: response.data });
}
componentDidMount(){
fetchBooks();
}
...
}
Here I have created a simple example in React for you.
componentDidMount() {
this.booksResponse();
}
booksResponse = async () => {
const response = await axios.get(
'https://openlibrary.org/search.json?author=token'
);
console.log(response.data);
};
Here is Stackblitz link.
https://stackblitz.com/edit/react-zk8nhq
If you find any confusing then Please comment here I can create more example for you.
Can someone please tell me the equivalent code using hooks for the following:
componentDidMount() {
const { match: { params } } = this.props;
axios.get(`/api/users/${params.userId}`)
.then(({ data: user }) => {
console.log('user', user);
this.setState({ user });
});
}
The exact functionality to match your class component into a functional component with hooks would be the following:
import * as React from "react";
import { useParams } from "react-router-dom";
const Component = () => {
const { userId } = useParams();
const [state, setState] = React.useState({ user: null });
React.useEffect(() => {
axios.get(`/api/users/${userId}`)
.then(({ data: user }) => {
console.log('user', user);
setState({ user });
});
}, []);
}
React.useEffect(() => {}, []) with an empty dependency array essentially works the same way as the componentDidMount lifecycle method.
The React.useState hook returns an array with the state and a method to update the state setState.
References:
https://reactjs.org/docs/hooks-state.html
https://reactjs.org/docs/hooks-effect.html
As an aside, and pointed out by #Yoshi:
The snippet provided is error prone, and the "moving to hooks" snippet will have the same errors that occur in the example. For example, as the request is in componentDidMount, if the userId changes it won't trigger a fetch to get the user data for the userId. To ensure this works in the hook, all you need to do is provide the userId in the dependency array in the useEffect...
const latestRequest = React.useRef(null);
React.useEffect(() => {
latestRequest.current = userId;
axios.get(`/api/users/${userId}`)
.then(({ data: user }) => {
if (latestRequest.current == userId) {
setState({ user });
}
});
}, [userId]);
I am making calls to firestore inside the fetchData function and then adding the received data to state object but the problem is the global state is overwritten by the last api call as seen in my console.log output:
Here's my code :
import React, { useState, useEffect } from 'react';
import { db } from './firebase';
export const StateContext = React.createContext();
export function ContextController({ children }) {
const intialState = {
projectList: [],
blogList: [],
count: 0
};
const [state, setState] = useState(intialState);
useEffect(() => {
fetchData();
}, []);
async function fetchData() {
// get all projects
db.collection('projects')
.get()
.then((querySnapshot) => {
const data = querySnapshot.docs.map((doc) => doc.data());
let sorted = [];
// loop through each object and push to sorted array
data.forEach((item) => {
sorted.push(item.body);
});
// reverse sort the projects
sorted.sort(
(a, b) => parseInt(b.project_id) - parseFloat(a.project_id)
);
setState({
...state,
projectList: sorted
});
});
// get all blogs
db.collection('blog')
.get()
.then((querySnapshot) => {
const data = querySnapshot.docs.map((doc) => doc.data());
setState({
...state,
blogList: data
});
});
}
return (
<StateContext.Provider value={[state, setState]}>
{children}
</StateContext.Provider>
);
}
How do I correctly add data from both endpoints to the state object so that I can access it anywhere in my app?
I think the issue is where you spread the old state into setState the state has gone stale. React does some performance enhancements around setState that will make code like this not work as expected.
One thing to try is using the setState callback.
setState((state) => ({
...state,
projectList: sorted
}));
The question comes from a issue where I need data binding and save it to a reducer so I can use pusher to modify the data when needed and it changes in real-time. The problems I find are that:
I am new in react and I don't really know much about how to bind data variables.
I am using remote data with fetch so the data can be refreshed but it can't seem to find a way to properly bind or even save it to a reducer.
Below, the relevant code:
class MainTable extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<MaterialTable
tableRef={this.tableRef}
columns={columnsSetup}
options={materialTableOptions}
data={query =>
new Promise((resolve, reject) => {
pageQuery = query.page;
pageSizeQuery = query.pageSize;
let url = GET_ORDERS_URL;
url += 'qtt=' + pageSizeQuery;
url += '&page=' + pageQuery;
fetch(url)
.then(response => response.json())
.then(result => {
resolve({
data: result.data,
page: result.page,
totalCount: result.totalElems
});
});
})
}
/>
);
}
}
Data fetching should be done in one of the react lifecycle methods. These are build-in functions that will be called on specific "life" events of your component, for example when it gets mounted (componentDidMount).
You should read the docs thoroughly to really get the hang of it.
To give you an example of the implementation of a lifecycle method, I fixed your code below.
A couple of important, but also opinionated, subjects to look into are: lifecycle methods, state, Async/await (instead of promises), Components: classes and hooks.
class MainTable extends React.Component {
state = {
data: {}
};
componentDidMount() {
this.fetchData();
}
fetchData = async query => {
let url = `${GET_ORDERS_URL}qtt=${query.pageSize}&page=${query.page}`;
const response = await fetch(url);
const result = response.json();
this.setState({
data: {
data: result.data,
page: result.page,
totalCount: result.totalElems
}
});
};
render() {
return (
<MaterialTable
tableRef={this.tableRef}
columns={columnsSetup}
options={materialTableOptions}
data={this.state.data}
/>
);
}
}
Below is a functional component (exactly the same logic as above) that uses hooks:
import React, { useEffect, useState } from 'react';
function MainTable() {
const [data, setData] = useState({});
useEffect(() => {
const fetchData = async query => {
let url = `${GET_ORDERS_URL}qtt=${query.pageSize}&page=${query.page}`;
const response = await fetch(url);
const result = response.json();
setData({
data: result.data,
page: result.page,
totalCount: result.totalElems
});
};
fetchData();
}, [data]);
return (
<MaterialTable
columns={columnsSetup}
options={materialTableOptions}
data={data}
/>
);
}
Material-table is a fantastic out the box soloution for tables however, I don't particularity like the way the pagination is handled. I use await and async operators rather than promises and wanted my component to be responsible for managing the state rather than these promise methods that material-table wants you to provide. Material-table provide a way to completely override their pagination logic so this is what I did.
material-table's Pagination component appears to just be a wrapper Material UI's TablePagination component.
const [data,setData] = React.useState([])
const [pagination, setPagination] = React.useState({take: 20, page: 0})
const [count, setCount] = React.useState(0)
React.useEffect(() => {
async function fetch() {
var {result, count} = await someAsyncCallToServer()
setData(fetch)
setCount(count)
}
fetch()
}
,[pagination])
const handleChangePage = (event, newPage) => {
setPagination(old => ({...old, page: newPage}))
}
...
<MaterialTable
data={data}
...
components = {{
Pagination : props =>
<TablePagination
rowsPerPage={pagination.take}
onChangeRowsPerPage={take => setPagination(old=> ({...old, take}))}
page={pagination.page}
count={count}
onChangePage={handleChangePage}
/>
}}
/>
I have a container component in which I get the ID and drop this ID into the function and the request goes, in principle, the props should come right away, but they are undefined. But when you re-enter the same component, the necessary props are shown.
Explain how to make props appear on the first render?
class View extends React.Component {
componentDidMount() {
let id = this.props.match.params.id;
this.props.GetProjData(id);
}
render() {
return <ProjView {...this.props}></ProjView>;
}
}
let mapStateToProps = state => {
return {
initialValues: {
NameProj: state.project.OneProject.NameProj,
Text: state.project.OneProject.Text,
target: state.project.OneProject.target,
startdate: state.project.OneProject.startdate,
enddate: state.project.OneProject.enddate
},
error: state.settings.error,
loading: state.settings.loading
};
};
My request
export const GetProjData = data => async (
dispatch,
getState,
{ getFirestore }
) => {
const firestore=getFirestore()
try {
await firestore
.collection("Projects")
.where("idProject", "==", data)
.get().then(snap => {
snap.forEach(doc => {
let project=doc.data()
console.log(doc.data());
dispatch({type:getOne,project})
});
})
} catch (err) {}
};
If I'm understanding the flow of your app correctly, you need to account for the renders between when you request your project data and when you receive the project data.
class View extends React.Component {
// constructor fires first so we might as well move it here
constructor(props) {
const id = props.match.params.id;
props.GetProjData(id);
}
render() {
// Your component will rerender before receiving the new data.
// We block the component from mounting so that initialValues
// gets set only when we have the data needed
if (this.props.initialValues && this.props.initialValues.NameProj) {
// A better way to do this would be to listen to a loading variable
// that gets updated when your request finishes
return <ProjView {...this.props} />;
}
return null; // or loading graphic
}
}