Count array item every time condition is met with useEffect - javascript

Im trying to set a counter everytime this condition is met in every array.push :
interface FilterProps {
filterQuery: any
setFilterQuery: (e: any) => any
resetFilter: (e: any) => any
handleCategory: (e: any) => any
categoryList: any
getCategoryValue: any
handleOnClick: (e: any) => any
paginateOnClick: (e: any) => any
paginateIcon: any
handleToggle: (e: any) => any
checkState: any
expandFilter: boolean
printEvent: PrintEvent
}
export const EventFilter: React.FC<FilterProps> = ({
filterQuery,
setFilterQuery,
resetFilter,
handleCategory,
categoryList,
getCategoryValue,
handleOnClick,
paginateOnClick,
paginateIcon,
handleToggle,
checkState,
expandFilter,
}, printEvent: PrintEvent) => {
const [countUnlabeled, setCountUnlabeled] = React.useState(0)
const classes = useStyles()
const { box, ui } = useStores()
const { labels } = printEvent
let unlabeledEvents: any[] = []
function getUnlabeled() {
box.descEvents.forEach((printEvent: PrintEvent) => {
const isStopEvent =
(printEvent && printEvent.name === 'control_fault') ||
(printEvent.name === 'running' && printEvent.value === false) ||
(printEvent.name === 'safety_chain' && printEvent.value === false) ||
(printEvent.name === 'torch_collision' && printEvent.value === true) ||
(printEvent.name === 'motion_sup' && printEvent.value === true) ||
(printEvent.name === 'e_stop' && printEvent.value === true)
const unlabeled = printEvent.labels === null && isStopEvent
if (unlabeled) {
unlabeledEvents.push(unlabeled)
ui.setUnlabeledCount(unlabeledEvents.length)
}
})
}
useEffect(() => {
if (box.descEvents && printEvent) {
getUnlabeled()
console.log('useEffect just ran', ui.unlabeledCount, unlabeledEvents.length)
}
}, [unlabeledEvents, ui.unlabeledCount, printEvent.name])
return (
<Accordion
className={classes.eventAccordion}
TransitionProps={{ unmountOnExit: true }}
defaultExpanded={expandFilter}
>
<AccordionSummary>
<div className={classes.filterHeader}>
<div className={classes.filterText}>
<FilterListIcon />
<p>Filter by:</p>
</div>
<div className={classes.unfiltered}>
Unlabeled events:
<Chip
size="small"
label={ui.unlabeledCount}
className={classes.chipMissing}
/>
</div>
</div>
</AccordionSummary>
</Accordion>
export default EventFilter
normally it should run the functuion check everytime the event is pushed or there are changes in the array, but its not counting sychronously.
i tried adding a count to the unlabeled conditional but doesnt work and dont want to overcomplicate things here.
What is the problem here?
counter example

In React, stuff you write inside the functional component body is run on every render. That means
let unlabeledEvents: any[] = [];
useEffect(() => {
console.log(unlabeledEvents);
}, [unlabeledEvents]);
creates a fresh array every time the component is rendered, leaving the object reference (or length, or any other property) unchanged. If you want to react changes to an object, you need a way to store the old reference somewhere and then update it in a way that creates a new reference. This is exactly what the useState hook is for. Just make sure a new array is created every time in order to update that reference:
const [unlabeledEvents, setUnlabeledEvents] = useState<any[]>([]);
function getUnlabeled() {
// find unlabeled item
if (unlabeled) {
setUnlabeledEvents([...unlabeledEvents, unlabeled]);
}
}
This way your useEffect runs every time getUnlabeled adds a new entry to the unlabeledEvents array. You can also ditch countUnlabeled, since unlabeledEvents will always be up to date, you can use its length directly instead.
However, are you sure your useStores hooks works as expected? Since we don't know anything about it, it could suffer from the same problem as described above. And if it does, I'd recommend using useMemo instead, to recalculate the array every time box is changed (since you're iterating the entire array anyways):
const { box } = useStores();
const unlabeledEvents = useMemo(
box.descEvents.filter((e) => true /* your unlabeled condition here */),
[box]
)
Also check out this question for some more details on updating stateful arrays in a component.

Related

Time from the change of variable in useSelector and triggering useEffect takes too long

I am using a useSelector in redux to save a variable. For example, in this case a variable called inFocus. This variable contains an array of objects. When this variable is updated (objects are being inserted into the array), it should trigger a useEffect which should execute a certain function. However, this trigger takes a very long time (up to 6000ms), making the app looks laggy. How can I improve on this?
The code:
function setItemOcrInFocus() {
if (selections && selections.clicks) {
const clicks = selections.clicks.filter(
(c) =>
c?.selectedItem === item.id || c?.selectedItemIndex === item.index
);
if (clicks.length > 0) {
dispatch({
type: SET_STATE,
payload: {
inFocus: clicks,
},
});
}
}
}
return (
<Component onMouseEnter={() => setItemOcrInFocus()} />
)
Component.js
function Component() {
const [inFocus] = useSelector((state) => [
state.item.inFocus,
]);
useEffect(() => {
// execute a function here
}, [inFocus])
}
The function setItemOcrInFocus is triggered when a specific component is being hovered over.
Am I missing something here?

React Native, State Changes, but JSX Conditional Rendering Does not Updated UI

Hello and thank you for your time in advance!
I am struggling with a small issue that I have not encountered before, with React not rendering an UI element based on a check function. Basically, what I am trying to make, is a multiple selection filter menu, where when an option is clicked, the dot next to it changes to red.
For this purpose I append the value of each option to an array, and using array.sort, verify when it is to be added (value is pushed to FilterList) and removed (value is popped from FilterList)
The checkfilter function operates normally, and when logging the state, indeed it works as intended with the array being updated.
However, the JSX code running the checkfilter to render the extra red dot inside the bigger circle, unfortunately does not.
Additionally, when the screen is refreshed, the UI is updated normally, with every option clicked, now showing the aforementioned red dot.
Why is this happening? I have tried several hooks, JSX approaches, using imported components and more that I can't even remember, yet the UI will not update oddly.
Below you can find a snippet of the code. Please bear in mind this is a render function for a flatlist component
const checkFilter = useCallback((element) => {
return filterList?.some((el: any) => (el == element))
}, [filterList])
const removeFilter = useCallback((cat) => {
let temparr = filterList
var index = temparr?.indexOf(cat);
if (index > -1) {
temparr?.splice(index, 1);
}
setFilterList(temparr)
}, [filterList])
const addFilter = useCallback((cat) => {
let temparr = filterList;
temparr.push(cat);
setFilterList(temparr)
}, [filterList])
const renderFilter = useCallback((item) => {
return (
<Pressable
onPress={() => {
checkFilter(item?.item) ? removeFilter(item?.item) : addFilter(item?.item);
console.log(filterList)
}}
style={styles.modalOptionWrapper}
>
<Text style={styles.modalOptionTitle(checkFilter)}>{item?.item}</Text>
<View style={styles.modalOptionRowRight}>
<View style={styles.radioBtn}>
{checkFilter(item?.item) ?
<View style={styles.radioBtnBullet} />
:
null
}
</View>
</View>
</Pressable>
)
}, [filterList])
This may not be correct answer but try this. When I say simple basic codes, like this.
const ListItems = () => {
const [filterList, setFilterList] = React.useState([]); // must be array
const checkFilter = filterList?.some((el) => el === element);
const removeFilter = useCallback(
(cat) => {
// not updating new state, just useing previous state
setFilterList((prev) => [...prev].filter((el) => el !== cat));
// used spread iterator
},
[] // no need dependency
);
const addFilter = useCallback((cat) => {
// used spread indicator
setFilterList((prev) => [...prev, cat]);
}, []);
const renderFilter = useCallback((item) => {
// just checking our codes is right with basic elements
// it may be 'undefined'
return <Text>{JSON.stringify(item)}</Text>;
}, []);
return filterList.map(renderFilter);
};

Why can't I append a child using useState in React?

I am using React and found something which was not quite working, but it made sense to me totally:
const [Res, setRes] = useState(<div></div>);
const test = (e) => {
if (e.keyCode === 13) {
setRes( prevState => prevState.append(<p>{e.target.value)}</p>))
}
}
return(
{Res}
)
If this us wrong, please tell me the correct way to solve similar problems please.
Keeping JSX in state is an antipattern.
Instead of keeping the JSX in an array in state, keep the data in the array without JSX, then build the JSX when you are ready to render:
const YourComponent = () => {
const [res, setRes] = useState([]);
const test = (e) => {
const {value} = e.target;
if (e.code === "Enter") {
setRes(prev => [...prev, value]);
}
};
return (
<div>
{res.map(e => <p>{e}</p>)}
</div>
);
};
<p> is using index as key, which is a problem. How to fix it is application-specific depending on where your data is coming from and whether it can be removed from the array, whether it's unique, etc, but ideally generate an id.
Also, e.target.value shouldn't be accessed in a state setter callback. It's async so the event object might have gone stale by the time it's read. Pull out the primitive value into the handler closure.
I suggest picking better names: test and res are pretty meaningless.
Finally, instead of e.keyCode === 13, use e.code === "Enter".

How to get correct value with useState() in React?

I need to draw a filled star when item is in favourite. But now I have always blank star in the beginning (even if checkFav returns true).
Example: itemId is 5
checkFav returns true in its calling in isFav useState
in jsx element isFav false
Result: not filled star, but on every click it changes correctly (so its adding and deleting to favourite list)
function CardPage() {
const { itemId } = useParams();
const [beer, setBeer] = useState([])
useEffect(() => {
const fetchData = async () => {
const result = await axios(`https://api.punkapi.com/v2/beers/${itemId}`)
setBeer(result.data[0])
}
fetchData();
}, [itemId])
const checkFav = () => {
const icons = store.getState().favourite
for (let i = 0; i < icons.length; i++) {
if (icons[i].id === itemId) {
return true //thats returning true
}
}
return false
}
const [isFav, setIsFav] = useState(checkFav())
const toggleFav = () =>{
if (isFav === false) {
setIsFav(true)
} else {
setIsFav(false)
}
}
return (
<div className="cardPage">
<img src={beer.image_url} alt={beer.name} className="cardPage__image" />
<div className="cardPage__content">
<div className="cardPage__favourite" onClick={toggleFav}>
{isFav ? <i className={`pi pi-star-fill cardPage__star`} /> : //thats drawing like false
<i className={`pi pi-star cardPage__star`} />}
</div>
</div>
</div>
)
}
export default CardPage
useState in ReactJS is asynchronous, thus it takes certain milli seconds to update before which the component would have already been rendered.
One approach I found working was to directly use the value returned from the methods rather than setState.
<div className="cardPage__favourite" onClick={toggleFav}>
{checkFav() ? <i className={`pi pi-star-fill cardPage__star`} /> :
<i className={`pi pi-star cardPage__star`} />}
</div>
Also once you set the isFav using setIsFav, you can use its value in other methods using the state variable isFav.
if (icons[i].id.toString() === itemId) {
Converting the id to toString() will help you solve your query since you're also checking for type using ===.

Delay update of calculated value until some required state has been set

I am looking for some help in delaying the display of some calculated data until the state that it relies on is set in a parent component. In the below-simplified example, App has a value set to 0, then some calculations are run and the state is updated.
The problem is that when Calculate is clicked, the calculation which updates the state of the initial val hasn't yet happened, and the final value derived from calcOverUnderPayment is therefore incorrect.
It only displays the correct value on all subsequent clicks.
What could I do to fix this?
Thanks so much in advance.
function App() {
const [val, setVal] = useState(0)
const calculate = () => {
// code to run some calculations which I then set as state
setVal(100)
}
return (
<>
<button onClick={() => calculate()}>Calculate</button>
<Outcome
val={val}
/>
</>
)
}
function Outcome(val) {
const calcOverUnderPayment = (value: number, type: string) => {
if (type === 'under') {
return (value > 0) ? value : 0
} else {
return (value < 0) ? value : 0
}
}
return (
<>
Final Val is {calcOverUnderPayment(val)}
</>
)
}
I have gone through comments of yours on the other answer. You can use another state variable and useEffect hook in your Outcome component.
function App() {
// If it is possible, try to change the initial value to null so that the Outcome component can figure out when did the value change.
const [val, setVal] = useState(null)
const calculate = () => {
// code to run some calculations which I then set as state
setVal(100)
}
return (
<>
<button onClick={() => calculate()}>Calculate</button>
<Outcome
val={val}
/>
</>
)
}
function Outcome({ val }) {
// Use this state to display initial value and update this state instead of calling the function directly in the return
const [valueToDisplay, setValueToDisplay] = useState('YOUR_INITIAL_VALUE_TO_DISPLAY');
const calcOverUnderPayment = (value: number, type: string) => {
if (type === 'under') {
return (value > 0) ? value : 0
} else {
return (value < 0) ? value : 0
}
}
// use useEffect hook to run your function whenever val prop is changed
useEffect(() => {
// If the val prop is null, then it is evident that it is rendering for the first time.
// So, don't run the calcOverUnderPayment() method. This is why "null" is important to provide as your initial state for "val" in App component
if(val !== null) {
const value = calcOverUnderPayment();
// set the state variable here to display
setValueToDisplay(value);
}
},[val]) // Give the val prop as the dependency. This tells useEffect to run whenever val prop is changed.
return (
<>
{/* Instead of calling your function here, use the state variable*/}
Final Val is {valueToDisplay}
</>
)
}
This should work as you intended. Let me know if you encounter any problem or didn't understand it.
As one of the options you could just use conditional rendering, and render your child component, only when state isn't default, in your case something like this would do:
return( //your components here
{val != 0 && <Outcome val={val} />}
//rest of your component
)

Categories

Resources