useState not re-rendring the componenet - javascript

I'm using useState hook but after changing the state, the component is not rending itself. I don't know what thing I'm missing.
import React, {useState} from 'react'
import List from '#material-ui/core/List'
import ListTile from "./components/ListTile/ListTile"
import style from './App.module.css'
import InputField from "./components/inputField/InputField";
const App = () => {
const [list, setList] = useState([])
const onFormSubmitHandler = (data) => {
list.push(data)
setList(list)
}
return (
<div className={style.outerDiv}>
<h1 className={style.center}>CLister</h1>
<InputField onSubmit={onFormSubmitHandler}/>
<List component="nav">
{list.map((data, index) =>
<ListTile index={index} body={data}/>
)}
</List>
</div>
);
}
export default App

As your list an array a reference type in js. If you modify the list using push
like list.push() it will also modify the original list in your state ,as a result there will be no change in your state.
Example
let list = [1, 2, 3, 4];
let list2 = list;
// if I modify list2 now
list2.push(5);
console.log(list); // list also gets modified as ,they are reference type
So what you can do
const onFormSubmitHandler = (data) => {
let list2=[...list]; // creating a new variable from existing one
list2.push(data)
setList(list2);
}
or
const onFormSubmitHandler = (data) => {
setList(prev=>([...prev,data]));
}

Remember that your state cant be modificate with push, because the way to modificate it is with the method set
Use this code in the method onFormSubmitHandler
const onFormSubmitHandler = (data) => {
setList(list => ([...list, data]))
}
Lastly remember if your form will be submit you need to break it with e.prevent.default()

const onFormSubmitHandler = (data) => {
list.push(data);
setList([...list]);
}

You should try something like this
import List from '#material-ui/core/List'
import ListTile from "./components/ListTile/ListTile"
import style from './App.module.css'
import InputField from "./components/inputField/InputField";
const App = () => {
const [list, setList] = useState([])
const onFormSubmitHandler = (data) => {
list.push(data)
setList(list)
}
return (
<div className={style.outerDiv}>
<h1 className={style.center}>CLister</h1>
<InputField onSubmit={(e) => onFormSubmitHandler(e.target.value)}/>
<List component="nav">
{list.map((data, index) =>
<ListTile index={index} body={data}/>
)}
</List>
</div>
);
}
export default App

You are editing it the wrong way, you should directly give the new values to the setList function and not try to update the list variable. Thats why you have the function, so that you do not update the original value. What you have to do here is use the previous state within the function and the spread operator since its an array and u just want to add an item:
const onFormSubmitHandler = (data) => {
setList(prevList => [...prevList, data])
}
You should look at the list variable as a read-only variable and not attempt to modify it, you modify it through the setList function.
If you want to do some other modifications instead of just adding item:
const onFormSubmitHandler = (data) => {
let listCopy = [...list];
// do something with listCopy
setList(listCopy);
}
In addition, it seems like you are not sending data at all to the function, the way to send data with your function call is to do it with anonymous function in the component:
<Component onSubmit={(e) => { onFormSubmitHandler(e.target.value) }} />

Related

why useState is not updating data?

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>
);
};

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);
}

Is there a way to populate option html tag with an array in react?

I'm trying to make an option in jsx to be populated by the values in an array (currencyOptions). I used this approach but it is not working as the options still remain to be blank. The array is passed down to the component as a prop. I set the array using usestate and the data is gotten from an API. Please help.
import React from "react";
function Currencyrow(props) {
const {
currencyOptions,
selectedCurrency,
onChangeCurrency,
amount,
onChangeAmount,
} = props;
// console.log(currencyOptions);
return (
<>
<input
type="number"
className="input"
value={amount}
onChange={onChangeAmount}
></input>
<select value={selectedCurrency} onChange={onChangeCurrency}>
{currencyOptions.map((option) => {
<option key={option} value={option}>
{option}
</option>;
})}
</select>
</>
);
}
export default Currencyrow;
That is the component where I pass down currencyOptions as a prop from my main app.js
import "./App.css";
import React from "react";
import Currencyrow from "./Components/Currencyrow";
import { useEffect, useState } from "react";
const BASE_URL =
"http://api.exchangeratesapi.io/v1/latest?access_key=1fe1e64c5a8434974e17b04a023e9348";
function App() {
const [currencyOptions, setCurrencyOptions] = useState([]);
const [fromCurrency, setFromCurrency] = useState();
const [toCurrency, setToCurrency] = useState();
const [exchangeRate, setExchangeRate] = useState();
const [amount, setAmount] = useState(1);
const [amountInFromCurrency, setAmountInFromCurrency] = useState(true);
let toAmount, fromAmount;
if (amountInFromCurrency) {
fromAmount = amount;
toAmount = fromAmount * exchangeRate;
} else {
toAmount = amount;
fromAmount = amount / exchangeRate;
}
useEffect(() => {
fetch(BASE_URL)
.then((res) => res.json())
.then((data) => {
const firstCurrency = Object.keys(data.rates)[0];
setCurrencyOptions([Object.keys(data.rates)]);
setFromCurrency(data.base);
// console.log(currencyOptions);
setToCurrency(firstCurrency);
setExchangeRate(data.rates[firstCurrency]);
});
}, []);
function handleFromAmountChange() {
// setAmount(e.target.value);
setAmountInFromCurrency(true);
}
function handleToAmountChange() {
// setAmount(e.target.value);
setAmountInFromCurrency(false);
}
return (
<>
<h1>Convert</h1>
<Currencyrow
currencyOptions={currencyOptions}
selectedCurrency={fromCurrency}
onChangeCurrency={(e) => {
setFromCurrency(e.target.value);
}}
amount={fromAmount}
onChangeAmount={handleFromAmountChange}
/>
<div className="equals">=</div>
<Currencyrow
currencyOptions={currencyOptions}
selectedCurrency={toCurrency}
onChangeCurrency={(e) => {
setToCurrency(e.target.value);
}}
amount={toAmount}
onChangeAmount={handleToAmountChange}
/>
</>
);
}
export default App;
When I run the app the option element is still blank.
Is there a way to populate option html tag with an array in react?
This is possible. Just as a tip, you can always try hardcoding currencyOptions in your CurrencyRow and test it out.
Looking through your code, firstly it may be not what you want wrapping Object.keys() in an additional array in setCurrencyOptions([Object.keys(data.rates)]). Object.keys() already returns an array. You probably are not accessing the actual options in your currencyOptions.map((option) => ..). Try setting the keys array directly like this setCurrencyOptions(Object.keys(data.rates)).
Secondly, you should return the desired value inside map by either using it as an arrow function or adding the return keyword in front of the option JSX.
Other than that, is there any error displayed in the browser console? And it would certainly help you to log the mapped option to the console and see what you are actually getting from it.
Your map function should return a value.
<select>{numbers.map((m)=>{return(<option>{m}</option>)})}</select>

why wont jsx render firestore data

I'm trying to GET data from firestore and render it, in the most basic way possible. I can console.log the data, but as soon as I try to render it via JSX, it doesn't. Why is this?
import React from 'react'
import { useState, useEffect } from 'react'
import {db} from '../../public/init-firebase'
export default function Todo() {
const [todo, setTodo] = useState()
const myArray = []
//retrive data from firestore and push to empty array
function getData(){
db.collection('Todos')
.onSnapshot((querySnapshot) => {
querySnapshot.forEach((doc) => {
console.log(doc.id +'pushed to myArray')
myArray.push(doc.id)
})
})
}
useEffect(() => {
getData()
}, [])
return (
<>
<div>
<h1>Data from firestore: </h1>
{myArray.map((doc) => {
<h1>{doc.id}</h1>
console.log('hi')
})}
</div>
</>
)
}
First, change myArray to State like this:
const [myArray, setMyArray] = useState([]);
Every change in myArray will re-render the component.
Then, to push items in the State do this:
function getData(){
db.collection('Todos')
.onSnapshot((querySnapshot) => {
querySnapshot.forEach((doc) => {
console.log(doc.id +'pushed to myArray')
setMyArray(oldArray => [...oldArray, doc.id])
})
})
}
You're just pushing the ID in myArray, so when to show try like this:
{myArray.map((id) => {
console.log('hi')
return <h1 key={id}>{id}</h1>
})}
If you look closely,
{myArray.map((doc) => {
<h1>{doc.id}</h1>
console.log('hi')
})}
those are curly braces {}, not parenthesis (). This means that although doc exists, nothing is happening since you are just declaring <h1>{doc.id}</h1>. In order for it to render, you have to return something in the map function. Try this instead:
{myArray.map((doc) => {
console.log('hi')
return <h1>{doc.id}</h1>
})}
In order to force a "re-render" you will have to use the hooks that you defined
https://reactjs.org/docs/hooks-intro.html
function getData(){
db.collection('Todos')
.onSnapshot((querySnapshot) => {
querySnapshot.forEach((doc) => {
console.log(doc.id +'pushed to myArray')
myArray.push(doc.id)
})
})
// in case the request doesn't fail, and the array filled up with correct value using correct keys:
// i'd setState here, which will cause a re-render
setTodo(myArray)
}

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>

Categories

Resources