remove items in local storage based on the cms data updates - javascript

I need to remove an item from the localStorage based on the fact that it was deleted from the CMS data.
So, I need to access cmsData collectionId in my useEffect() hook and give to each of the collections a new state. I have an access to the IDs though cmsData but I'm not sure how should I check for changes and assign a new state to each of collections in the useEffect() hook.
(Now my code just resets the whole localStorage on the reload.)
Could you give me a hint?
import React = require("react");
import {PropsWithChildren, useEffect, useState} from "react";
import {CollectionId} from "../../DataTypes";
import {ExpandingContext, ExpandingInfo} from "../../Expanding";
import {useCmsData} from "../../api/CmsDataProvider";
function ExpandControllerComponent (props: PropsWithChildren<any>) {
const [state, setState] = useState(() => getPersistState())
const cmsData = useCmsData();
useEffect(() => {
cleanUpPersistState()
}, [])
const expandingInfo: ExpandingInfo = {
state: state,
toggleExpand: collectionId => {
setState((state) => {
const isExpanded = expandingInfo.state.get(collectionId) ?? true
state.set(collectionId, !isExpanded)
const newState = new Map<CollectionId, boolean>(state)
persistState(newState)
return newState
})
},
}
return (
<ExpandingContext.Provider value={expandingInfo}>
{props.children}
</ExpandingContext.Provider>
)
}
const persistenceKey = "expandingState"
function persistState (state: Map<CollectionId, boolean>) {
const json = JSON.stringify(Array.from(state.entries()))
localStorage.setItem(persistenceKey, json)
}
function getPersistState (): Map<CollectionId, boolean> {
const json = localStorage.getItem(persistenceKey)
return new Map(JSON.parse(json))
}
function cleanUpPersistState () {
localStorage.removeItem(persistenceKey);
}
export = ExpandControllerComponent

You can check for changes in useEffect Hook by useEffect({},[cmsData]), if cmsData gets some value assigned or updated it the useEffect will run. Be careful useEffect will also run on page load and only run when cmsData values changes. Further more you can also listen for other variable by useEffect({}, [cmsData, otherVar, etc]) by this way useEffect will also listen for those variables and run when any of the values provided in the array changes.

Related

how to memorize a value obtained from redux REACTJS

I want to obtain a value from a variable hosted in redux and memorize it, to later compare it with itself and verify if it is different.
I need, for example, to do the following : const value = useSelector(state => state.data.value); (suppose that here the value is 0) now, when value changes, i need to compare it with the value it had previously
If you want to check what the value was on the previous render, you can save it in a ref:
const SomeComponent = () => {
const value = useSelector(state => state.data.value)
const prevRef = useRef();
useEffect(() => {
// Once the render is complete, update the ref's value
prevRef.current = value;
});
// Do something comparing prevRef.current and value
}
If you're doing this a lot you might find it useful to make a custom hook:
const usePrevious = (value) => {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.curren;t
}
// used like:
const SomeComponent = () => {
const value = useSelector(state => state.data.value)
const prev = usePrevious(value);
// Do something comparing prev and value.
}
You have to use selectors(i use 'reselect' library for that), such as:
file: selectors.js
import { createSelector } from 'reselect';
const stateEnvironments = state => state.environments;
export const selectEnvironments = createSelector([stateEnvironments],
environments => environments.data);
so then in your component you can use mapStateToProps with reselect and connect
import { createStructuredSelector } from 'reselect';
import { connect } from 'react-redux';
// your component here
const EnvironmentCurrencies = props => {
const {
data,
} = props;
return (
<div>
...
</div.
);
};
const mapStateToProps = createStructuredSelector({
data: selectEnvironments,
});
// here you can update your values with actions
// mapDispatchToProps is especially useful for constraining our actions to the connected component.
// You can access these via `this.props`.
const mapDispatchToProps = dispatch => ({
setEnvironment: environment => dispatch(actions.setEnvironment(environment)),
});
export default connect(mapStateToProps, mapDispatchToProps)(Environments);
this is not a full working example and the version that you might use, can have a bit different synaxis, but I hope this gives you a kick start. If not, let me know, I'll add more extensive example

Only one item is added in state when adding multiple with multiple setState calls

For learning purposes, I'm creating an e-shop, but I got stuck with localStorage, useEffect, and React context. Basically, I have a product catalog with a button for every item there that should add a product to the cart.
It also creates an object in localStorage with that item's id and amount, which you select when adding the product to the cart.
My context file:
import * as React from 'react';
const CartContext = React.createContext();
export const CartProvider = ({ children }) => {
const [cartProducts, setCartProducts] = React.useState([]);
const handleAddtoCart = React.useCallback((product) => {
setCartProducts([...cartProducts, product]);
localStorage.setItem('cartProductsObj', JSON.stringify([...cartProducts, product]));
}, [cartProducts]);
const cartContextValue = React.useMemo(() => ({
cartProducts,
addToCart: handleAddtoCart, // addToCart is added to the button which adds the product to the cart
}), [cartProducts, handleAddtoCart]);
return (
<CartContext.Provider value={cartContextValue}>{children}</CartContext.Provider>
);
};
export default CartContext;
When multiple products are added, then they're correctly displayed in localStorage. I tried to log the cartProducts in the console after adding multiple, but then only the most recent one is logged, even though there are multiple in localStorage.
My component where I'm facing the issue:
const CartProduct = () => {
const { cartProducts: cartProductsData } = React.useContext(CartContext);
const [cartProducts, setCartProducts] = React.useState([]);
React.useEffect(() => {
(async () => {
const productsObj = localStorage.getItem('cartProductsObj');
const retrievedProducts = JSON.parse(productsObj);
if (productsObj) {
Object.values(retrievedProducts).forEach(async (x) => {
const fetchedProduct = await ProductService.fetchProductById(x.id);
setCartProducts([...cartProducts, fetchedProduct]);
});
}
}
)();
}, []);
console.log('cartProducts', cartProducts);
return (
<>
<pre>
{JSON.stringify(cartProductsData, null, 4)}
</pre>
</>
);
};
export default CartProduct;
My service file with fetchProductById function:
const domain = 'http://localhost:8000';
const databaseCollection = 'api/products';
const relationsParams = 'joinBy=categoryId&joinBy=typeId';
const fetchProductById = async (id) => {
const response = await fetch(`${domain}/${databaseCollection}/${id}?${relationsParams}`);
const product = await response.json();
return product;
};
const ProductService = {
fetchProductById,
};
export default ProductService;
As of now I just want to see all the products that I added to the cart in the console, but I can only see the most recent one. Can anyone see my mistake? Or maybe there's something that I missed?
This looks bad:
Object.values(retrievedProducts).forEach(async (x) => {
const fetchedProduct = await ProductService.fetchProductById(x.id);
setCartProducts([...cartProducts, fetchedProduct]);
});
You run a loop, but cartProducts has the same value in every iteration
Either do this:
Object.values(retrievedProducts).forEach(async (x) => {
const fetchedProduct = await ProductService.fetchProductById(x.id);
setCartProducts(cartProducts => [...cartProducts, fetchedProduct]);
});
Or this:
const values = Promise.all(Object.values(retrievedProducts).map(x => ProductService.fetchProductById(x.id)));
setCartProducts(values)
The last is better because it makes less state updates
Print the cartProducts inside useEffect to see if you see all the data
useEffect(() => {
console.log('cartProducts', cartProducts);
}, [cartProducts]);
if this line its returning corrects values
const productsObj = localStorage.getItem('cartProductsObj');
then the wrong will be in the if conditional: replace with
(async () => {
const productsObj = localStorage.getItem('cartProductsObj');
const retrievedProducts = JSON.parse(productsObj);
if (productsObj) {
Object.values(retrievedProducts).forEach(async (x) => {
const fetched = await ProductService.fetchProductById(x.id);
setCartProducts(cartProducts => [...fetched, fetchedProduct]);
});
}
}
Issue
When you call a state setter multiple times in a loop for example like in your case, React uses what's called Automatic Batching, and hence only the last call of a given state setter called multiple times apply.
Solution
In your useEffect in CartProduct component, call setCartProducts giving it a function updater, like so:
setCartProducts(prevCartProducts => [...prevCartProducts, fetchedProduct]);
The function updater gets always the recent state even though React has not re-rendered. React documentation says:
If the new state is computed using the previous state, you can pass a function to setState. The function will receive the previous value, and return an updated value.

React useEffect dependency not triggering from async callback

I've tried to break this down to it's simplest components to understand why the useEffect in ButtonStatus is not updating when the state of eventDataId is updated in the processClickButton function of the ButtonView React Component. In this example, I'm making calls to ethereum's smart contract on my local Ganache instance, and that is working fine and returning a vew eventDataId with each call.
The use case: a user clicks a button, handleButtonClick fires off a request (in this case an ethereum smart contract call), and in the on() function, we set the state of eventDataId. I hoped this would then trigger the ButtonStatus useEffect by way of it's dependencies array, specifically [eventDataId]. I think this in turn should look up additional info and render the MintStatus.
// I've removed all extraneous lines of code, for example the .catch() and .error() handlers on the contract.methods calls.
import React, { useEffect, useState} from 'react';
const Web3 = require('web3');
const ButtonStatus = ({contract, account}) => {
const [eventDataId, setEventDataId] = useState();
const [additionalData, setAdditionalData] = useState();
function retrieveAdditionalData(contract, account) {
contract.methods.additionalData()
.call({ from: account })
.then(function(res) {
setAdditionalData(res);
});
}
useEffect(() => {
retrieveAdditionalData(contract, account);
}, [eventDataId]);
return (
<div>Event Data Id is {eventDataId} with {additionalData}</div>
);
}
class ButtonView extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
eventDataId: undefined
};
this.handleButtonClick = this.handleButtonClick.bind(this);
}
handleButtonClick() {
const setState = this.setState.bind(this);
const contract = this.props.contract;
const account = this.props.account;
contract.methods.doSomething()
.send({ from: account })
.on('confirmation', function(confirmationNumber, receipt){
let eventDataId = receipt.events['Data Event'].returnValues.eventDataId;
setState({ eventDataId: eventDataId });
});
}
render() {
return (
<button onClick={this.handleButtonClick}>Click Me</button>
<ButtonStatus contract={this.props.contract} account={this.props.account} />
);
}
}
export default ButtonView;
I'm sure I'm missing some fundamental concept here. Does the behind-the-scenes "workings" of React{ useState } even associate the eventDataId state value from ButtonView to the state value in ButtonStatus. I suspect this is the where the trouble lies.
In short, I'm looking for help understanding how the response from contract.methods.doSomething().on(...) can trigger the useEffect of ButtonStatus.
It seems you are wanting the useEffect hook in ButtonStatus to run when the eventDataId state in the parent ButtonView updates. When the state in the parent updates it will trigger a rerender, and thus rerender the child ButtonStatus component. You can pass this.state.eventDataId in as a prop to be used in the useEffect's dependency array.
ButtonView
render() {
return (
<button onClick={this.handleButtonClick}>Click Me</button>
<ButtonStatus
contract={this.props.contract}
account={this.props.account}
eventDataId={this.state.eventDataId}
/>
);
}
ButtonStatus
const ButtonStatus = ({ account, contract, eventDataId }) => {
const [additionalData, setAdditionalData] = useState();
function retrieveAdditionalData(contract, account) {
contract.methods.additionalData()
.call({ from: account })
.then(function(res) {
setAdditionalData(res);
});
}
useEffect(() => {
retrieveAdditionalData(contract, account);
}, [eventDataId]);
return (
<div>Event Data Id is {eventDataId} with {additionalData}</div>
);
}

`object' is undefined due to async function that fetched data in useEffect hook in reactjs

I am fetching an object from api using axios.get("url"). The object fetched successfully (in Animal state) but there is a component level state (imageState) which requires updation using setState with fetched data. Code:Component:
import React,{useEffect, useState} from 'react'
import axios from 'axios'
const AnimalDetail = ({match}) => {
const [Animal ,setAnimal ] = useState({})
const Id = parseInt(match.params.id)
const [imageState, setImageState] = useState ("");
useEffect(()=>{
const fetchAnimal = async () => {
const {data} = await axios.get(`/api/animals/${Id}`)
setAnimal(data)
}
fetchAnimal()
// setImageState(Animal.image[0]) // need to access first index of image object
},[])
useEffect(()=>{
setImageState(Object.values(Animal.image)[0]) // error cant convert undefined to object
}
return (
<>
<h2>imageState </h2> //undefined
<h2>{typeof(Animal.image)}</h2> //gives object
</>
)
}
export default AnimalDetail
Backend Api :
{"id":2,
"image":["/image.jpg","/image2.jpg"],
"price":60000,
"breed":"",
"isAvailable":true,
"weight":110,
}
How can i fetch the data and update the component level state periodically(after fetching)?
You can try following, maybe this can help you. Removed the second useEffect and updated the image state in the first useEffect.
And also I can see, you have declared const [imageState, setImageState] = useState (""); twice. You can remove the second one.
Also, make sure you handle the API error in useEffect otherwise this may break the application on API failure.
import React, { useEffect, useState } from 'react';
import axios from 'axios';
const AnimalDetail = ({ match }) => {
const [Animal, setAnimal] = useState({});
const Id = parseInt(match.params.id);
const [imageState, setImageState] = useState('');
useEffect(() => {
const fetchAnimal = async () => {
const { data } = await axios.get(`/api/animals/${Id}`);
setAnimal(data);
setImageState(data.image[0]);
};
if (Id) {
fetchAnimal();
}
}, [Id]);
return (
<>
<h2>imageState </h2> //undefined
<h2>{typeof Animal.image}</h2> //gives object
</>
);
};
export default AnimalDetail;
your code has some error in the second useEffect.
you can use this one :
useEffect(() => {
if (Animal) setImageState(Object.values(Animal.image)[0]); // error cant convert undefined to object
}, [Animal]);
this is because the Animal should have value first.
and you are defining imageState two times in your code! the first one is enough.

How to load dynamically data from localStorage in React JS?

I'm trying to display data stored in localstorage dynamically, meaning when data is added to localstorage it should be also displayed on the same page at the same time. I have the following code:
const [peopleInfo, getPeopleInfo] = useState(
JSON.parse(localStorage.getItem("peopleInfo"))
);
const [objects, getObjectsList] = useState(
JSON.parse(localStorage.getItem("objects"))
);
const setPeopleInfo = async () => {
const people = await JSON.parse(localStorage.getItem("peopleInfo"));
getPeopleInfo(people);
};
const getObjects = async () => {
const object = await JSON.parse(localStorage.getItem("objects"));
getObjectsList(object);
};
useEffect(() => {
let isSubscribed = true;
if (isSubscribed) {
setPeopleInfo();
getObjects();
}
return () => (isSubscribed = false);
});
When useEffect doesn't include getObject() like this:
useEffect(() => {
let isSubscribed = true;
if (isSubscribed) {
setPeopleInfo();
}
return () => (isSubscribed = false);
});
code works fine, peopleInfo gets updated every time when localStorage changes, but when I include getObjects() and other similar functions to get all the necessary data, app crashes without showing errors. I don't know the reason why. When I use useEffect with empty [], data doesn't get updated on localStorage change unless I refresh the page (this is not what I want). Can anyone please suggest what to do?
According to your code useEffect call, every time when your state changes, if you are using [] as your dependency useEffect call only one time but I realize when you call getObjects() function in useEffect() it changes the state every time so your state changes infinity times
import React,{useState,useEffect} from "react";
import "./style.css";
export default function App() {
const [objects, getObjectsList] = useState([{ name: "object1" }]);
useEffect(() => {
getObjectsList([{ name: "object1" }])
console.log("loop executed");
});
return <div />;
}
so I just change some code
import React, { useState, useEffect} from "react";
import "./style.css";
export default function App() {
const [objects, getObjectsList] = useState([{ name: "object1" }]);
useEffect(() => {
setInterval(() => {
const localStorageValue = [{ name: "object2" }]; //JSON.parse(localStorage.getItem("objects"));
if (JSON.stringify(localStorageValue) !== JSON.stringify(objects)) {
getObjectsList(localStorageValue);
}
}, [1000]);
}, []);
return <div />;
}
in this code, setInterval check every time if your local storage changes it update your state

Categories

Resources