why useState is not updating data? - javascript

I am very new to react and after taking a course, I just wanted to do a project in react that I already did in Vue earlier just to practice React. But my useState is not updating the data that I want to list out.
The data from api is an array of Objects. End goal is to make a data table. So to be dynamic, I took the keys of the first object to take the column names. I used DataTable from 'react-data-table-component' for the data table. That didn't work. Now just to debug I thought to list out the column names but that didn't work either but it console logs.
I know this is something very basic and have searched lot of help in the internet and tried to solve who faced similar issue from lot of portals but nothing helped so far. Where am I doing wrong ?
import React, { useState, useEffect, Component } from 'react';
import axios from 'axios'
const tableData = () => {
const [tableData, setTableData] = useState([]);
const [columns, setColumns] = useState([])
useEffect(() => {
axios.get('http://localhost:4000/')
.then((res) => {
if (res.status === 200) {
if (res.data === 0) {
console.log("Something is wrong !!!");
} else {
const data = res.data['rows']
const columns = Object.keys(data[0])
console.log(columns)
setTableData(data)
setColumns(columns)
}
}
}).catch(error => {
console.log(error)
})
}, [])
return (
<div>
<ul>
{columns.map((columnName, index) => {
const {name} = columnName;
return (
<li key={index}>{ name }</li>
)
})}
</ul>
</div>
)
}

in react state, set state works on references.
sometime we need to change the state to the same reference that we changed the value of it.
so instead put the same reference as an argument, we need to set a copy of it,
because if we will set to the same state that already in the state, react can not see any different so it will not render again.
in your example, you didn't use the same reference for data, but for columns you used the same name of the state and the new columns.
example:
let referenceName = 'John';
const [name, setName] = useState(referenceName);
referenceName = 'Johnny'
setName(...referenceName )
I want to set the name of the state, but if i will NOT use the '...', react will not see any difference because i use the same reference.. so I need to put a copy od the refence for set a new reference.

check the shape of response data
here I simpliplified the useEffect by call that function out side
import React, { useState, useEffect, Component } from "react";
import axios from "axios";
const tableData = () => {
const [tableData, setTableData] = useState([]);
const [columns, setColumns] = useState([]);
const fetchDetails = () => {
axios
.get("http://localhost:4000/")
.then((res) => {
const data = res.data["rows"];
const columns = Object.keys(data[0]);
console.log(columns);
setTableData(data);
setColumns(columns);
})
.catch((error) => {
//dont need to check the status if any occurs it will come to this catch block
console.log(error.message);
});
};
useEffect(() => {
fetchDetails();
}, [columns]);
return (
<div>
<ul>
{columns.map((columnName, index) => {
const { name } = columnName;
return <li key={index}>{name}</li>;
})}
</ul>
</div>
);
};

Related

React useState array empty on initial load but after editing code while app is running array fills?

This is going to be really hard to explain, but here goes. I am building a React card grid with a filter. The data is pulled from an MySQL AWS API I built. The .tags property is JSON with an array that stores each tag associated with the card. I have written Javascript in App.jsx to turn this JSON into an object, and then store every unique tag in a piece of state. See code below:
//App.jsx
import { useEffect, useState } from 'react';
import '../assets/css/App.css';
import Card from './Card';
import Filter from './Filter'
import {motion, AnimatePresence} from 'framer-motion'
function App() {
const [cards, setCards] = useState([]);
const [filter, setFilter] = useState([]);
const [activeFilter, setActiveFilter] = useState("all");
const [tags,setTags] = useState([]);
useEffect(() => {
fetchData();
}, []);
/*useEffect(() => {
console.log(tags);
console.log(activeFilter);
}, [activeFilter,tags]);
*/
const getTags = () => {
let tags = [];
cards.forEach((card) => {
let obj = JSON.parse(card.tags);
obj.forEach((tag) => {
if (!tags.includes(tag)) {
tags.push(tag);
}
});
});
setTags(tags);
}
const fetchData = async () => {
const data = await fetch("<<api>>");
const cards = await data.json();
setCards(cards);
setFilter((cards));
getTags();
}
return (
<div className="App">
<Filter
cards={cards}
setFilter={setFilter}
activeFilter={activeFilter}
setActiveFilter={setActiveFilter}
/>
<motion.div layout className="Cards">
<AnimatePresence>
{filter.map((card) => {
return <Card key={card.id} card={card}/>;
})}
</AnimatePresence>
</motion.div>
</div>
);
}
export default App;
The problem that I am having is that when I run the app initially, the tags state is empty when inspecting from React Dev tools. However, when I keep the app running, and then add something like a console.log(tags); before setTags(tags) is called in the getTags() function, the data suddenly appears in the state. If someone could explain why the state seems to be empty even though I am updating it on the initial render that would be really appreciated.
You are running getTags on empty array. setCards doesn't set the const variable instantly. New values will be present in the next render cycle.
Try adding cards param
const getTags = (cards) => {
let tags = [];
cards.forEach((card) => {
let obj = JSON.parse(card.tags);
obj.forEach((tag) => {
if (!tags.includes(tag)) {
tags.push(tag);
}
});
});
setTags(tags);
}
And use it like this:
const fetchData = async () => {
const data = await fetch("API url");
const cards = await data.json();
setCards(cards);
setFilter((cards));
getTags(cards);
}

How to use data of an Async function inside a functional component to render HTML in React

I've been trying to use the data I get from an Async function inside of another function I use to display HTML on a react project. I have made several attempts but nothing seems to work for me. Hope any of you could help me. Please correct me if I did anything wrong.
I've tried it with a useEffect as well:
import React, { useState, useEffect } from 'react';
import { getGenres } from './api/functions';
const ParentThatFetches = () => {
const [data, updateData] = useState();
useEffect(() => {
const getData = async () => {
const genres = await getGenres('tv');
updateData(genres);
}
getData();
}, []);
return data && <Screen data={data} />
}
const Screen = ({data}) => {
console.log({data}); //logs 'data: undefined' to the console
return (
<div>
<h1 className="text-3xl font-bold underline">H1</h1>
</div>
);
}
export default Screen;
The Error I get from this is: {data: undefined}.
The getGenres function that makes the HTTP Request:
const apiKey = 'key';
const baseUrl = 'https://api.themoviedb.org/3';
export const getGenres = async (type) => {
const requestEndpoint = `/genre/${type}/list`;
const requestParams = `?api_key=${apiKey}`;
const urlToFetch = baseUrl + requestEndpoint + requestParams;
try {
const response = await fetch(urlToFetch);
if(response.ok) {
const jsonResponse = await response.json();
const genres = jsonResponse.genres;
return genres;
}
} catch(e) {
console.log(e);
}
}
I want to use the data inside my HTML, so the H1 for example.
Once again, haven't been doing this for a long time so correct me if I'm wrong.
There are a few conceptual misunderstandings that I want to tackle in your code.
In modern React, your components should typically render some type of jsx, which is how React renders html. In your first example, you are using App to return your genres to your Screen component, which you don't need to do.
If your goal is to fetch some genres and then ultimately print them out onto the screen, you only need one component. Inside that component, you will useEffect to call an asynchronous function that will then await the api data and set it to a react state. That state will then be what you can iterate through.
When genres is first rendered by react on line 6, it will be undefined. Then, once the api data is retrieved, React will update the value of genre to be your array of genres which will cause the component to be re-rendered.
{genres && genres.map((genre) ... on line 20 checks to see if genres is defined, and only if it is, will it map (like looping) through the genres. At first, since genres is undefined, nothing will print and no errors will be thrown. After the genres are set in our useEffect hook, genres will now be an array and we can therefore loop through them.
Here is a working example of your code.
import React, { useState, useEffect } from "react";
import { getGenres } from "./api/functions";
function App() {
const [genres, setGenres] = useState();
useEffect(() => {
async function apiCall() {
const apiResponse = await getGenres("tv");
console.log(apiResponse);
setGenres(apiResponse);
}
apiCall();
}, []);
return (
<div>
<h1 className="text-3xl font-bold underline">H1</h1>
{genres && genres.map((genre) => <div key={genre}>{genre}</div>)}
</div>
);
}
export default App;
You should use a combination or useEffect and useState
You should use useEffect to launch the async get, without launching it each rerendering. If a async function is used to get some data from outside the component, this is called 'side effect' so use useEffect.
You should use useState to react to changes on theses side effects, re-rendering the component to get the data in the dom.
In the next example, Im using a dummy async function getGenres which returns an array of genres.
Here is an example and a WORKING EXAMPLE :
const {useState, useEffect} = React;
async function getGenres() {
var promise = new Promise(function(resolve, reject) {
window.setTimeout(function() {
resolve( ['genre1', 'genre2']);
});
});
return promise;
}
const Screen = () => {
const [genres, setGenres] = useState([])
useEffect(
() => {
getGenres().then(
res => setGenres(res)
)
}, [getGenres]
)
return (
<ul>
{
genres.map(
i => <li>{i}</li>
)
}
</ul>
);
}
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<Screen/>)

react constantly calling the API when using .map to go through the list

i have setup a strapi API, and i am using react to consume that API (with Axios).
here's what the code look like inside App.js
import axios from "axios";
import React, {useEffect, useState} from "react";
import LineCard from "./components/Linecard"
function App() {
// don't mind the URL i will fix them later
const root = "http://localhost:1337"
const URL = 'http://localhost:1337/pick-up-lines'
// this is the "messed up" data from strapi
const [APIdata, setAPIdata] = useState([])
//this is the clean data
const [lines, setLines] = useState([])
// the array that i will be using later to "setLines" state
const linesFromApi = APIdata.map((line, index) => {
const profileImage = root + line.users_permissions_user.profilePicture.formats.thumbnail.url
const userName = line.users_permissions_user.username
const title = line.title
const lineBody = line.line
const rating = line.rating
const categories = line.categories.map((category, index) => category.categoryName)
return {
profileImage,
userName,
title,
lineBody,
rating,
categories
}
})
useEffect(() => {
// calling the API with get method to fetch the data and store it inside APIdata state
axios.get(URL).then((res) => {
setAPIdata(res.data)
})
setLines(linesFromApi)
}, [URL, linesFromApi])
return (
<div>
// mapping through the lines list and rendering a card for each element
{lines.map((line, index) => <LineCard line={line} />)}
</div >
);
}
export default App;
i know for sure that this is causing the problem
return (
<div>
{lines.map((line, index) => <LineCard line={line} />)}
</div >
);
my problem is that react keeps sending GET requests constantly, and i want it to stop after the first time it has the list.
how can i do that!
Try adding a check in your hook so that it restricts the api call if the value is already set.
Something like this
useEffect(() => {
if(lines.length === 0){
axios.get(URL).then((res) => {
setAPIdata(res.data)
})
setLines(linesFromApi)
}
}, [URL, linesFromApi])
You need to add the key property to the element in a map.
<div>
{lines.map((line, index) => <LineCard key={index} line={line} />)}
</div>

logging the data but not rendering p tag , why?

I am using firebase firestore and i fetched the data , everything is working fine but when i am passing it to some component only one item gets passed but log shows all the elements correctly.
I have just started learning react , any help is appreciated.
import React, { useEffect, useState } from 'react'
import { auth, provider, db } from './firebase';
import DataCard from './DataCard'
function Explore() {
const [equipmentList, setEquipments] = useState([]);
const fetchData = async () => {
const res = db.collection('Available');
const data = await res.get();
data.docs.forEach(item => {
setEquipments([...equipmentList, item.data()]);
})
}
useEffect(() => {
fetchData();
}, [])
equipmentList.forEach(item => {
//console.log(item.description);
})
const dataJSX =
<>
{
equipmentList.map(eq => (
<div key={eq.uid}>
{console.log(eq.equipment)}
<p>{eq.equipment}</p>
</div>
))
}
</>
return (
<>
{dataJSX}
</>
)
}
export default Explore
You have problems with setting fetched data into the state.
You need to call setEquipments once when data is prepared because you always erase it with an initial array plus an item from forEach.
The right code for setting equipment is
const fetchData = async () => {
const res = db.collection('Available');
const data = await res.get();
setEquipments(data.docs.map(item => item.data()))
}

API based search using React

I am new to React and I want to run API based search. I have written a sample code with the search functionality but it is not working as per requirement. I want to search from the list but it is always giving the same array of as the whole list when I am writing anything in the search box.
Please help me out and let me know where the code in wrong.
Here is my code:
TestEntry.js
import React , {useState,useEffect} from 'react'
import {Table} from 'reactstrap'
import {Navbar,Nav,NavDropdown,Form,FormControl,Button} from 'react-bootstrap'
//import axios from 'axios'
import Loading from './loading.gif';
const CoinGecko = require('coingecko-api');
const CoinGeckoClient = new CoinGecko();
function TestEntry(){
const[item,SearchData]=useState([]);
const[cryptos,setCryptos]=useState([]);
useEffect(()=>{
fetchItems()
},[])
const fetchItems=async()=>{
const url="https://api.coingecko.com/api/v3/coins/list";
const response= await fetch(url);
const info=await response.json();
console.log(info);
setCryptos(info);
}
const Search=(key)=>{
console.log(key);
fetch("https://api.coingecko.com/api/v3/coins/list?q="+key)
.then((data)=>{
data.json().then((resp)=>{
console.warn("resp:",resp)
SearchData(resp)
})
})
}
const cryptoJsx=cryptos.map(crypto=>(
<div key={crypto.id}>
{crypto.id}
</div>
));
return(
<div>
Search:
<input type="text" onChange={(event)=>Search(event.target.value)}/>
<div>
{
{item} ?
<div>
{
item.map((items)=>
<div key={items.id}>{items.name}</div>
)
}
</div>
: ""
}
</div>
{cryptoJsx}
</div>
)
}
export default TestEntry
The search api seems not working. When I tried api-search for a text separately in browser, it returned full results.
Anyway....
You can do search locally i.e. filter the cryptos array. It will cause re-render and only filtered results are shown.
Note:
Maintain a copy of the cryptos and always filter based on original and only mutate the cryptos. This way search works (both typing a char and deleting a char) and search results are re-rendered automatically
The downside of filtering state data is that new data from server is only obtained in client upon page refresh
If you really want to use api docs and use correct endpoint. Also consider using debouncing. Perform debounce in React.js
I have checked this and search is working fine.
import React, { useState, useEffect } from "react";
function TestEntry() {
const [item, SearchData] = useState([]);
const [cryptos, setCryptos] = useState([]);
const [origCryptosCount, setOrigCryptosCount] = useState([]);
useEffect(() => {
fetchItems();
}, []);
const fetchItems = async () => {
const url = "https://api.coingecko.com/api/v3/coins/list";
const response = await fetch(url);
const info = await response.json();
setCryptos(info);
setOrigCryptosCount(info);
};
// const Search_Old = key => {
// console.log(key);
// fetch("https://api.coingecko.com/api/v3/coins/list?q=" + key).then(data => {
// data.json().then(resp => {
// SearchData(resp);
// });
// });
// };
//
const Search = key => {
const newResults = origCryptosCount.filter(crypto => crypto.name.includes(key));
console.log('newResults', newResults);
setCryptos(newResults);
};
const cryptoJsx = cryptos.map(crypto => (
<div key={crypto.id}>{crypto.id}</div>
));
return (
<div>
Search:
<input type="text" onChange={event => Search(event.target.value)} />
{cryptoJsx}
</div>
);
}
export default TestEntry;

Categories

Resources