array wont render on React state change - javascript

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

Related

Why is my data not rendering appropriately?

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

UseContext Doesn't Re-render when an component value is updated

I'm using React's context api to store an array of Favorite products.The favorites Array is filled with Boolean Value False and turned to true based on id of the products.There is collection page which displays productCards having an addtoFavorite button,Upon clicking the button disables but if the product is already present in favorites it has to disabled.
Now it works perfectly fine for the 1st Page , disabling only favorite products with the array containing values true and false based on index of the products but when navigated to another page it disables other products at the same index even though the favorites array is updated to have all values as false.If we Navigate Back or move to another page its value now remains false in the array.It looks as if UseContext updates the value of the array late or doesn't rerender on change.
I have tried implementing other stuffs but it still wouldn't re-render when the array was changed.
Here's the FavoritesContext:
const FavoritesContext = React.createContext({
addToFavorites: (id,index) => {},
favorites:[],
storedFavorites:(data) => {}
});
export const FavoritesContextProvider = (props) => {
const authCtx = useContext(AuthContext)
const token = authCtx.token;
const userId = authCtx.userId;
const [favorites,setFavorites] = useState([]);
// To retrieve stored favorites from FireBase
const retrieveStoredFavorites = (data) => {
let fav = new Array(data.length).fill(false);
setFavorites(fav);
let queryParams = '?auth=' + token + '&orderBy="userId"&equalTo="' + userId + '"';
axiosInstance.get('/Favorites.json' + queryParams)
.then((response) => {
let fetchProductData = [];
for (let key in response.data) {
fetchProductData.push({
...response.data[key],
productId: key,
});
}
let favoriteList = [];
//To find if the product is present in the Fetched Favorite products List
for(let i=0;i<data.length;i++){
let ids = data[i].id
let favoriteProducts = !!fetchProductData.find((product)=>product.id==ids)
favoriteList.push(favoriteProducts)
}
//console.log(favoriteList)
setFavorites(favoriteList)
});
}
//Add to Favorites
const addTofavoritesHandler = (Product,index) => {
axiosInstance
.post('Favorites.json?auth='+token,Product)
.then((response) => {
//console.log("SUCCESS")
})
.catch((error) => console.log(error));
let favoriteOnes = [...favorites];
favoriteOnes[index] = true;
setFavorites(favoriteOnes);
};
const contextValue = {
addToFavorites:addTofavoritesHandler,
favorites:favorites,
storedFavorites:retrieveStoredFavorites
};
return (
<FavoritesContext.Provider value={contextValue}>
{props.children}
</FavoritesContext.Provider>
);
};
export default FavoritesContext;
Now here is the Collection Page
const CollectionPage = () => {
const classes = useStyles();
const [products, setProducts] = useState([]);
const [filteredProducts, setFilteredProducts] = useState([]);
const [currentPage, setCurrentPage] = useState(1);
const [productsPerPage] = useState(9);
const [loading, setLoading] = useState(false);
const { enqueueSnackbar } = useSnackbar();
const authCtx = useContext(AuthContext);
const token = authCtx.token;
const userId = authCtx.userId;
const favoriteCtx = useContext(FavoritesContext)
const favorites = favoriteCtx.favorites
//To Display the Products in Main Content
const DisplayProductsHandler = (Data) => {
//Get value of FirstPageNumber and LastPageNumber
const indexOfLastPage = currentPage * productsPerPage;
const indexOfFirstPage = indexOfLastPage - productsPerPage;
//console.log("[Products]")
const productData = Data.slice(indexOfFirstPage, indexOfLastPage);
favoriteCtx.storedFavorites(productData)
//console.log(productData);
const updatedProductData = productData.map((product,index) => {
return (
<ProductCard
Link={`/Info/${product.id}`}
key={product.id}
Title={product.productName}
Image={product.productImage}
Value={product.price}
addToFavorites={() => addTofavoritesHandler(product,index)}
addToCart={() => addToCartHandler(product)}
disableFavoriteButton={favorites[index]}
/>
);
});
setProducts(updatedProductData);
};
//Display the Products from DisplayProductHandler
useEffect(() => {
setLoading(true);
//Scroll To Top When Reloaded
window.scrollTo(0, 0);
//To Display the Products
if (filteredProducts.length === 0) {
DisplayProductsHandler(ProductData);
} else {
DisplayProductsHandler(filteredProducts);
}
setLoading(false);
}, [currentPage, filteredProducts]);
//Add to Favorites Handler
const addTofavoritesHandler =(likedProduct,index) => {
setLoading(true);
let updatedLikedProduct = {
...likedProduct,
userId: userId,
};
favoriteCtx.addToFavorites(updatedLikedProduct,index)
//To Display ADDED TO FAVORITES Message using useSnackbar()
enqueueSnackbar("ADDED TO FAVORITES", { variant: "success" })
setLoading(false);
};
I need it to re-render every time the array in context is updated.

having n states in react, assuming that n won't be received in props

How could I have n states in a React component
Assuming that the component won't receive this n value in any props, is something that it will get from a database
Using useState will create the state, setState for each pair, but I need n pairs
Rafael
JavaScript arrays doesn't have a fixed length.
You can do something like
const [arr, setArr] = useState([]);
And when you receive n values from database just set it to the array using setArr(values)
Now arr will be an array containing n elements retrieved from database. You can then iterate over it and render them as you wish.
As T J pointed out. You can use an array in state.
Or, another option is to map n Components for each item, therefore instantiating n states.
const Example = (props) => {
const [data, setData] = useState();
useEffect(() => {
// ...fetch data
// setData(data);
});
if (data === undefined) {
return null;
}
return data.map((data) => <Item data={data} />);
};
const Item = (props) => {
const [state, setState] = useState(props.data);
return <>Example</>;
};
Or if n is literally just a number, a count. Then you could do something like this.
const Example = (props) => {
const [count, setCount] = useState();
useEffect(() => {
// ...fetch count
// setCount(count);
});
if (count === undefined) {
return null;
}
const items = [];
for (var i = 1; i <= count; i++) {
items.push(<Item />);
}
return items;
};
const Item = (props) => {
const [state, setState] = useState();
return <>Example</>;
};

Array search returns a new empty array

Below is my code with a search input hoos and I can't identify why it isn't working.
import Herois from './json/videos.json'
function App() {
const [valueInput, setValueInput] = useState('')
const [newArray, setNewArray] = useState([])
useEffect(() => {
const results = Herois.filter((i) => {
i.title.toLowerCase().includes(valueInput.toLowerCase())
})
setNewArray(results)
console.log(newArray)
}, [valueInput])
}
is always becoming an empty array
const results = Herois.filter((i) => {
// you have to return the something here
return i.title.toLowerCase().includes(valueInput.toLowerCase())
})
or
const results = Herois.filter((i) => (i.title.toLowerCase().includes(valueInput.toLowerCase())
))

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