Why is my data not rendering appropriately? - javascript

I'm still struggling with React Natives rendering order. I'm fetching the API, then I filter this data and finally I'm manipulating the data. When I first load the app, it does not show the Data appropriately only when I'm saving within my code editor it shows up.
My simplified code:
const [data, setData] = useState([]);
const [sumPost, setSumPost] = useState(0);
const [sumProd, setSumProd] = useState(0);
useEffect(() => {
const unsubscribe = db.collection("Dates").where("projektName", "==", Projektname).onSnapshot(snapshot => (
setData(
snapshot.docs.map((doc) => ({
id: doc.id,
data: doc.data(),
})))
))
return unsubscribe;
}, [])
const produktionsFilter = data.filter( x =>
x.data.jobtype == "Produktion"
);
const postFilter = data.filter( x =>
x.data.jobtype == "Postproduktion"
);
const terminFilter = data.filter( x =>
x.data.jobtype == "Termin"
);
let i;
const addPostProdTage = () => {
const post = [];
const prod = [];
for(i=0; i < postFilter.length; i++){
const p = postFilter[i].data.alleTage.length;
post.push(p)
}
for(i=0; i < produktionsFilter.length; i++){
const l = produktionsFilter[i].data.alleTage.length;
prod.push(l)
}
setSumPost(post.reduce(function(a, b){
return a + b;
}, 0));
setSumProd(prod.reduce(function(a, b){
return a + b;
}, 0));
}
useEffect(() => {
addPostProdTage();
}, [])
return(
<View>
<Text>{sumPost}</Text>
<Text>{sumProd}</Text>
</View>
)
sumProd should be 18 and sumPost should be 3. Right now it is showing 0 on both, because both states are empty arrays initially. It needs to some sort refresh.
I'm sure, there are more efficient ways to code this, but I need help to understand, why my data is not showing appropriately when I first load the app, because I'm running into this problem over and over again.

Thanks to all the advise I got on here, so for future reference this is how I solved this:
I filtered the data inside snapshot:
useEffect(() => {
const post = db
.collection("Dates")
.where("projektName", "==", Projektname)
.where("jobtype", "==", "Postproduktion")
.onSnapshot((snapshot) =>
setPost(
snapshot.docs.map((doc) => ({
id: doc.id,
data: doc.data(),
}))
)
);
return post;
}, []);
I had unnecessary steps to do my calculation. I could simplify this into a single function:
const revData = () => {
setSumPost(
post.reduce(function (prev, cur) {
return prev + cur.data.alleTage.length;
}, 0)
);
};
And finally, I had a useEffect to call that function after the data has been fetched using the dependency array:
useEffect(() => {
revData();
}, [post]);

You are creating local variables that go out of scope. You would be able to catch this error if you were using typescript instead of javascript.
You want to instead create state objects like this:
const [sumPost, setSumPost] = useState(0)
const [sumProd, setSumProd] = useState(0);
And then set the values of those objects as shown:
setSumPost(postproduktionsTage.reduce(function(a, b){
return a + b;
}, 0));
setSumProd(produktionsTage.reduce(function(a, b){
return a + b;
}, 0));
And then you can use it as you desire:
return(
<View>
<Text>{sumPost}</Text>
<Text>{sumProd}</Text>
</View>
)

Related

array wont render on React state change

I have this useState hook:
const [products, setProducts] = useState([])
and I have these functions:
const sortByLow =()=>{
const newArray = products.sort((a,b)=>b.price-a.price)
setProducts(newArray)
console.log(newArray);
}
const sortByHigh =()=>{
const newArray = products.sort((a,b)=>a.price-b.price)
setProducts(newArray)
console.log(newArray);
}
a useEffect hook:
useEffect(()=>{
const displayProducts = async()=>{
try {
//fetch from server at port 3000
const response = await fetch('http://localhost:3000/')
if(!response.ok){
throw new Error("displayProducts response is not ok")
}
const responseDataObject = await response.json()
const allProducts = responseDataObject.data.allProducts
setProducts(allProducts);
} catch (error) {
console.log("theres an error" + error);
}
}
//call the function, duh
displayProducts();
}, [])
and the return value of the component is this:
<div>
{products.filter( product => {return (product.price > lowPrice && product.price < highPrice)} ).map(productObj => <ProductComponent
navigateToProduct = {productObj._id}
navigateToCategory = {productObj.category}
key = {productObj._id}
name = {productObj.name}
category = {productObj.category}
price = {productObj.price}
description = {productObj.description}
image = {productObj.image}
/>)}
</div>
now I expect the product array to change according to the functions above but it wont happen for some reason.
what can be the problem? please help me
thanks!
ok I figured it out thanks to #KonradLinkowski comment... The sort only references the original array, so in order to create a new array I should have written [...products] as the source array, as follows:
const sortByLow =()=>{
const newArray = [...products].sort((a,b)=>b.price-a.price)
setProducts(newArray)
console.log(newArray);
}
const sortByHigh =()=>{
const newArray = [...products].sort((a,b)=>a.price-b.price)
setProducts(newArray)
console.log(newArray);
}
Thanks to all who read and helped!
When you sort through an array it does not make a new reference ID so it does not know to update the state. This is how you can force it to make a new reference
const sortByLow = () => {
const newArray = [...products];
newArray.sort((a, b) => b.price - a.price);
setProducts(newArray);
console.log(newArray);
};
const sortByHigh = () => {
const newArray = [...products];
newArray.sort((a, b) => a.price - b.price);
setProducts(newArray);
console.log(newArray);
};
This should update the react state

Set interval on component mount is getting too fast and is not spreading my array

Goal is to display a real time log that comes from an async function ( this func will return the last log ) and is updated each second. After that i want to accumulate the results on an array and if it get bigger than 5 i want to remove the first element.
Expected: A list of 5 items displayed on screen that is updated each second.
Results: Array is not accumulating above 2 items and the update is random and fast
code ->
const [logs, setLogs] = useState([])
const getLogs = async () => {
const lastLog = await window.getAppLogs()
if (logs.length > 5) {
// here i tried this methods ->
// reduceLogs.shift()
// reduceLogs.splice(0, 1)
const reduceLogs = [...logs, lastLog ]
delete reduceLogs[0]
return setLogs(reduceLogs)
}
const test = [...logs, lastLog] // this line is not accumulating above 2
setLogs(test)
}
useEffect(() => {
setInterval(getLogs, 1000);
}, [])
Updating state in setinterval needs to use the => syntax. So it should be -
setLogs(prevState => [...prevState.slice(-4), lastLog])
Plus you need to clear the interval as well. I've made a demo to display last 5 users when they are updated every second (from api).
export default function App() {
const [users, setUsers] = useState([
"michael",
"jack",
]);
const getUser = async () => {
const response = await fetch("https://randomuser.me/api/?results=1");
const user = await response.json();
return user;
};
const getUsers = async () => {
const user = await getUser();
setUsers(prevState => [...prevState.slice(-4), user.results[0].name.first]);
};
useEffect(() => {
const handle = setInterval(getUsers, 1000);
return () => {
clearInterval(handle);
};
}, []);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
{users.map((log) => (
<div>{log}</div>
))}
</div>
);
}
Update: This should be your getLogs function, no if check, no return etc.
const getLogs = async () => {
const lastLog = await window.getAppLogs()
setLogs((prevState) => [...prevState.slice(-4), lastLog]);
}
hey AG its almost working perfectly, updates are constant now and it is accumulating. But is still not reducing length of the array. Its like u show me:
const [logs, setLogs] = useState([])
const getLogs = async () => {
const lastLog = await window.getAppLogs()
if (logs.length > 4) {
return setLogs((prevState) => [...prevState.slice(-4), lastLog])
}
setLogs((prevState) => [...prevState, lastLog])
}
useEffect(() => {
const handle = setInterval(getLogs, 2500);
return () => {
clearInterval(handle);
};
}, [])

React js, javascript not filtering properly

i am trying to make a filter feature for a website i am working on, i am using an html range slider. The problem is that the values update just if they are going down, for example if i set the slider to $500, only the products that cost $500 or less will appear, if i set the value lower, it's going to work how is supposed to work, but if i try to set the value bigger, the items will not filter, for example, the value is set to $500, if set the value to $600 only the items that are $500 or less will render, but not the $600 ones.
here is my code:
const Shop = () => {
const [sliderValue, setValue] = useState(0);
const [filterItems, setApplyFilter] = useState(false);
const [newData, setData] = useState(data);
const checkChange = () => {
if (sliderValue > 3) {
setApplyFilter(true);
} else {
setApplyFilter(false);
}
console.log(applyFilter);
};
const applyFilter = () => {
if (filterItems === true) {
const filteredData = newData.filter((item) => item.price <= sliderValue);
console.log(filteredData);
setData(filteredData);
} else {
setData(data);
}
};
useEffect(() => {
checkChange();
applyFilter();
}, [sliderValue]);
const handleChange = (value) => {
setValue(value);
};
return (
<div className="slider-container">
<input
type="range"
min={0}
max={1000}
value={sliderValue}
onChange={(e) => handleChange(e.target.value)}
className="slider"
/>
</div>
);
}
The problem: you are changing the data with setData(), so every time you move your scrollbar this deletes some data. If you want to keep a constant information that is available to all your application, consider using useRef(). This creates a persistent object for the full lifetime of the component.
import { useRef } from 'react'
const Shop = () => {
const dataArr = useRef(data)
...
const applyFilter = () => {
if (filterItems === true) {
// Access with "current" attribute
const filteredData = dataArr.current
.filter((item) => item.price <= sliderValue);
setData(filteredData);
}
}
}
Working example
I think it's something to do with this two lines:
const filteredData = newData.filter((item) => item.price <= sliderValue);
setData(filteredData);
Once you have filtered your data once, the value of newData in your state will be only the already filtered data.
Let's say we start with prices: newData=[100, 200, 300, 400]
We filter it for the first time down to 200, so now newData=[100, 200]
Next we filter up to 300, but newData only has [100, 200]
So just change those two lines for:
const filteredData = data.filter((item) => item.price <= sliderValue);
setData(filteredData);
This is asuming you have a variable data declared or imported somewhere with the comple data set.
You don't need state for data array since it can be determined on every render based on some other state.
const Shop = ({ inputData }) => {
const [sliderValue, setValue] = useState(0);
// This flag is deterministic based on sliderValue, so determine it here
const filterItems = sliderValue > 3;
// The items that will make it past the filter are deterministic, based on your filterItems flag
// so no state is necessary
const renderItems = filterItems ? inputData.filter(i => i.price <= sliderValue) : inputData;
const handleChange = (value) => {
setValue(value);
};
return ...
};

How to get sum of array from two different input in two different components? React

I am new to React and I am building a budget calculator. I am taking the amount from one input and adding it to another input so I can come up with the balance. I have tried reduce and concat and they are coming up to sum but the value is wrong. I don't know what I'm doing wrong. Can anyone point me in the right direction. I think the problem is that the values are rendering twice and that's throwing off the math. I don't know.
Here is my code:
// this is the component to get the balance
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
balance: []
}
}
getBalance = (total) => {
this.setState((prevState) => ({
balance: [prevState.balance, total].reduce((acc, currentVal) => {
return Number(currentVal) + Number(acc)
}, 0)
}));
}
render() {
return (
<div className="App" >
<div className="count">
<h2 className="balancetitle">Your Balance</h2>
<h1 style={{ color: this.state.balance >= 0 ? 'green' : 'red' }}>${this.state.balance}</h1>
</div>
<Transactions getBalance={(total) => this.getBalance(Number(total))} />
<Income getBalance={(total) => this.getBalance(Number(total))} />
</div>
);
}
}
// this is the code to get the transaction. I have another component that is identical to get the sum of the income.
const Transactions = (props) => {
const [expenses, setExpense] = useState([])
const [amount, setAmount] = useState([])
const [id, setId] = useState([])
const [listOfTrans, setListofTrans] = useState([])
const [total, setTotal] = useState([0])
//fires on click or enter
const handleSubmit = (e) => {
e.preventDefault()
addExpense({
amount,
expenses,
id
});
setAmount('')
setExpense('')
}
//get value of inputs
const getValue = (hookSetter) => (e) => {
let { value } = e.target;
return hookSetter(value)
}
// turn amount and expense into objects and put them setListofTranas
const addExpense = (expenseObject) => {
setListofTrans([...listOfTrans, expenseObject])
}
const show = () => {
if (listOfTrans.legnth > 1) {
return listOfTrans
} else return null
}
// get total amount of listoftrans
const getAmount = () => {
if (listOfTrans.length > 0) {
let listAmount = listOfTrans.map(list => {
if (list.amount) {
return -Math.abs(list.amount);
} else {
return 0;
}
})
return listAmount.reduce((acc, currentValue) => {
return Number(acc) + Number(currentValue)
}, 0)
} else return 0
}
//update amount total on click
useEffect(() => {
setTotal(getAmount())
props.getBalance(getAmount())
}, [listOfTrans])
// delete item from array
const deleteExpense = (i) => {
let objExpense = i
setListofTrans(listOfTrans.filter((list) => {
return list.id !== objExpense
}))
}
I am adding it here as the suggestion is not possible to add long description in comments section.
What you are doing buggy in the the solution above is making use of useEffect to do the calcualtions. The approach can be real buggy and difficult to debug.
//update amount total on click
useEffect(() => {
setTotal(getAmount())
props.getBalance(getAmount())
}, [listOfTrans])
In the code above listOfTans is an array , may be changing due to various operation, which cause the useEffect callback to run repeatedly. The callback is reponsible for updating the sum.
So instead of doing that, you should just call
props.getBalance(getAmount())
in onClick Handler.
This is just the suggestion for what I can understand from the question above.

How to show all my arrays with Firebase and React?

I've some pushes in Firebase, and I've the data. I've separate the arrays side by side, and I want to show they. But, with my code, I just have the last array on my all arrays.
How to show all the arrays side by side ?
My code :
// useEffect()
let postJSON
firebase.database().ref('plugins/posts/').on('value', (snapshot) => {
const json = snapshot.toJSON()
for (const i in json) {
const element = json[i]
postJSON = [element.name, element.description, element.price, element.linkPlugin]
console.log(postJSON)
setPost(postJSON.map((x, i) => <p key={i}>{x}</p>))
}
})
// Render
return (
{post}
)
try
const MyComponent = () => {
const [posts, setPosts] = useState([]);
useEffect(() => {
firebase.database().ref('plugins/posts/').on('value', (snapshot) => {
const json = snapshot.toJSON()
const keys = Object.keys(json);
const postJSON = keys.map(key => {
const element = json[key];
return [element.name, element.description, element.price, element.linkPlugin]
});
setPosts(postJSON);
})
}, []);
return (
<div>{posts.map((x, i) => <p key={i}>{x}</p>)}</div>
)
}
as is you are calling setPosts 3 times, once for each array item, each time overriding the previous call to setPosts. You need to just call it once with an array of arrays

Categories