So i am currently using states to determine if a user has added an item to their cart or not. It is working almost flawlessly other than when they are on the "Product Page"(The page where they add to cart), and they refresh it empties out the in_cart array, but if im on the home page after adding them i can refresh all i want, this means it has to be something in the product page code but cant figure it out, here is the product page code:
const ProductPageBody = ({ products, in_cart, set_in_cart }) => {
//Keeps track of color user selects
const [color, setColor] = useState("");
//Keeps track of size user selects
const [size, setSize] = useState("Small");
//Filters out the product that the user selected
const { shirtName } = useParams();
const shirt = products.filter((product) => product.name === shirtName);
//Updates state size of shirt being selected
const updateSize = () => {
let select = document.getElementById("sizeSelect");
let text = select.options[select.selectedIndex].text;
setSize(text);
};
//Updates state color of shirt being selected
const updateColor = useCallback(async (userColor, shirt) => {
const shirtColorSource = await fetch(
`http://localhost:5000/products/${shirt.product_id}/${userColor}`
);
const shirtColor = await shirtColorSource.json();
console.log(shirtColor);
shirt.image = shirtColor[0].image;
setColor(userColor);
}, []);
//Occurs when ADD TO CART is clicked
const updateInCart = async (e) => {
e.preventDefault();
const newShirt = { ...shirt[0] };
newShirt["color"] = color;
newShirt["size"] = size;
const newList = in_cart.list.concat(newShirt);
const cost = newList.reduce((sum, shirt) => sum + shirt.price, 0);
set_in_cart({
list: newList,
totalcost: cost,
});
};
//Stores in cart items
useEffect(() => {
localStorage.setItem("inCartItems", JSON.stringify(in_cart));
}, [in_cart]);
and its parent where the state is located:
const Routes = () => {
const [products, setProducts] = useState([]);
const [in_cart, set_in_cart] = useState({
list: [],
totalCost: 0,
});
const getProducts = async () => {
try {
const response = await fetch("http://localhost:5000/products/");
const jsonData = await response.json();
setProducts(jsonData);
} catch (err) {
console.error(err.message);
}
if (localStorage.getItem("inCartItems")) {
set_in_cart(JSON.parse(localStorage.getItem("inCartItems")));
}
};
useEffect(() => {
getProducts();
}, []);
any help would be appreciated, thank you!
In Routes, add an effect to persist the cart data (in_cart) to localStorage when it updates.
useEffect(() => {
try {
localStorage.setItem("inCartItems", JSON.stringify(in_cart));
} catch(err) {
// do something if cart persistence fails
}
}, [in_cart]);
Related
Using React Native Async Storage. I have a single storage item "favorites" which holds an array of post IDs. Works great, adding and removing articles successfully, but there is a problem:
In "Favorites" TabScreen - showing all currently favorited posts - it works but is rendering an outdated list of items.
E.g. Load the app (Expo), the Favorites screen shows current list, but if I go ahead and a remove an item from the array, and go back to the Favorites screen, it still shows the removed item. Same if I add a new item and navigate back to Favorite screen, new item missing.
It only updates if I reload the app.
If you don't mind taking a look, here's the relevant code:
const POSTS_QUERY = gql`
query posts {
posts {
data {
id
attributes {
title
}
}
}
}
`
export default ({ navigation }) => {
const [isLoading, setIsLoading] = useState(true)
const [isAsyncLoading, setIsAsyncLoading] = useState(true)
const [nodes, setNodes] = useState({})
const favCurrent = useRef();
const getFavorites = async () => {
try {
const value = await AsyncStorage.getItem('favorites')
if(value !== null) {
const val = JSON.parse(value)
favCurrent.current = val
setIsAsyncLoading(false)
console.log(favCurrent.current,'favCurrent')
}
} catch(e) {
// error reading value
}
}
getFavorites()
// note the console.log above always shows correct, updated list
const { data, loading, error } = useQuery(POSTS_QUERY)
useEffect(() => {
if (loading) {
return <Loading/>
}
if (error) {
return <Text>Error :( </Text>;
}
const filterNodes = data?.posts?.data.filter(item => favCurrent.current.includes(item.id));
setNodes(filterNodes)
setIsLoading(false)
}, [data]);
if( isLoading || isAsyncLoading ) {
return (
// 'Loading...'
)
} else {
return (
// 'List of items...'
)
}
}
I've also tried a solution from this answer, to no effect:
const [favData, setFavData] = useState(null)
useEffect(() => {
async function getFavorites() {
setFavData(await AsyncStorage.getItem('favorites'));
}
getFavorites()
setIsAsyncLoading(false)
}, []);
I have App component where the list of items is shown, and ModalForm where you can add a new one. But when I'm adding new item I have to reload the page to see it in my list. I want to trigger somehow my GET method after I execute my POST method.
**App.js**
const App = () => {
let books = useAxiosGet(API_URL)
//some code
return (
//some code
<ModalForm />
);
}
**ModalForm.js**
const ModalForm = () = > {
//some logic for opening modal form
function submit(e) {
e.preventDefault();
axios.post(API_URL, {
name: data.name,
description: data.description,
count: data.count,
imageURL: data.imageURL,
price: data.price
})
.then(res => {
// document.location.reload();
// here I want to trigger GET
console.log(res.data);
})
.catch((err) => {
console.log(err);
})
setData({
name: "",
description: "",
count: "",
imageURL: "",
price: ""
})
}
function handle(e){
const newData={...data}
newData[e.target.id] = e.target.value
setData(newData)
console.log(newData)
}
return (//some code);
}
You should use React State to store your books.
https://ar.reactjs.org/docs/state-and-lifecycle.html
Make a state for books
const App = () => {
// Books storage.
const [books, setBooks] = useState([]);
}
Make a function that loads the books.
const App = () => {
// Books storage.
const [books, setBooks] = useState([]);
// Load books
const loadBooks = () => {
const response = useAxiosGet(API_URL);
setBooks(response );
}
}
Loads the books when component mound using useEffect hook.
https://ar.reactjs.org/docs/hooks-effect.html
const App = () => {
// Books storage.
const [books, setBooks] = useState([]);
// Load books
const loadBooks = () => {
const response = useAxiosGet(API_URL);
setBooks(response );
}
// Load books when component mount.
useEffect(loadBooks, [])
}
In your submit function, after the submission success, you can just call loadBooks again.
const submit = () => {
...
.then(() => {
loadBooks();
})
}
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.
I am stuck on this for some reason. I know how to use .sort when there is a simple array. I am not quite sure how to sort a nested object in an array using a variable in that object. I can sort it, but I am not sure how to display it.
Here is what I am working with. I get data from a database and map over that data to display it. Everything works as expected. Now I want to take that data and sort it by artist.
Here is the code I am working with.
export default function ShowRecords() {
const classes = recordFormStyles();
const url = " http://localhost:5000";
//get userData state to use in useEffect
//set state for showing records in database and opening/closing modals
const [newRecords, newRecordData] = React.useState([]);
const [editOpen, handleEditModal] = React.useState(false);
const [addModalOpen, handleAddModal] = React.useState(false);
//set state for edit records
const [title, setTitle] = React.useState("");
const [artist, setArtist] = React.useState("");
const [rating, setRating] = React.useState("");
const [genre, setGenre] = React.useState("");
const [description, setDescription] = React.useState("");
const [userId, setUserId] = React.useState("");
//set state for favorite icon
const [favorite, setFavorite] = React.useState([]);
const fetchFavoriteData = async () => {
const result = await axios.get(url + "/favorite/get", authToken);
setFavorite(result.data);
};
const addFavorites = async (_id, title, artist, rating, genre, description, isFavorite) => {
const favorites = {
userId: _id,
title,
artist,
rating,
genre,
description,
isFavorite
};
const result = await axios.post(
url + "/favorite/add",
favorites,
authToken
);
setFavorite(result.data);
};
const deleteFavorite = async (title) => {
await axios.delete("http://localhost:5000/favorite/delete", {
data: { title: title },
authToken,
});
};
//functions to control state
const handleAddModalOpen = () => {
handleAddModal(true);
};
const handleCloseAddModal = () => {
handleAddModal(false);
};
const handleIsEditModalClose = () => {
handleEditModal();
};
//fetch record data
const fetchData = async () => {
const result = await axios.get(url + "/record/get", authToken);
newRecordData(result.data);
};
React.useEffect(() => {
fetchData();
fetchFavoriteData();
}, []);
// delete records
const deleteRecord = async (_id) => {
const deleteRecords = {
_id: _id,
};
await axios.delete(url + "/record/" + _id, deleteRecords).then((result) => {
const refresh = newRecords.filter((result) => result._id !== _id);
newRecordData(refresh);
});
};
//functions for controlling edit record state
const editRecord = (_id, title, artist, rating, genre, description) => {
setUserId(_id);
setTitle(title);
setArtist(artist);
setRating(rating);
setGenre(genre);
setDescription(description);
handleEditModal(true);
console.log(title);
};
//functions for setting favorite state and color and post request to add favorite
return (
<div>
{/* set props */}
<Favorites />
<AddRecord
isAddModalOpen={addModalOpen}
handleIsAddModalClose={handleCloseAddModal}
addNewRecords={newRecords}
handleIsAddModalOpen={handleAddModal}
refreshRecordData={newRecordData}
/>
<EditRecords
editModalOpen={editOpen}
handleCloseEditModal={handleIsEditModalClose}
editUserId={userId}
editTitle={title}
editArtist={artist}
editRating={rating}
editGenre={genre}
editDescription={description}
editTitleState={setTitle}
editArtistState={setArtist}
editRatingState={setRating}
editGenreState={setGenre}
editDescriptionState={setDescription}
editUrl={url}
editFetchData={fetchData}
editNewRecordData={newRecordData}
/>
<Button
className={classes.addButton}
onClick={() => handleAddModalOpen(true)}
>
Add Record
</Button>
<div className={classes.cardsContainer}>
<Grid container spacing={8} style={{ padding: 80 }} justify = "center">
{newRecords.length > 0 &&
newRecords.map((element) => (
<RecordCard
key = {element._id}
element={element}
editRecord={editRecord}
deleteRecord={deleteRecord}
addFavorites = {addFavorites}
deleteFavorite = {deleteFavorite}
favorite = {favorite}
/>
))}
</Grid>
</div>
</div>
);
}
I get the data in my uesEffect and I want to sort it using the Arist name. I am just unsure on how to do that. I couldn't find much googling.
Sort the data before you save it into state. The sort function can take in a function that returns -1, 0, 1 to determine how things should be ordered. The below example uses the localeCompare function to sort by the artist.
let data = [
{ artist: 'john', record: '1' },
{ artist: 'mary', record: '2' },
{ artist: 'bob', record: '3' }
];
let sorted = data.sort((a,b) => (a.artist.localeCompare(b.artist)));
console.log(sorted);
so I am trying to fetch images from Firebase storage and then display them in a React component. Frankly, I'm new to React/Javascript and I'm having a hard time understanding the asynchronous nature of React/JS and I'm running into the problem where my images are fetched but the component is not re-rendered upon completion of the fetch...
This is the code on my main page, in the useEffect function, I am trying to fetch the images and then store their download urls in an array and then set that array in state (upon page load i.e. only once). Since these are promises, its not happening synchronously and when I first load the page it displays no images. If I, however, click on a different component on the page, it re-renders my Pictures component and the images show up(??), so I know the fetch has worked.
let storageRef = firebase.storage().ref()
let calendarRef = React.createRef()
const position = props.location.state.name.indexOf("#")
const username = props.location.state.name.substring(0, position);
const [simpleDate, setSimpleDate] = useState(null)
const [selectedDate, setSelectedDate] = useState('')
const [showModal, setShowModal] = useState(false)
const [showCancelModal, setShowCancelModal] = useState(false)
const [expectedPeople, setExpectedPeople] = useState(null)
const [events, setEvents] = useState([])
const [helpModal, showHelpModal] = useState(false)
const [pictureURLs, setPictureURLs] = useState([])
useEffect(() => {
//load pictures
const fetchImages = async () => {
let urls = []
storageRef.child('cabinPictures').listAll().then((result) => {
result.items.forEach((imageRef) => {
imageRef.getDownloadURL().then(url => {
urls.push(url)
})
})
})
return urls;
}
fetchImages().then(urls => {
setPictureURLs(urls);
console.log("inside .then() " + pictureURLs)
})
//fetch reservations
firebase
.firestore()
.collection('reservations')
.onSnapshot(serverUpdate => {
const reservations = serverUpdate.docs.map(_doc => {
const data = _doc.data();
data['id'] = _doc.id;
return data;
});
let fetchedEvents = reservations.map(reservation => {
let date = reservation.reservationDate.toDate()
const month = ("0" + (date.getUTCMonth() + 1))
let dateString = date.getUTCFullYear() + "-" + ("0" + (date.getUTCMonth()+1)).slice(-2) + "-" + ("0" + date.getUTCDate()).slice(-2)
return {title: reservation.username + " - " + reservation.numPeople + " total", date: dateString, id: reservation.id, totalPeople: reservation.numPeople, month: month}
})
console.log(fetchedEvents)
setEvents(fetchedEvents)
});
}, [])
My Pictures component in the main page where the useEffect (above) is run. I pass the urls from state as a prop:
<div className="pictures-div-container">
<Pictures pictureURLs={pictureURLs}>
</Pictures>
</div>
The code for my Picture component:
import React, { useState, useEffect } from 'react'
import styles from "./styles.css"
const firebase = require('firebase');
const Pictures = (props) => {
const [uploadImage, setUploadImage] = useState(null)
const [progressValue, setProgressValue] = useState(0)
let storageRef = firebase.storage().ref()
let { pictureURLs } = props
const handleUpload = () => {
setProgressValue(0)
const uploadTask = storageRef.child(`cabinPictures/${uploadImage.name}`).put(uploadImage)
uploadTask.on('state_changed',
(snapshot) => {
//progress function
const progress = Math.round((snapshot.bytesTransferred / snapshot.totalBytes ) * 100)
setProgressValue(progress)
},
(error) => {
//error function
console.log(error)
},
() => {
//complete function
storageRef.child('cabinPictures').child(uploadImage.name).getDownloadURL().then(url => {
console.log(url)
} )
});
}
const handleFileSelect = (e) => {
if (e.target.files[0]) {
setUploadImage(e.target.files[0])
}
}
return (
<div className="pictures-container">
<h2>Upload a Picture!</h2>
<button className="upload-button" onClick={() => handleUpload()}>Upload</button>
<input type="file" onChange={(e) => handleFileSelect(e)}></input>
<progress value={progressValue} max="100"></progress>
<div className="pictures">
{
pictureURLs.map((url, index) => {
return <img className="picture" key={index} src={url}></img>
})
}
</div>
</div>
)
}
export default Pictures
So, can anyone help me understand why the Pictures component is not re-rendering automatically when the state is set after fetching the picture urls from firebase? I thought that when a prop changes in a component, the whole component is re-rendered?
EDIT:
So this is what I changed in my main page's useEffect function as per the answer's suggestions, and it works!
//fetch and load pictures
const fetchImages = async () => {
let result = await storageRef.child('cabinPictures').listAll();
let urlPromises = result.items.map(imageRef => imageRef.getDownloadURL())
return Promise.all(urlPromises)
}
const loadImages = async () => {
const urls = await fetchImages()
setPictureURLs(urls)
}
loadImages()
You have to let all the nested promises resolve before you return urls
I am not very current on firebase API so am not sure if result.items is an actual array or an object that has a foreach method. Following should work if it is a js array
Try something like:
//load pictures
const fetchImages = async() => {
let result = await storageRef.child('cabinPictures').listAll();
/// map() array of the imageRef.getDownloadURL() promises
let urlPromises = result.items.map(imageRef => imageRef.getDownloadURL());
// return all resolved promises
return Promise.all(urlPromises);
}
const urls = await fetchImages()
setPictureURLs(urls);
console.log("inside .then() ", urls)