Problem with fetching data in React, Assignment to constant variable - reactjs

I just started to learn React and was trying to fetch some random data. i created a useState and have two values : const [name, setName] = useState([]);
when i try to do name : response.json();
I get an error that assignment to a constant variable, I'm following a tutorial which is doing the same.
surprisingly when I create a constant variable with a different name, it works. I only can't assign the name = await response.json();
Thank you
import React, { useEffect, useState } from "react";
import { ReactDOM } from "react";
const FetchData = () =>{
const [name, setName] = useState([]);
const fetchNames = async () =>{
const url = `https://randomuser.me/api`;
try {
const response = await fetch(url);
name = await response.json();
console.log(name);
setName(name);
} catch (error) {
console.log(error);
}
}
useEffect(()=>{
fetchNames();
},[])
return(
<div>
</div>
);
}
export default FetchData;

Check my example, as I understand you want to use name inside the try as variable not from state so you should add const. I also mapped the response in your render which you can see the results. Here also codesandbox example https://codesandbox.io/embed/friendly-ishizaka-57i66u?fontsize=14&hidenavigation=1&theme=dark
import { useEffect, useState } from "react";
import "./styles.css";
export default function App() {
const [name, setName] = useState([]);
const fetchNames = async () => {
const url = `https://randomuser.me/api`;
try {
const response = await fetch(url);
constname = await response.json();
console.log(name);
setName(name?.results);
} catch (error) {
console.log(error);
}
};
useEffect(() => {
fetchNames();
}, []);
return (
<div>
{name.map((el) => (
<>
<p>{el.email}</p>
<p>{el.phone}</p>
</>
))}
</div>
);
}

there are 2 things that might help with this confusion
any variable created using const will forever hold the value it had when it was declared, so for eg if we have const x = "😀", we later can't do something like x = "something else"
in the snippet you shared name is a "state variable". state variables should NEVER be updated directly, state updates must happen through setState function only(i.e. setName in this case)
this is the part in official docs that talks about it https://reactjs.org/docs/state-and-lifecycle.html#do-not-modify-state-directly , https://reactjs.org/docs/hooks-state.html#updating-state

Related

My object doesn't render, is it because of the scope of the recipeID variable? Or did I do something else wrong?

Ive been trying to wrap my head around this as its bugging me. Recipe is taking in recipeID via useParams() and I am using it to filter and find the matching recipe. For some reason its not returning an object to render. I can trace it where I call .filter() on the response.items within the try/catch, as .filter() works in another component when I use a string compared to recipeID, is this happening because the variable types are different or is it because recipeID isnt in scope when used within fetchRecipe()? Because Ive noticed that my compiler is asking me to also put recipeID as another dependency in the useCallback() . Here is my code:
import { Skeleton } from '#chakra-ui/react'
import React from 'react'
import Layout from './Layout'
import client from '../client.js'
import {useParams} from 'react-router-dom'
import {useState, useEffect, useCallback} from 'react';
function Recipe() {
const params = useParams()
let recipeID = params.index
let name = params.name
const [recipe, setRecipe] = useState([])
const [isRecipeLoading, setIsRecipeLoading] = useState(false)
const cleanUpRecipe = useCallback((rawData) => {
const cleanRecipe = rawData.map((recipe) =>{
const {sys, fields} =recipe
const {id} = sys
const recipeTitle = fields.recipeName
const recipeImage = fields.recipePicture.fields.file.url
const recipeIngredients = fields.recipeItems
const recipeDirections = fields.recipeDirections
const recipeTime = fields.totalTime
const mealType = fields.mealType
const cleanedRecipe = {id, recipeTitle, recipeImage, recipeIngredients,recipeDirections, recipeTime, mealType}
return cleanedRecipe
})
setRecipe(cleanRecipe)
},[])
const fetchRecipe = useCallback (async () => {
try {
const response = await client.getEntries({content_type:'recipeNookRecipes'})
const responseData = response.items.filter((entry) => entry.sys.id === recipeID )
console.log(responseData)
if (responseData) {
cleanUpRecipe(responseData)
setIsRecipeLoading(true)
} else {
setRecipe([])
setIsRecipeLoading(false)
}
}
catch (error) {
console.log(error)
setIsRecipeLoading(false)
}
},[cleanUpRecipe])
useEffect(() => {
fetchRecipe()
}, [fetchRecipe])
console.log("We have this" + recipe.recipeTitle)
return (
<Layout>
<Skeleton isLoaded={isRecipeLoading}>
<div>Hi</div>
<h1>{recipe.recipeTitle}</h1>
<img src={recipe.recipeImage} alt={recipe.recipeTitle}/>
<div>
{recipe.recipeIngredients}
</div>
<div>
{recipe.recipeDirections}
</div>
</Skeleton>
</Layout>
)
}
export default Recipe
Any help with solving this and understanding what I did wrong is greatly appreciated!

react get data from rest api with axios and and useEffect redering empty array plus array with data

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

How can I manage promise and map objects?

I am working on a project in which data in my database is something like this:
I'm not getting what wrong am I doing! here is my
FetchList.js
import { useState } from "react";
const FetchList = async()=>{
const[data, setData] = useState()
const response = await fetch('https://__firebase__url__');
if (!response.ok) {
throw new Error("Something went wrong");
}
let responseData = await response.json();
const loadedHospitals = [];
for (const key in responseData){
loadedHospitals.push({
key: responseData[key].key,
Name: responseData[key].Name,
Email: responseData[key].Email,
Contact_no: responseData[key].Contact_no,
img: responseData[key].img,
});
}
setData(loadedHospitals);
const hospitals = data.map(hospital => {
console.log(hospital);
});
return data;
}
export default FetchList;
I want to pass the entire result in an array to ListHospital.js and then map it with Card component.
import Card from "../UI/Card";
import FetchList from "./FetchList";
import { useState, useEffect } from "react";
const ListHospitals = () => {
const [data, setData] = useState();
useEffect(() => {
FetchList().then(data => setData(data));
}, []);
return data.map((item)=><Card>{item}</Card>);
}
export default ListHospitals;
Error:
TypeError: Cannot read properties of undefined (reading 'map')
I think you need to write const [data, setData] = useState(); as const [data, setData] = useState([]); first. as fetch list is an async function so it will take some time to fetch your data hence it is giving you an error. You can only use map on arrays and during the initial render the data variable is undefined.
Fetching query at the higher level like App.js solved my problem.
Like :-
function App() {
const [ data, setData ] = useState([]);
const fetching = async () => {
const response = await fetch("_____url___");
const responseData = await response.json();
let loadedItem = [];
for (const key in responseData) {
loadedItem.push(responseData[key]);
}
console.log(loadedItem);
}
fetching();
return (
<>
<h1>Hey</h1>
</>
);
}
export default App;

useState variable is called before useEffect API call

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])

this axios api call returns an empty array for the call, but the second one returns a value

I am trying to use the context below to fetch the data from the api, then pass it down to other components. The data is from unsplash, so I would like to filter it out first using a keyword set by the user. However, the first array returned by the function is empty, but if I console.log() it, I get all the data as expected
import React, {useState, createContext} from 'react';
import axios from 'axios';
export const ImageContext = createContext();
export const ImageContextProvider = ({children}) => {
const [images, setImages] = useState([]);
const count = 15;
const start = 1;
const getImages = async (keyword) => {
await axios.get(`api/photos?count=${count}&start=${start}&keyword=${keyword}`)
.then(res => setImages(res.data.results));
}
return (
<ImageContext.Provider value={{images, getImages: getImages }}>
{children}
</ImageContext.Provider>
);
}
This is the component that passes the keyword to the context that will be used to fetch data from the API
import React, { useState, useContext} from 'react';
import { ImageContext } from '../../Context/ImageContext';
export const SearchBar = () => {
const { getImages, images } = useContext(ImageContext);
const[keyword, setKeyword] = useState('');
const searchImages = async (e) => {
e.preventDefault();
await getImages(keyword);
console.log(images);
}
return (
<form onSubmit={searchImages}>
<input type='text' placeholder='Search Images' value={keyword} onChange={(e) =>
setKeyword(e.target.value)}/>
<input type='submit'/>
</form>
);
}
You are almost there :)
Since you are using React hooks, you need useEffect so that you can be able to send Http request. It is equivalent to `componentDidMount.
How to use it?
import React, {useState, createContext, useEffect} from 'react';
useEffect(() => {
const result = axios.get(`api/photos?count=${count}&start=${start}&keyword=${keyword}`)
.then(res => setImages(res.data.results)); // remember to update your state
}, []); // don't forget to pass an empty array
Take a look at your getImages() function:
const getImages = async (keyword) => {
await axios.get(`api/photos?...`).then(...);
}
If I’m right that is a wrong construction as you’re mixing and await and a then(). Furthermore, your getImages() is not returning a value. I think the following solves your problem:
const getImages = async (keyword) => {
const res = await axios.get(`api/photos?...`);
setImages(res.data.results);
return res.data.results;
}

Resources