so, I have this function here
const [shopItems,setShopItems] = useState("");_
useEffect(()=> {
commerce.products.list().then((product) => {
setShopItems(product)
});
}, [])
function categorize(category) {
let categoryarray = []
if (shopItems!= "s") {
shopItems.data.forEach((el)=> {
for (let i =0; i < el.categories.length; i++) {
if (el.categories[i].slug == category) categoryarray.push(el)
}
})
}
return categoryarray;
}
the useEffect Hook was just for context, I'm mostly concerned about the categorize function. Is there anyway to optimize this since I have observed that my website scrolls very slowly, and I think this might be one of the culprits behind the slow scrolling speeds. Thanks in Advance!
The only way I can see to optimise that code is to exit as soon as a match is found. (I prefer using a while loop for this purpose).
shopItems.data.forEach(el => {
let idx = 0;
while (idx < el.categories.length) {
if (el.categories[idx].slug == category) {
categoryarray.push(el);
break;
} else {
idx++;
}
}
});
If you wanted something that looked slightly better (not mixing forEach and for, for example) you could use this: (no performance enhancements as far as I can see though)
shopItems.data.forEach(el => {
if (el.categories.map(({ slug }) => slug).includes(category)) categoryarray.push(el);
});
Or even use reduce:
let categoryarray = shopItems.data.reduce((a, c) => {
if (c.categories.map(({ slug }) => slug).includes(category) return a.concat(c);
else return a;
}, []);
The first option is still the most performant as realistically it will run through the loop less times.
You can use useMemo
https://reactjs.org/docs/hooks-reference.html#usememo
useMemo works by having 2 parameters. A function and an array of dependencies, if any of the dependencies change it re-runs the provided function and stores the value, next render if the dependencies havent changed it just uses the previous value.
const categories = useMemo(() =>
let categoryarray = []
if (shopItems!= "s") {
shopItems.data.forEach((el)=> {
for (let i =0; i < el.categories.length; i++) {
if (el.categories[i].slug == category) categoryarray.push(el)
}
})
}
return categoryarray;
}, [shopItems.data, category])
Related
I have a dynamic layout within a React Native view - that changes based on provided data for the particular data set. Ie there could be a provided Guide, Inventory, or Observation list that need displaying - but there may just be a combination of one, two or 3 of these things provided within any dataset.
I need to supply conditional styles / display object throughout the view based on the state of each provided data type.
Currently I have a series of checks that return true if the state is populated i.e:
const hasSiteGuide = () => {
if (!this.isEmpty(this.state.locationSiteGuidePdf)) {
return true;
}
};
I then have a series of functions that determine how many items are supplied and provide the relevant style - here are the logic checks:
const layout_full = () => {
if (hasSiteGuide() && hasInventoryItems() && hasObservations()) {
return true;
}
};
const layout_one = () => {
if (!hasSiteGuide() && !hasInventoryItems() && !hasObservations()) {
return true;
}
};
const layout_two = () => {
if (!hasSiteGuide() || !hasInventoryItems() !hasObservations()) {
if (hasSiteGuide() || hasInventoryItems()) {
return true;
}
}
};
the checks are used to display conditional styles in multiple areas - for example:
<View
style={[
layout_full() && RP_Styles.btnWrap,
layout_three() && RP_Styles.btnWrap_three,
layout_two() && RP_Styles.btnWrap_two,
layout_one() && RP_Styles.btnWrap_one,
]}>
My question is - i'm sure that there is a better way to do this.. particularly as i'm about to implement a 4th possibility - I was wondering if there is a way that I could use just one function to count how many true statements are in a list and return the relevant condition
ie:
returnTrueCount(hasSiteGuide(), hasInventoryItems(), hasObservations()). - is this possible? Or has anybody got an idea for a better approach please? Thanks very much
If you want do something like this : returnTrueCount(hasSiteGuide(), hasInventoryItems(), hasObservations()) then I think the following solution would would work.
const a = () => true;
const b = () => true;
const c = () => false;
const d = () => true;
function returnTrueCount(...funcs) {
return funcs.reduce((a, f) => a + +f(), 0);
}
console.log(returnTrueCount(a, b, c, d));
This worked for me:
const returnTrueCount = (array, value) => {
return array.filter((v) => v === value).length;
};
called with a basic array and the value to check.
Is there a way where I can use for loops and if statements without breaking the hook rule? To elaborate, I am currently trying to compare two lists (allData and currentSelection) and if there are similarities, I will add them to another list (favData). However, I am constantly either having visibility issues or errors. If I can get some help, I would much appreciate it!
const [favData, setFavData] = useState([]);
useEffect(() => {
getFilterFavMeal();
}, []);
function getFilterFavMeal() {
allData.forEach((mealList) => {
currentSelection.forEach((mealList2) => {
if (mealList["menu_item"]["menu_item_id"] === mealList2.value) {
// with push, I have visibility issues
// favData.push(mealList);
setFavData(mealList);
}
});
});
setFavData(favData);
}
The set function that useState returns updates the state and schedules a re-render of the component so that the UI can update. It doesn't make sense to call the set function many times in one render.
You also don't want to mutate React state by using functions like push.
Since it looks like favData is deterministic, you can simply remove it from the component state and calculate it in the render loop.
const favData = allData.filter(a => currentSelection.some(c => c.value === a.menu_item.menu_item_id));
Answering your original question, of course you can use loops. As long as you don't mutate the existing state object. And don't set the state more than once per render.
const FF = () => {
const [list, setList] = useState([]);
const addStuffToList = () => {
const tail = Array.from(new Array(3)).map((_e, i) => i);
// Build a new array object and use that when setting state
setList([...list, ...tail]);
}
const forLoop = () => {
const tail = [];
for (let i = 0; i < 4; i++) {
tail.push(i);
}
// Same thing
setList([...list, ...tail]);
}
return ...
};
I have a simple component that allows me to select an item from a list, then remove an item from a list. I display the active list within a parent component. No matter what I do or how I approach it, the removal of an active component is never updated unless they are all in active.
Here is a smaller (yet large snippet) of how it is setup. Below it I describe where I found to be the problem:
const Viewer = () => {
const [items, setItems] = useState(["inactive"]);
return (
<ItemSelect setItems={setItems} selected={items}/>
<DisplayItems items={items}/>
)
}
const ItemSelect = ({setItems, selected}) => {
const handleActiveItems = (activeItems) => {
setItems(activeItems);
}
return (
<SelectItems
handleActiveItems={handleActiveItems}
items={selected}
/>
)
}
const SelectItems = ({handleActiveItems, items}) => {
const [selected, setSelected] = useState([])
useEffect(() => {
setSelected(items);
}, [items]);
const randomTestItem = ["apple", "peach", "orange"];
const handleOnClick = (isSelected, item) => {
let tmpItems = items;
if (isSelected) {
let index = tmpItems.indexOf("inactive");
if (index > -1) {
handleActiveItems([option]);
} else {
handleActiveItems([...selected, option]);
}
} else if (!isSelected) {
let index = tmpItems.indexOf(option);
if (index > -1) {
tmpItems.splice(index, 1);
if (tmpItems.length === 0) {
handleActiveItems(["inactive"]);
} else {
handleActiveItems([tmpItems]);
}
}
}
}
return (
{
randomTestItem?.map((item,index) => {
return (
<DisplayClickable item={item} onClick={handleOnClick} key={index}/>
)
})
}
)
}
<DisplayClickable item={item} onClick={handleOnClick}/> holds a useState() that toggle from active/inactive.
I've tested this in many different area's I believe the crux of the problem to be here:
} else if (!isSelected) {
let index = tmpItems.indexOf(option);
if (index > -1) {
tmpItems.splice(index, 1);
if (tmpItems.length === 0) {
handleActiveItems(["inactive"]);
} else {
handleActiveItems([tmpItems]);
}
}
}
specifically:
} else {
handleActiveItems([tmpItems]);
}
When I unselect all the items and switch the array back to "inactive", everything updates instantly and exactly how you would expect. Selecting items always adds to the list correctly, it's removing them that everything goes wonky. I've done a console.log right before calling handleActiveItems() and the tmpItems array is always correct to what it should be. It just never updates the set state.
Within handleActiveItems the log also shows it is receiving the array just before setting it. It just never sets it.
I believe since you are using the splice method, you just modify the existing array and React does not recognize it as "updatable". You can try to use the filter method:
if (index > -1) {
const newArray = tmpItems.filter((_, itemIndex)=> itemIndex !== index)
if (newArray.length === 0) {
handleActiveItems(["inactive"]);
} else {
handleActiveItems(newArray);
}
}
With the code above, filter method will generate a new array.
Give it a try, hopefully it will help =)
update
I've just realized, maybe you don't need the extra [] you are putting into handleActiveItems(). So instead of:
handleActiveItems([tmpItems])
It could be just:
handleActiveItems(tmpItems)
I figured it out.
It all came down to this line:
let tmpItems = items;
Changing to this:
let tmpItems = [...items];
for some reason allowed React to pay more attention and notice that there was in fact a change.
I just changed in my development build and it works without a hiccup.
This is the code, it goes over an object looking for deeply-nested children and supposed to stop once there are no more children or it exceeds ALLOWED_NESTING_DEPTH.
The console.log seems to run only once, although I do get the correct number.
const ALLOWED_NESTING_DEPTH = 4
function traverseChildren (childrenIdList, parentsNesting = 0) {
parentsNesting++
if (parentsNesting > ALLOWED_NESTING_DEPTH || _.isEmpty(childrenIdList)) {
console.log(parentsNesting) // Why does this show only once even if there are many levels of nesting?
return parentsNesting
}
let children = childrenIdList.map(child => _.find(tree.items, { id: child }))
let allChildren = []
if (!_.isEmpty(children)) {
allChildren = _.flattenDeep(children.map(child => child.children))
}
return traverseChildren(allChildren, parentsNesting)
}
traverseChildren(someChild)
When you enter this block:
if (parentsNesting > ALLOWED_NESTING_DEPTH || _.isEmpty(childrenIdList)) {
console.log(parentsNesting) // Why does this show only once even if there are many levels of nesting?
return parentsNesting
}
... you are returning an object and not calling the function again. In other words, this is your terminating case. You only come through here once, so you only see one console.log.
My idea is to build an application that generates sudoku's and allows users to fill them in. I'm using Node and Mongo to do so. For generating the sudoku, I have imported my 'sudoku.js' into a route function. The sudoku generation in 'sudoku.js' runs fine only when I run it by itself, but not in the route.
In short, the sudoku generator picks a random number, checks whether it has already been used in the row/column/block, and if not, adds it to the array. If it has been used, the function is re-ran, until it does 'discover' a correct sudoku. The function should return an array consisting of nine arrays, each with 9 numbers.
It appears to go south when the function genSud() is called within itself. The function, as it is now, returns 'undefined'. When I comment out the function call within the function, it does return an array, but those are almost always unfinished sudoku's. If I leave out the return statement, which I think the issue is related to, it will just keep on rerunning the function until it hits the stack limit.
const createSudoku = {
getRandomNumber: function(array) {
return array[Math.floor(Math.random() * array.length)];
},
checkIfIn: function(array, blockNumber, blockAvailable, columnNumber, columnAvailable) {
let availableNumbers = [];
array.forEach(function(element) {
if (blockAvailable[blockNumber].includes(element) && columnAvailable[columnNumber].includes(element)) {
availableNumbers.push(element);
}
});
if (availableNumbers.length === 0) {
return false;
};
return availableNumbers;
},
genSud: function(callback) {
let availableNumbers = [];
let goodLines;
let blockAvailable = [ [1,2,3,4,5,6,7,8,9], [1,2,3,4,5,6,7,8,9], etc, etc ]
let columnAvailable = [ [1,2,3,4,5,6,7,8,9], etc, etc ]
let rowAvailable = [ [1,2,3,4,5,6,7,8,9], etc, etc ]
let blockNumber;
let randomNumber;
for (var i = 0; i < 9; i++) {
for (var j = 0; j < 9; j++) {
blockNumber = Math.floor(j / 3) + 3 * Math.floor(i / 3);
availableNumbers = this.checkIfIn(rowAvailable[i], blockNumber, blockAvailable, j, columnAvailable);
if (availableNumbers == false) {
this.genSud(callback);
return;
}
randomNumber = this.getRandomNumber(availableNumbers);
rowAvailable[i].splice(rowAvailable[i].indexOf(randomNumber), 1);
columnAvailable[j].splice(columnAvailable[j].indexOf(randomNumber), 1);
blockAvailable[blockNumber].splice(blockAvailable[blockNumber].indexOf(randomNumber), 1);
body[i].push(randomNumber);
}
}
callback(body);
}
}
// createSudoku.genSud();
module.exports = createSudoku;
Then, in my route:
var sudoku = require('../sudoku.js');
var completeSudoku = sudoku.genSud(function(result) {
return result;
});
I'm aware I could abandon the rerunning altogether by replacing numbers etc., but for now it's fast enough by itself. Also, I know I could store a bunch of sudoku's in a database and retrieve them, but I like the idea of generating them on the spot.
Thanks in advance!
Edit: I have created a CodePen here:
https://codepen.io/anon/pen/OjmGMy?editors=0000
You can run it in the console using:
createSudoku.genSud();
I have made it work by removing the callback and using #nstraub 's suggested edit. Changed:
if (availableNumbers == false) {
this.genSud();
return;
}
to
if (availableNumbers == false) {
return this.genSud();
}
I'm not sure why this works, but it does solve the issue.