I am getting information from 2 apis, one which is the Unsplash api which returns an random image and a 2nd api which returns a bible verse from ourmanna.com via the cors-anywhere proxy. The unsplash api works fine and always returns an image. The bible api returns the correct data but the useState does not update the state and verse stays as an empty string. I can't see why this is the case.
Below is the code:
import React, { useState, useEffect, Fragment } from "react";
import styles from "./BibleQuote.module.css";
import axios from "axios";
import Poster from "../Poster/Poster";
const UNSPLASH_API = process.env.REACT_APP_UNSPLASH_API;
const UNSPLASH_API_KEY = process.env.REACT_APP_UNSPLASH_API_KEY;
function BibleQuote() {
const [photo, setImage] = useState({ poster: "" });
const [verse, setVerse] = useState({ verse: "" });
useEffect(() => {
async function fetchData() {
await axios(UNSPLASH_API, {
headers: {
Authorization: `Client-ID ${UNSPLASH_API_KEY}`,
},
}).then((res) => {
setImage(res.data.urls.regular);
console.log("image", res.data.urls.regular);
});
await axios(
`https://cors-anywhere.herokuapp.com/http://www.ourmanna.com/verses/api/get?format=text&order=random`).then((res) => {
setVerse(res.data);
console.log("APIquote", res.data);
console.log("State: Quote", verse);
});
}
fetchData();
}, []);
return (
<Fragment>
<Poster photo={photo} quote={verse} />
</Fragment>
);
}
export default BibleQuote;
this is the result in the chrome developer tools:
I am pretty sure it is updated but you can not see it from console.log right after the state is changed. Try to use another one useEffect working only when state is changed.
const App = () => {
const [data, setData] = useState(false)
useEffect(()=> {
setData(true)
console.log(data) //shows false despite of setData on the previous line
}, [])
useEffect(()=> {
console.log(data) //shows true - updated state
}, [data])
return <h2>Hello world</h2>
}
That's because setState works async so the next line always contain the previous value of the state.
Related
Hello I am developing a todo list app using reactjs with axios. I managed to view, and add data to the database, my problem now is that I dont know how to load the updated data after submitting the form.
This is the code for fetching all the data from the database. The file name is FetchData.js
import { useEffect, useState} from 'react';
import axios from 'axios';
const FetchData = () => {
const [data, setData] = useState({});
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchData = async () => {
try {
const { data: response } = await axios.get('http://localhost/todolistci/backend/index.php/todos/view', { crossDomain: true });
setData(response);
} catch (error) {
console.error(error);
}
setLoading(false);
};
fetchData();
}, []);
return {
data,
loading,
};
};
export default FetchData;
This is how I view the list of items came from FetchData.js. The file name is List.js
import React from 'react';
import ListItem from './ListItem'
import FetchData from './FetchData';
function List() {
const {
data,
loading,
} = FetchData();
return (
<ul>
{loading && <div>Loading</div>}
{!loading && (
<>
{data.map(item => (<ListItem key={item.id} id={item.id} name={item.name} complete={item.complete} />))}
</>
)}
</ul>
)
}
export default List
Now this is the form That I am submitting. File name is FormToDo.js
import React, {useState} from 'react';
import axios from 'axios';
function FormToDo() {
const [formValue, setformValue] = useState({
name: '',
});
const handleSubmit = async(e) => {
e.preventDefault();
// store the states in the form data
const nameFormData = new FormData();
nameFormData.append("name", formValue.name)
try {
// make axios post request
const response = await axios({
method: "post",
url: "http://localhost/todolistci/backend/index.php/create",
data: nameFormData,
headers: { "Content-Type": "multipart/form-data" },
});
} catch(error) {
console.log(error)
}
//empty the text field
setformValue({name: ''});
//I need to update the list of data in here
}
const handleChange = (event) => {
setformValue({
...formValue,
[event.target.name]: event.target.value
});
}
return (
<div>
<form onSubmit={handleSubmit}>
<input type="text" name="name" id="name" required placeholder="Enter To Do"
value={formValue.name} onChange={handleChange} onKeyDown={handleChange} />
<button type="submit">+</button>
</form>
</div>
)
}
export default FormToDo
This is the image of the todo app I am making.
enter image description here
Please help me. Thank you.
Your example doesn't describe how you are going back to the list after axios posted the data and got a response.
What you need is to mutate after database is updated.
one way could be to move "fetchData" from useEffect to "FetchData" and add a a mutate function that fetches the data and is made available in the return
const FetchData = () => {
const [data, setData] = useState({});
const [loading, setLoading] = useState(true);
const fetchData = async () => {
try {
const { data: response } = await axios.get(
"http://localhost/todolistci/backend/index.php/todos/view",
{ crossDomain: true }
);
setData(response);
} catch (error) {
console.error(error);
}
setLoading(false);
};
const mutate = () => fetchData();
useEffect(() => {
fetchData();
}, []);
return {
data,
loading,
mutate,
};
};
and then call mutate after data is posted.
A second solution could be to push the browser to the list page and make sure fetchData runs.
A third solution (and the solution I would choose) is to use for example SWR - React Hooks for Data Fetching that would help you to fetch & mutate data, you can see axios example in their docs
I am trying to render data from rest api site, I can get all info without issues, but is duplicating the data with an empty array first and this is creating a conflict with the map() function.
when I do a console logo I can see the duplication. what I need is to only get the array that has the data and the empty one or how can I select the array with data, since for somereason when i used the map() function I get error because its reading the empty array
useFetchData.js
import { useEffect, useState} from 'react';
import http from '../../services/httpservices';
import config from '../../services/config.json';
const useFetchData = () => {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchData = async () => {
try {
const { data: response } = await http.get(config.apiEndpoint);
setData(response);
} catch (error) {
console.error(error)
}
setLoading(false);
};
fetchData();
}, []);
return {
data,
loading,
};
};
export default useFetchData;
customsite.jsx
import React, { useState } from 'react';
import Modal from './reusable/modal';
import useFetchData from './hooks/useFetchData';
const Customsite = ()=> {
const {
data,
loading,
} = useFetchData();
console.log(data);
return(
<div>
<p> we here </p>
</div>
)
}
export default Customsite
you only need to wait until the data has loaded to get the full array, you must condition the console log to loading === false
!loading && console.log(data);
the same goes with the map function you want to use. you need to add this condition. Either that or test if data.length > 0
From what I understand useEffect hook runs last as a sideEffect. I am attempting to console log data.main.temp. I can understand that it doesn't know what that is yet, because it is fetching the data from the api in the useEffect hook which runs after.
How would I be able to access or console log data.main.temp AFTER the api call? (I feel like setTimout is the cheating way?)
import React, { useState, useEffect } from "react";
import Button from "../UI/Button";
import styles from "./Weather.module.css";
import moment from "moment";
import Card from "../UI/Card";
export default function Weather() {
//State Management//
const [lat, setLat] = useState([]);
const [long, setLong] = useState([]);
const [data, setData] = useState([]);
//openWeather API key
const key = "xxxxxxxxxxxxxxxxxxxxxxxxxx";
useEffect(() => {
const fetchData = async () => {
//get coordinates//
navigator.geolocation.getCurrentPosition(function (position) {
setLat(position.coords.latitude);
setLong(position.coords.longitude);
});
//fetch openWeather api//
await fetch(`https://api.openweathermap.org/data/2.5/weather/?lat=${lat}&lon=${long}&units=metric&APPID=${key}`)
.then((res) => res.json())
.then((result) => {
setData(result);
console.log(result);
});
};
fetchData();
}, [lat, long]);
//Examples of what I want, they run too early before api//
console.log(data.main.temp);
const Farenheit = data.main.temp * 1.8 + 32;
return (
<Card>
{typeof data.main != "undefined" ? (
<div className={`${styles.weatherContainer} ${styles.clouds}`}>
<h2>Weather</h2>
<p>{data.name}</p>
<p>{data.main.temp * 1.8 + 32} °F</p>
<p>{data.weather[0].description}</p>
<hr></hr>
<h2>Date</h2>
<p>{moment().format("dddd")}</p>
<p>{moment().format("LL")}</p>
</div>
) : (
<div></div>
)}
</Card>
);
}
You're right, the effect function is run after the first render which means you need to wait somehow until your api call is done. One common way to do so is to introduce another state flag which indicate whether the data is available or not.
Another thing which does not follow react good practices is the fact that you're effect function does more than one thing.
I also added trivial error handling and cleaned up mixed promises and async await
here is your refactored code
import React, { useState, useEffect } from "react";
import Button from "../UI/Button";
import styles from "./Weather.module.css";
import moment from "moment";
import Card from "../UI/Card";
//openWeather API key
const key = "xxxxxxxxxxxxxxxxxxxxxxxxxx";
export default function Weather() {
//State Management//
const [lat, setLat] = useState();
const [long, setLong] = useState();
const [data, setData] = useState();
const [error, setError] = useState();
const [loading, setLoading] = useState(false);
useEffect(() => {
navigator.geolocation.getCurrentPosition((position) => {
setLat(position.coords.latitude);
setLong(position.coords.longitude);
});
}, []);
useEffect(() => {
const fetchData = async () => {
if (lat && long && key) {
try {
setLoading(true);
const response = await fetch(
`https://api.openweathermap.org/data/2.5/weather/?lat=${lat}&lon=${long}&units=metric&APPID=${key}`
);
const data = await response.json();
setData(data);
setLoading(false);
} catch (err) {
setError(err);
setLoading(false);
}
}
};
fetchData();
}, [lat, long]);
if (error) {
return <div>some error occurred...</div>;
}
return (
<Card>
{loading || !data ? (
<div>loading...</div>
) : (
<div className={`${styles.weatherContainer} ${styles.clouds}`}>
<h2>Weather</h2>
<p>{data.name}</p>
<p>{data.main.temp * 1.8 + 32} °F</p>
<p>{data.weather[0].description}</p>
<hr></hr>
<h2>Date</h2>
<p>{moment().format("dddd")}</p>
<p>{moment().format("LL")}</p>
</div>
)}
</Card>
);
}
You can use another useEffect, which depends on changing the data state
useEfect(() => {
if (data) {
// do something with data
}
}, [data])
You can create a simple function and call it in your API call response and pass in the data directly from the api response, that way you will have access to the data immediately there's a response.
E.g
...
.then((result) => {
setData(result);
getDataValue(result) // this function will be called when the response comes in and you can use the value for anything
console.log(result);
});
METHOD 2:
You can use a useEffect hook to monitor changes in the data state, so that whenever there's an update on that state, you can use the value to do whatever you want. This is my less preferred option.
useEffect(() => {
//this hook will run whenever data changes, the initial value of data will however be what the initial value of the state is
console.log(data) //initial value = [] , next value => response from API
},[data])
export default function Main({ match }) {
const userid = match.params.id;
const [user, setUser] = useState([]);
async function fetchuser() {
const response = await api.get('/emps/profile', {
headers: {
userid: match.params.id,
},
});
setUser(response.data);
console.log(response.data);
console.log(user);
}
useEffect(() => {
fetchuser();
}, [match.params.id]);
In the above code the response.data is written into console but user state is empty. Can someone tell me why this is?
Two suggestions:
Determine if response contains any data before logging it.
Move your fetchData function into the useEffect hook. https://stackoverflow.com/a/56851963/8943092
Below is an example of how you can test for the existence of data, and here is a live Sandbox.
Note that we use a simple conditional to check if (myData) is truthy. Our useState hook sets no default value, so the conditional returns true once data is present.
In the render method, we use a ternary to check for the existence of data.
Your solution may be slightly different because you set the default value of user to an empty array []. Assuming your API call returns an array, you'll test for data with if (user.length > 0).
import React, { useEffect, useState } from "react";
export default function App() {
const [myData, setMyData] = useState();
useEffect(() => {
function fetchData() {
setTimeout(function () {
setMyData("I am user data");
}, 3000);
}
if (myData) {
console.log(myData);
} else {
console.log("No data yet");
}
fetchData();
}, [myData]);
return (
<div className="App">{myData ? <p>{myData}</p> : <p>No data yet</p>}</div>
);
}
when I try to get some data from my backend API using axios, and set the state after I've gotten the result for some reason the state is not updated and when I try to use the state it will only show me an empty array. but what's so interesting is that when I console.log(res.data) it will show me my array of lists with no problem, so I guess the problem is with the setCategories() state function. What am I doing wrong?
const Home = (props) => {
const [categories, setCategories] = useState([]);
useEffect(() => {
getCats();
}, []);
const getCats = async () => {
const data = await axios.get(`${myUrl}/allItems`, {
withCredentials: true,
});
const cats = await data.data;
console.log(cats); //this one works perfectly
setCategories(cats);
console.log(categories) //this one doesn'nt work which means the setState didn't work
};
return (
<>
<div className="card-div mt-5">
{categories.map((cat) => {
<li>{cat.name}</li>;
})}
</div>
</>
);
};
the state is set asynchronously, so the data is not updated instantly. that's why you are not getting the output on console.log(categories) right after setCategories(cats);
here is a small example of asynchronous behaviour of useState state update:
Link to working example: stackblitz
import React, { useEffect, useState } from "react";
import "./style.css";
import axios from "axios";
const url = "https://jsonplaceholder.typicode.com/users";
export default function App() {
const [users, setUsers] = useState([]);
useEffect(() => {
axios.get(url).then(result => {
console.log("1. when data is fetched sucessfully: ", result.data);
setUsers(result.data);
console.log("2. Just after setting state: ", users);
});
}, []);
// secons useEffect for logging out upadated todos useState
useEffect(() => {
console.log("todos upadated: ", users);
}, [users]);
return (
<div>
<h1>Hello StackBlitz!</h1>
<p>Start editing to see some magic happen :)</p>
{users.map(user => (
<p>{user.name}</p>
))}
</div>
);
}
Here is what is happening in the above example:
You can see the flow of data fetching and async update of state.
The useState function is asynchronous, so you will never get the new state in the same function, the best way is to use it in another function or useEffect.
Example:
useEffect(() => {
console.log(categories);
}, [categories]);