Storing E-Commerce Product Data On LocalStorage - reactjs

I am busy working on a "Headless" E-Commmerce Application in ReactJS and I Have stumbled upon an issue regarding performance.
My application uses a serverless approach with commercejs meaning I fetch my products and every other data via API calls instead of a traditional approach that involves me setting up a database and having other backend tools.
I already have this:
const App = () => {
const [products, setProducts ] = useState([]);
const getProducts = async () => {
const { data } = await commerce.products.list();
setProducts(data)
}
useEffect(() => {
getProducts()
},[])
}
Which is all used to get products and assign them to the products variable which I map through to display them inside divs and this works perfectly.
Here's what I need help with:
Is it be a good idea for me to use localStorage to store products instead of making a new commerce.products.list() every time the same user visits a page that need to display products?
Also, if so, how would one go about creating a function that knows when to update the products localStorage if there has been any changes (say a new product has been added or there's been price change for a certain product) if the products are now being fetched from localStorage?
Surely the answer to number 2, if is a yes, will be something like: make api requests that will be called on intervals but my main question/concern is how to know exactly when localStorage should be updated in an event like this without calling the API every now and then?
Also the use of sessionStorage did cross my mind, and it seemed like a better idea as the products data will be updated every time a user visits the application but I have considered the possibility of a user resuming a current window which was left open in the background for weeks and not see anything new.
I know this isn't a typical code/error/solution question but still any form of guidance/assistance will be highly appreciated.

Related

Next.js: How to cache initial data on the server (same for all users)

I'm learning Next.js and started building one of my first applications - https://www.codart.io/
If you visit the site, you'll notice a delay until the collections become available: The application makes an initial graphql request to retrieve a json file with a number of NFT collections, which takes a few seconds. This data needs to be available across different components, so I built a global context to store the data (I'm not sure this is the best approach):
https://github.com/bartomolina/codart/blob/main/app/components/collections-context.tsx
const ArtBlocksContext = createContext({
aBCollections: [] as IABCollection[],
cACollections: [] as ICACollection[],
fetchCACollections: () => {},
});
export const useArtBlocks = () => useContext(ArtBlocksContext);
export const ArtblocksProvider = ({ children }: React.PropsWithChildren) => {
...
useEffect(() => {
fetchCACollections();
execute(ArtblocksCollectionsDocument, {}).then((result) => {
I wonder what would be the best way to cache this data (as the data doesn't change often, it could be cached at build time, or ideally, indicating an expiration date i.e. 24 hours). Note that the cache will be shared between all users.
I've been looking at some similar posts, and I got some ideas, although it seems there isn't a clear and simple way to do this:
Using Redux - Seems an overkill for a small project like this.
Use some custom caching libraries - I'd rather not use any external libraries.
Use getStaticProps along with the Context API - It seems you can't getStaticProps within the _app.ts page, so you would need to call it in every page where the context is used?
Use SWR and an API call - SWR will cache the data, but on a per-user basis? i.e. the first time a user visits the site, it will still take a few seconds to load.

How do we persist redux store without using localstorage and make full reactjs web application work offline

I have to do full offline functionality like all redux store data should remain after page refresh/offline(network connection lost).
When user goes offline previously stored data should be used in viewing pages & for suppose i have big forms(ex: employee form with all work experience and personal details)suddenly if user loose network, data should not be lost.. should be saved somehow & after that when user is online have to store data to DB
I am using react -18.0.0 version with react-redux & redux-saga.What are the possible ways?
lets say users are required to log in to system .once login is successfull save that
data to local storage then dispatch loginsuccess with user and token as payloads
execute a function inside useEffect in app.js to get back saved data from local storage and dispatch
loginsuccess with those data
inside signup function:
let res=await axios.post("/signin",{user});
let{user,token}= res.data;
localStorage.setItem("user", JSON.stringify(user));
localStorage.setItem("token",token);
dispatch({ type: "loginsuccess",payload:{user,token}});
inside app.js :
useEffect(() => {
dispatch(isUserLoggedIn());
}, []);
inside isUserLoggedIn :
const localstorage = localStorage.getItem("user");
if (localstorage) {
const user = JSON.parse(localStorage.getItem("user"));
dispatch({ type: "loginsuccess",payload:{user}});
} else {
dispatch({ type: "loginfailed",payload:{error:"please log in"}});
}
One way can be to store your form(or state) in localstorage. Another way can be use library redux-persist .
I think your question is similar to the one asked here. Have a look here for more ideas.
Another approach to store your data, is to make use of the IndexedDB API, it already has a great support on the latest browser versions caniuse.
There is a really good library that make use of this api but
combined with promises idb.
After picking up how you're gonna do it, you can have a watcher saga that reacts to the online / offline status and do the appropriated synchronization.
Also I think it's important to mention that all of the above will only take care of the data, you still need to make sure to make the JS and CSS to be available offline. on web.dev there is a great induction on this subject.

Trying to push to state variable with multiple API call responses

Im using the google maps API for the first time and managed to finally get the map to render to the screen, I'm now working on adding markers of locations that I receive from elsewhere in my application. When I loop through the locations to get their respective lat and lng coordinates, I want to add them to my state variable. Currently the way I have my code, I am getting a response and it is being stored in my state variable, but it is only storing the most recent one rather than all of them.
Whats the best way to add all of these seperate responses from the API to my state variable?
Here is my code currently
export const MapRender =(props) => {
const {logs} = useContext(LogContext)
const [latLong, setLatLong] = useState([])
useEffect(()=>{
logs.map(l =>{
return fetch(`https://api.opencagedata.com/geocode/v1/json?q=${l.location}&key=MYKEY&limit=1`)
.then(res => res.json())
.then(parsedRes => setLatLong([parsedRes.results[0].geometry]))
})
},[logs])
console.log(latLong) --- returns a separate log for each response object, I want it to return one array with all responses
Updated
I didn't catch this when I first answered, having that cycle that sets the state on each iteration causes a lot of renders to be queued which leads to be the last incoming respobe the one to prevail.
The solution is to store all responses in one array and when all of them are finished you can set the state.
You may want to use this to know when all of the promises are done and then set the state.

Create and Read State for thousands of items using Recoil

I've just started using Recoil on a new project and I'm not sure if there is a better way to accomplish this.
My app is an interface to basically edit a JSON file containing an array of objects. It reads the file in, groups the objects based on a specific property into tabs, and then a user can navigate the tabs, see the few hundred values per tab, make changes and then save the changes.
I'm using recoil because it allows me to access the state of each input from anywhere in my app, which makes saving much easier - in theory...
In order to generate State for each object in the JSON file, I've created an component that returns null and I map over the initial array, create the component, which creates Recoil state using an AtomFamily, and then also saves the ID to another piece of Recoil state so I can keep a list of everything.
Question 1 Is these a better way to do this? The null component doesn't feel right, but storing the whole array in a single piece of state causes a re-render of everything on every keypress.
To Save the data, I have a button which calls a function. That function just needs to get the ID's, loop through them, get the state of each one, and push them into an Array. I've done this with a Selector too, but the issue is that I can't call getRecoilValue from a function because of the Rules of Hooks - but if I make the value available to the parent component, it again slows everything right down.
Question 2 I'm pretty sure I'm missing the right way to think about storing state and using hooks, but I haven't found any samples for this particular use case - needing to generate the state up front, and then accessing it all again on Save. Any guidance?
Question 1
Get accustomed to null-rendering components, you almost can't avoid them with Recoil and, more in general, this hooks-first React world 😉
About the useRecoilValue inside a function: you're right, you should leverage useRecoilCallback for that kind of task. With useRecoilCallback you have a central point where you can get and set whatever you want at once. Take a look at this working CodeSandbox where I tried to replicate (the most minimal way) your use-case. The SaveData component (a dedicated component is not necessary, you could just expose the Recoil callback without creating an ad-hoc component) is the following
const SaveData = () => {
const saveData = useRecoilCallback(({ snapshot }) => async () => {
const ids = await snapshot.getPromise(carIds);
for (const carId of ids) {
const car = await snapshot.getPromise(cars(carId));
const carIndex = db.findIndex(({ id }) => id === carId);
db[carIndex] = car;
}
console.log("Data saved, new `db` is");
console.log(JSON.stringify(db, null, 2));
});
return <button onClick={saveData}>Save data</button>;
};
as you can see:
it retrieves all the ids through const ids = await snapshot.getPromise(carIds);
it uses the ids to retrieve all the cars from the atom family const car = await snapshot.getPromise(cars(carId));
All of that in a central point, without hooks and without subscribing the component to atoms updates.
Question 2
There are a few approaches for your use case:
creating empty atoms when the app starts, updating them, and saving them in the end. It's what my CodeSandbox does
doing the same but initializing the atoms through RecoilRoot' initialState prop
being updated by Recoil about every atom change. This is possible with useRecoilTransactionObserver but please, note that it's currently marked as unstable. A new way to do the same will be available soon (I guess) but at the moment it's the only solution
The latter is the "smarter" approach but it really depends on your use case, it's up to you to think if you really want to update the JSON at every atom' update 😉
I hope it helps, let me know if I missed something 😊

Global variables in React

I know Redux solves this but I came up with an idea.
Imagine I have an app that gets some JSON on start. Based on this JSON I'm setting up the environment, so let's assume the app starts and it downloads an array of list items.
Of course as I'm not using Redux (the app itself is quite simple and Redux feels like a huge overkill here) if I want to use these list items outside of my component I have to pass them down as props and then pass them as props again as deep as I want to use them.
Why can't I do something like this:
fetch(listItems)
.then(response => response.json())
.then(json => {
window.consts = json.list;
This way I can access my list anywhere in my app and even outside of React. Is it considered an anti-pattern? Of course the list items WON'T be changed EVER, so there is no interaction or change of state.
What I usually do when I have some static (but requested via API) data is a little service that acts kind like a global but is under a regular import:
// get-timezones.js
import { get } from '../services/request'
let fetching = false
let timez = null
export default () => {
// if we already got timezones, return it
if (timez) {
return new Promise((resolve) => resolve(timez))
}
// if we already fired a request, return its promise
if (fetching) {
return fetching
}
// first run, return request promise
// and populate timezones for caching
fetching = get('timezones').then((data) => {
timez = data
return timez
})
return fetching
}
And then in the view react component:
// some-view.js
getTimezones().then((timezones) => {
this.setState({ timezones })
})
This works in a way it will always return a promise but the first time it is called it will do the request to the API and get the data. Subsequent requests will use a cached variable (kinda like a global).
Your approach may have a few issues:
If react renders before this window.consts is populated you won't
be able to access it, react won't know it should re-render.
You seem to be doing this request even when the data won't be used.
The only downside of my approach is setting state asynchronously, it may lead to errors if the component is not mounted anymore.
From the React point of view:
You can pass the list from top level via Context and you can see docs here.
Sample of using it is simple and exists in many libraries, such as Material UI components using it to inject theme across all components.
From engineering concept of everything is a trade of:
If you feel that it's gonna take so much time, and you are not going to change it ever, so keep it simple, set it to window and document it. (For your self to not forget it and letting other people know why you did this.)
If you're absolutely certain they won't ever change, I think it's quite ok to store them in a global, especially if you need to access the data outside of React. You may want to use a different name, maybe something like "appNameConfig"..
Otherwise, React has a feature called Context, which can also be used for "deep provision" - Reference

Resources