useMemo on useState with Array and many Object - reactjs

My apps run although its kind of slow since I loop to create button and loop again to change the color of the button
Its slow because every time I change a color in a button I have to loop in useState array of objects
My question is, how do I only render one object in an Array that uses useState ?
Or.. is there any faster way / better way than looping like this ?
I also try to target the checked status with booleanMonth[0].checked=true which change the status without looping but it doesn't change the color.
this is the array:
const [booleanMonth,setMonth]=useState([
{key:0,value:'January',title:'Jan',color:'black',checked:false},
{key:1,value:'February',title:'Feb',color:'black',checked:false},
{key:2,value:'March',title:'Mar',color:'black',checked:false},
{key:3,value:'April',title:'Apr',color:'black',checked:false},
{key:4,value:'May',title:'May',color:'black',checked:false},
{key:5,value:'June',title:'Jun',color:'black',checked:false},
{key:6,value:'July',title:'Jul',color:'black',checked:false},
{key:7,value:'August',title:'Aug',color:'black',checked:false},
{key:8,value:'September',title:'Sep',color:'black',checked:false},
{key:9,value:'October',title:'Oct',color:'black',checked:false},
{key:10,value:'November',title:'Nov',color:'black',checked:false},
{key:11,value:'December',title:'Dec',color:'black',checked:false}
])
this is where I create buttons which will loop if one of the object in array ABOVE changed because I pressed a button :
const createButtonMonth = useMemo(() =>{
console.log('createButtonMonth')
return (<View style={styles.containerForButtons2}>
{
booleanMonth.map((item,key) =>
<View key={item.key} style={styles.buttonFilter3}>
<Button style={styles.buttonFilter3}
title={item.title}
value={item.checked}
onCheckColor='red'
color={item.checked==true ? 'green':'black'}
onPress={()=>onPressMonthFilter(booleanMonth[item.key].key,booleanMonth[item.key].checked)}
/></View>)
}
</View>)
},[booleanMonth])
this is the press button function which also loop to determine which button to change its color because its checked status true / false (I think this slows me down also because of the loop)
const onPressMonthFilter = (keyMonth,statusMonth) =>{
let newArr = [...booleanMonth]
if(newArr[keyMonth].checked==false){
newArr[keyMonth].checked=true
}else{
newArr[keyMonth].checked=false
}
setMonth(newArr)
}
open for any suggestion :< help newbie here please

The only thing I can think of to further enhance this is to wrap onPressMonthFilter with useCallback
const onPressMonthFilter = useCallback((keyMonth,statusMonth) =>{
let newArr = [...booleanMonth]
if(newArr[keyMonth].checked==false){
newArr[keyMonth].checked=true
}else{
newArr[keyMonth].checked=false
}
setMonth(newArr)
}, [setMonth, booleanMonth])
So the function will return a memoized value. Other than that the code seems to be pretty straight forward, and you don't really have any choice but to loop them to assign the UI.
looping on the array that small shouldn't slow down the app tho, unless you have thousands of data on the array.

I suggest keeping the static data static and only the application state in the state. From a record perspective I know it makes sense to have all the attributes for an entity together but it but in this case I think you can just manage the state of the buttons and infer everything else. For instance, you can infer the color of the button if you know whether it is checked or not.
You can have a list of months as an static array to generate your UI. Then keep array state to keep track of buttons that are checked. If the value in the button is present in the state then button should be checked. If the button is checked then the color should be green. The action of the button will simply add its value to the state array if missing or remove it if exists.
Please review this example showcasing the suggestions
https://codesandbox.io/s/elated-bose-0vwtw

Related

Performance issues if MapComponent state is updated

I am not sure if this is an issue of react-leaflet-markercluster, react-leaflet, leaflet, react, or my code.
I have a map with several thousand markers and I am using react-leaflet-markercluster for marker clustering. If I need to update a global state of MapComponent, there is 1-3 seconds delay when this change is reflected.
I created a codesandox with 5000 markers and you can see there 2 use cases with performance issues:
1.) MapComponent is inside react-reflex element, that allows resizing panel and propagates new dimensions (width, height) to MapComponent. If width and height are changed, mapRef.invalidateSize() is called to update map dimensions. Resizing is extremely slow.
2.) If user clicks on Marker, global state selected is updated. It is a list of clicked marker ids. Map calls fitBounds method to focus on clicked marker and also marker icon is changed. There is around 1 second delay.
In my project, if I need to change a MapComponent state, it takes 2-3 seconds in dev mode when changes are reflected and it is just a single rerender of MapComponent and its elements (markers).
I took a look at Chrome performance profile and it seems like most time is spent in internal React methods.
It is possible to fix this by preventing rerendering using memo, which is similar to shouldComponentUpdate, but it makes whole code base too complicated. preferCanvas option doesn't change anything. I am wondering what is a good way to fix these issues.
The main problem I identified in your code is that you re-render the whole set of marker components. If you memoize the generation of those, you achieve a good performance boost; instead of running the .map in JSX, you can store all the components in a const; this way, the .map won't run on every render.
from this
...
<MarkerClusterGroup>
{markers.map((marker, i) => {
...
to something like this
const markerComponents = React.useMemo(() => {
return markers.map((marker) => {
return (
<MarkerContainer .../>
);
});
}, [markers, onMarkerClick]);
return (
<>
<MarkerClusterGroup>{markerComponents}</MarkerClusterGroup>
</>
);
The second refactor I tried is changing the way you select a marker. Instead of determining the selected prop from the selected array for each marker, I put a selected field on every marker object and update it when selecting a marker. Also, I add the position to the onClickHandler args to avoid looking for that in the markers array.
There are some other tweaks I don't explain here so please check my codesandbox version.
https://codesandbox.io/s/dreamy-andras-tfl67?file=/src/App.js

How do I use React refs in a map for functional components?

I have a map function which renders a list of divs and these divs are based on the components state, so it is dynamic. I need to put a ref on each of these items on the list so that the user can basically press up and press down and traverse the list starting from the top.
Something like this:
const func = () => {
const item = items.map((i, index) => {
return (
<div ref={index}>{i.name}</div>
)
});
}
This has been difficult since it seems like refs are not like state where they can change based on dynamic data. Its initialized once and set to a constant. But I don't know what to initialize it as until this list is rendered and I can't call the useRef hook in a function of course so what is the solution here?
EDIT:
Ok so, its seems there needs to be some more context around this question:
I have a dropdown which also allows the user to search to filter out the list of items. I want to make an enhancement to it so that when the user searches it focuses on the first element and allows the use of arrow keys to traverse the list of elements.
I got stuck when I tried to focus on the first element because the first element doesn't exist until the list is rendered. This list changes dynamically based on what the user types in. So how do you focus on the first element of the list if the list is always changing as the user types?
This can be done without refs, so it should probably be done without refs.
You can put the index of the currently focused element into state. Whenever the displayed data changes, reset the index to 0:
const [focusedElementIndex, setFocusedElementIndex] = useState(0);
useEffect(() => {
setFocusedElementIndex(0);
}, [items]);
// using this approach,
// make sure items only gets a new reference when the length changes
When rendering, if you need to know the focused index to change element style, for example, just compare the index against the state value:
<div class={index === focusedElementIndex ? 'focused' : ''}
And have a keydown listener that increments or decrements focusedElementIndex when the appropriate key is pressed.

Is it possible to save components state when they are stored in an array manipulated?

I'm trying to create a stepper form
I store my steps in an array of json with a proprety component ({typeOfComponent, component, key})
It works wells, but:
Everytime i slice my array, like when i move up/down a step or add a new step between two steps.
I lose the states inside my component.
I tried to use memo, i don't understand why it's only when an item position my composent is recreate. Is it possible like a pointer in C to store only his "adress"
the code sandbox exemple =>
https://codesandbox.io/s/infallible-maxwell-zkwbm?file=/src/App.js
In my real projet, the button ADD is a button for chosing the new step type
Is there any solution for manipulates my steps without losing the user data inside ?
Thanks for your help
React is re-mounting the components inside of this every re-render probably due to a variety of reasons. I couldn't get it to work as is, but by lifting the state up from your components, it will work.
You'd likely need to lift the state up anyway because the data isn't where you need it to be to make any use of your form when the user is done with it.
In order to lift the state up, I added the current value to the steps array:
function addNext(step, index) {
componentKey++;
setSteps(prevState => {
let newState = [...prevState];
step = 1;
newState.splice(index + 1, 0, {
stepNumber: step,
component: getStepContent(step, componentKey),
value: getDefaultValue(step),
key: componentKey
});
return newState;
});
}
I also made sure your getStepContent just returned the component rather than a node so you can render it like this:
<step.component
value={step.value}
onChange={handleChange}
data-index={i}
/>
There are definitely a lot of ways to optimize this if you start running into performance issues, of course.
https://codesandbox.io/s/beautiful-river-2jltr?file=/src/App.js

Store checkbox values as array in React

I have created the following demo to help me describe my question: https://codesandbox.io/s/dazzling-https-6ztj2
I have a form where I submit information and store it in a database. On another page, I retrieve this data, and set the checked property for the checkbox accordingly. This part works, in the demo this is represented by the dataFromAPI variable.
Now, the problem is that when I'd like to update the checkboxes, I get all sorts of errors and I don't know how to solve this. The ultimate goal is that I modify the form (either uncheck a checked box or vice versa) and send it off to the database - essentially this is an UPDATE operation, but again that's not visible in the demo.
Any suggestions?
Also note that I have simplified the demo, in the real app I'm working on I have multiple form elements and multiple values in the state.
I recommend you to work with an array of all the id's or whatever you want it to be your list's keys and "map" on the array like here https://reactjs.org/docs/lists-and-keys.html.
It also helps you to control each checkbox element as an item.
Neither your add or delete will work as it is.
Array.push returns the length of the new array, not the new array.
Array.splice returns a new array of the deleted items. And it mutates the original which you shouldn't do. We'll use filter instead.
Change your state setter to this:
// Since we are using the updater form of setState now, we need to persist the event.
e.persist();
setQuestion(prev => ({
...prev,
[e.target.name]: prev.topics.includes(e.target.value)
// Return false to remove the part of the array we don't want anymore
? prev.topics.filter((value) => value != e.target.value)
// Create a new array instead of mutating state
: [...prev.topics, e.target.value]
}));
As regard your example in the codesandbox you can get the expected result using the following snippet
//the idea here is if it exists then remove it otherwise add it to the array.
const handleChange = e => {
let x = data.topics.includes(e.target.value) ? data.topics.filter(item => item !== e.target.value): [...data.topics, e.target.value]
setQuestion({topics:x})
};
So you can get the idea and implement it in your actual application.
I noticed the problem with your code was that you changed the nature of question stored in state which makes it difficult to get the attribute topics when next react re-renders Also you were directly mutating the state. its best to alway use functional array manipulating methods are free from side effects like map, filter and reduce where possible.

React stackable snackbars/toasts

I'm creating my own simple snackbar/toast stacker. However, I'm having problems with queing them in an orderly manner. Removing a snackbar from the snackbar que causes re-render and odd behavior.
The basic flow:
Click a button which causes the addSnack function to fire which is provided by the withSnackbar HOC.
Take the parameters from the fired function, and create a snack accordingly and add it to the snackbar list.
At the end, we render the snackbar list.
Each snackbar controls it's own appearance and disappearance, and is controlled by a time out. After the timeout is fired, it calls removeSnack function which is suppose to remove the first snack from the list.
codesandbox
If you click the button for example, four times in a short amount of time. They render nicely, but when the first one is to be deleted, they all disappear and reappear abnormally.
I understand that it's partially the state re-renderings fault, however, I'm not sure how to handle it in a way that the removal is handled gracefully without affecting the rendering of other snacks.
So, after many hours of trial and error, I found a solution that works so far. Moving and reading the snacks outside of the state helped with the bizarre rendering problems, and with it, I was able to create a message que which works well.
Working example
Codesandbox
If you look at splice document, you will notice that it's returning an array of deleted elements and not the initial array.
You can correct it by splicing then updating:
snacks.splice(-1, 1);
addSnacks(snacks);
However you are still going to have some weird behavior and you might need to use a keyed list to fix that.
i had the same issue and i saw your solution, but i was really trying to find out why it happens - here is why:
when u call a useState hook from an async function's callback, you should use the callback format of the hook to make sure that you are working with the latest value. example:
const [messages, setMessages] = useState([]);
const addMessage = ( message ) => {
setMessages( prevMessages => {//prevMessages will be the latest value of messages
return [ ...prevMessages, message ];
});
};
const removeMessage = ( index ) => {
setMessages( prevMessages => {//prevMessages will be the latest value of messages
let newMessages = [...prevMessages];
newMessages.splice( index, 1 );
return newMessages;
});
};

Resources