cannot update during an existing state transition react native - reactjs

in my app, I am able to get the values, distance and names of location from an array by using the fuctions below. However, I am unable to dispatch the values obtained from them in my redux store using the mapDispatchToProps,
which is for example
handleNavigation() {
this.props.navigation.navigate('LocationLists');
this.props.totalDistanceChange(this.totalDistance()).
}
<Button
onPress={this.handleNavigation.bind(this)}
/>
const mapDispatchToProps = (dispatch) => ({
totalDistanceChange: totalDistance=> {
dispatch(totalDistanceChange(totalDistance));
}
});
I keep getting cannot update during an existing state transition.
below are just my functions as I wanted to keep it as simple as possible, kindly correct where appropriate.
totalDistance = () => {
const { selectedItemObjects } = this.state;
const total = selectedItemObjects.reduce((result, { Distance }) => result += Distance, 0);
return total.toFixed(1);
}
totalValue = () => {
const { selectedItemObjects } = this.state;
const total = selectedItemObjects.reduce((result, { Value }) => result += Value, 0);
return total;
}
renderLocationText = () => {
const { selectedItemObjects } = this.state;
return selectedItemObjects.length ?
`${selectedItemObjects.map((item, i) => {
let label = `${item.name}, `;
if (i === selectedItemObjects.length - 2) label = `${item.name} and `;
if (i === selectedItemObjects.length - 1) label = `${item.name}.`;
return label;
}).join('')}`
:
null;
}
my question is how can i pass the values obtained to my redux store

fixed it by converting it to a string
handleNavigation() {
this.props.navigation.navigate('LocationLists');
this.props.totalDistanceChange(this.totalDistance().toString()).
}

Related

getting localStorage data so that I can pass it to apollo query variables but skip query if the variable is empty

I save product ids in a localstorage to be used in recently viewed component.
My Recently viewed component is below
import { useQuery } from "#apollo/client";
import { getRecentlyViewedProductArr } from "#gb-utils/product/product"
import { RECENTLY_VIEWED } from "#gb-utils/queries/product";
import { useEffect, useState } from "react";
export default function RecentlyViewed() {
const [recentIds, setRecentIds] = useState([])
const { loading, error, data } = useQuery(RECENTLY_VIEWED, {
variables: { ids: recentIds }
})
useEffect(() => {
setRecentIds(getRecentlyViewedProductArr())
}, []);
if (loading) {
return 'Loading';
}
if (error) {
return error.message
}
return (
<div>{JSON.stringify(data)}</div>
)
}
My question is about how I use get the product from wp-graphql using userQuery with the local storage.
Currently I am defining a state to store product ids and on mount of the RecentlyViewed component I update the recentIds by getting the data from localstorage. Is there a better way of doing the code above because I feel like it fetching data from wp without or empty recentIds is a problem.
helper.js
export const addRecentlyViewedProduct = (productId) => {
let recent = getRecentlyViewedProduct()
if (isEmpty(recent)) {
// If recently viewed is empty then we just save the product id
saveRecentlyViewedProduct(productId)
return getRecentlyViewedProduct()
}
// if not empty then we check if product id exist and remove
if (recent.includes(productId)) {
recent = recent.replace(productId, '')
}
// Since we remove the product then we Add the product id again
recent = "".concat(productId, '|', recent)
recent = recent.replace('||', '|');
recent = recent.startsWith('|') ? recent.substring(1) : recent
recent = recent.endsWith('|') ? recent.substring(0, recent.length - 1) : recent;
const limit = 5;
let recentProductIds = recent.split('|')
if (recentProductIds.length > limit) {
recentProductIds = recentProductIds.slice(0, limit)
}
saveRecentlyViewedProduct(recentProductIds.join('|'))
return getRecentlyViewedProduct()
}
export const saveRecentlyViewedProduct = (value) => {
return localStorage.setItem('woo-next-recent-product', value);
}
export const getRecentlyViewedProduct = () => {
const recentProductsStr = localStorage.getItem('woo-next-recent-product')
return recentProductsStr
}
export const getRecentlyViewedProductArr = () => {
const recentProductsStr = getRecentlyViewedProduct()
if (isEmpty(recentProductsStr)) {
return ''
}
let recentProductIds = recentProductsStr.split('|')
return Array.from(recentProductIds.slice(1), Number); // get the 2nd to the last element
}
You can use the skip option from the useQuery API: https://www.apollographql.com/docs/react/data/queries/#skip
const [recentIds, setRecentIds] = useState([])
const { loading, error, data } = useQuery(RECENTLY_VIEWED, {
variables: { ids: recentIds },
skip: recentIds.length === 0
})
useEffect(() => {
setRecentIds(getRecentlyViewedProductArr())
}, []);

ReactJS: Use const from another file

In weights.js I have:
const Weights = ({ data, country, listItemUser, gear }) => {
const newarr = [];
data?.map(el => {
el?.gearHiking?.map(els => {
newarr?.push(els)
})
})
const newarr2 = [];
data?.map(el => {
el?.foodHiking?.map(els => {
newarr2?.push(els)
})
})
const getcamper4x4wei = useMemo(() => {
let initialValue = 0
if (country?.label == "4x4 TOWING CAMPER") {
const gettata = data.filter(el => el.packingLocation == "4x4" ? true : false)
return gettata?.reduce(
(previousValue, currentValue) => previousValue + Number(currentValue.Weight) * Number(currentValue.Quantity)
, initialValue
)
}
}, [data]);
}
export default Weights
and then I import this file in app.js as:
import Weights from './weights'
In app.js how do I access the const of getcamper4x4wei?
Add a return statement to your Weight function:
return getcamper4x4wei;
Then, in app.js, you can access its value - but be aware that Weight now returns a state variable that can change at runtime, so you should use it in useEffect:
// in app.js
useEffect(()=>console.log("Weight changed: ", Weight), [Weight]);

My useState hook is not updating itself and when i am trying to get data using filter, its not working

When I am trying to get data from an array using filter and find, it's not getting filtered also the _ids are the same when I cross-checked the array, also useState is also not updating
1. How should I filter one element from an array, Am I doing this right?
2. useState is not working, not updating data
I am getting every data from context (c1)
sd is returning array of single object, so to get one first index I am returning sd[0]
const ReadTemplate = (props) => {
const c1 = useContext(PostsContext);
const [first, myData] = useState({});
const first_load_func = () => {
const id = props.match.params.id;
const sd = c1.data.filter((c1) => id === c1._id);
const business_props = c1.business_data.filter((c1) => id === c1._id);
const startups_props = c1.startups_data.filter((c1) => id === c1._id);
const tech_props = c1.tech_data.filter((c1) => id === c1._id);
const sports_props = c1.sports_data.filter((c1) => id === c1._id);
if (sd) {
return sd[0];
} else if (business_props) {
return business_props[0];
} else if (startups_props) {
return startups_props[0];
} else if (tech_props) {
return tech_props[0];
} else if (sports_props) {
return sports_props[0];
} else {
return <MyAlert />;
}
};
const func = (data) => {
if (data) {
setTimeout(() => {
myData(data);
}, 1000);
console.log('ye first hai');
console.log(first._id);
console.log('ye data hai');
console.log(data);
} else {
console.log('No');
}
};
useEffect(() => {
first_load_func();
func(first_load_func());
}, [first]);
return (
<>
<PostDesign props={first} />
</>
);
};
export default ReadTemplate;
My guess from your code is that you should assign the filtered data when the component is rendered, not when first changes:
useEffect(() => {
func(first_load_func());
}, []);
It may be useful to convert ids toString() before comparing them:
const sd = c1.data.filter((c1) => id.toString() === c1._id.toString());

Use SWR to fetch multiple times to populate an array

I have a component in React in need of an array of users. I'm able to fetch one single user at a time with useUwr like so:
export function Hello(id: number) {
const { data } = useSWR('/api/user/{id}', fetcher)
return <div>hello {data.name}!</div>
}
What I need now is an array of users, so basically:
var users = [];
for(var i = 0; i < 100; i++) {
const { data } = useSWR('/api/user/{i}', fetcher);
users.push(data);
}
Issue with this approach is Error: Rendered more hooks than during the previous render.
Was thinking that there must be a smarter way to fetch the data of 100 users. Thanks for any suggestion.
Try this:
function arrayFetcher(...urlArr) {
const f = (u) => fetch(u).then((r) => r.json());
return Promise.all(urlArr.map(f));
}
export function Hello(id: number) {
let urlArray = [];
for(let i = 0; i < 100; i++) {
urlArray.push(`/api/user/${i}`);
}
const { data } = useSWR(urlArray, arrayFetcher);
return (
<ul>{data && data.map(x => <li key={x.name}>x.name</li>)}</ul>
)
}
function multiFetcher(...urls) {
return Promise.all(urls.map(url => fetcher(url))
}
const { data: [data1, data2, data3] } = useSWR([url1, url2, url3], multiFetcher)
add urlArray in array
const { data } = useSWR([urlArray], arrayFetcher);

What is the proper way to avoid rerenders when selecting an array in React Redux?

Is there a way to select a derived array from an array in a Redux store without spurious renders?
My Redux store contains an array of objects.
state = {items: [{id: 1, keys...}, {id: 2, keys...}, {id: 3, keys...}, ...]}
I wrote a selector to return an array of ids.
const selectIds = (state: MyStateType) => {
const {items} = state;
let result = [];
for (let i = 0; i < items.length; i++) {
result.push(items[I].id);
}
return result;
};
I then call this selector using react-redux's useSelector hook, inside a component to render out a list of components.
const MyComponent = () => {
const ids = useSelector(selectIds);
return (
<>
{ids.map((id) => (
<IdComponent id={id} key={id} />
))}
</>
);
};
I am finding that MyComponent is being rendered every call to dispatch which breaks down performance at a higher number of array elements.
I have passed in an equality function to useSelector like so:
import {shallowEqual, useSelector } from "react-redux";
const ids = useSelector(selectIds, (a, b) => {
if (shallowEqual(a, b)) {
return true;
}
if (a.length !== b.length) {
return false;
}
for (let i = 0; i < a.length; i++) {
if (a[i].id !== b[i].id) {
return false;
}
}
return true;
});
But dispatch is called enough times that checking equality becomes expensive with a large amount of array elements.
I have tried using the reselect library as well.
const selectItems = (state: MyStateType) => {
return state.items;
};
const selectIds = createSelector(
selectItems,
(items) => {
let result = [];
for (let i = 0; i < items.length; i++) {
result.push(items[i].id);
}
return result;
}
);
However, every time I modify the properties of one array element in state.items via dispatch, this changes the dependency of selectItems which causes selectIds to recalculate.
What I want is for selectIds to only recompute when the ids of state.items are modified. Is this possible?
I think the best you can do here is to combine reselect with the use of shallowEqual:
import { shallowEqual } from "react-redux";
const selectItems = (state: MyStateType) => state.items;
const selectIds = createSelector(
selectItems,
(items) => items.map(item => item.id)
);
const MyComponent = () => {
const ids = useSelector(selectIds, shallowEqual);
return (
<>
{ids.map((id) => (
<IdComponent id={id} key={id} />
))}
</>
);
};
Notes
I'm using Array.map to extract ids: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
shallowEqual can be passed directly to useSelector
With the code above:
The array of ids will be re-created only if state.items change.
The ids variable will have a new reference only if the ids changed.
If this solution is not enough (can't afford the shallowEqual) you can take a look at https://github.com/dai-shi/react-tracked it uses a more precise system to track which part of the state is used (using Proxies: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy).
Another way of doing this is to memoize the ids array in the selector:
const { createSelector, defaultMemoize } = Reselect;
const selectItems = (state) => {
return state.items;
};
const selectIds = (() => {
//memoize the array
const memArray = defaultMemoize((...ids) => ids);
return createSelector(selectItems, (items) =>
memArray(...items.map(({ id }) => id))
);
})(); //IIFE
//test the code:
const state = {
items: [{ id: 1 }, { id: 2 }],
};
const result1 = selectIds(state);
const newState = {
...state,
items: state.items.map((item) => ({
...item,
newValue: 88,
})),
};
const result2 = selectIds(newState);
console.log('are they the same:', result1 === result2);
<script src="https://cdnjs.cloudflare.com/ajax/libs/reselect/4.0.0/reselect.min.js"></script>

Resources