react how to prevent default on setState without event - reactjs

Let's say you have a defined function containing a this.setState in a react component which is not fired by an event.
How can you do preventDefault() in order to keep the current scroll on the page?
I create a sandbox to illustrate this behaviour:
https://codesandbox.io/embed/damp-night-r92m5?fontsize=14&hidenavigation=1&theme=dark
When groups are defined and something fire the renderer, the page scroll on top. This does not happend id groups are not defined or contain an empty array...
How can I prevent this scrolling

First of all, I think your issue was nothing to do with event but your component is triggering render.
Based on your sandbox that you provided, you are actually declaring a new array each time the component renders. Meaning that React will assume that your [{ id: 1, content: "group1" }, { id: 2, content: "group2" }] is a new instance, even though all the items in the array is the same.
This line is causing your issue :
groups={[{ id: 1, content: "group1" }, { id: 2, content: "group2" }]}
Method 1: Move your groups variables into state
const [groups, setGroups] = useState([{ id: 1, content: "group1" }, { id: 2, content: "group2" }]);
This way React will not rerender your App until you call setState ( In this case, setGroups )
Method 2: Move your groups outside of your App function
const groups = [{ id: 1, content: "group1" }, { id: 2, content: "group2" }];
function App() {
... App Codes
}
In this way, React will not rerender your App since groups is not declaring within App.
Method 3: Using Memoization useMemo React Hook
const groups = useMemo(() => [{ id: 1, content: "group1" }, { id: 2, content: "group2" }], []);
The second argument in your useMemo function defines your dependencies array, setting it to empty array means that the value will never change. Hence React will not rerender your App.
In your render:
groups={groups}

Related

useState hook not "reacting" when state variable modified

I have created a React application to test out the useState hook.
This the variable of concern:
let [blocks, setBlocks] = useState([
{text: "Hello", id: 1},
{text: "This is google", id: 2},
{text: "Wassup", id: 3},
{text: "Last One", id: 4}
]);
I've displayed this using the map function as follows:
return (
<div className="App">
{blocks.map((block) => (
<div className='block-element'>{block.text}</div>
))}
<button onClick={clickFunc}>ClickToChange</button>
</div>
);
As far as I've understood, to make any change in the webpage we have to pass the new-data into setBlocks() and wherever "blocks" was used will be updated.
I tried the following clickFunc() to do so:
const clickFunc = ()=>{
blocks[1].text = "Go Home";
setBlocks(blocks);
console.log(blocks);
}
I expected the output (onclicking the button) to be:
Hello
Go Home
Wassup
Last One
But nothing changed.
Surprisingly when I used the following (similar looking) clickFunc():
const clickFunc = ()=>{
blocks = [
{ text: "Hello", id: 1 },
{ text: "Go Home", id: 2 },
{ text: "Wassup", id: 3 },
{ text: "Last One", id: 4 }
];
setBlocks(blocks);
console.log(blocks);
}
And it worked perfectly as expected.
On click output:
Hello
Go Home
Wassup
Last One
NEVER mutate state directly.
The following code block is mutation and wrong. Javascript is kind of like some other programming languages where only the reference of the object is stored in the memory. So when you mutate the object instead of creating a new one, you are not really changing the content of the object in memory, because it still has the same value(reference) as before.
const blocks = [{text: 'one'}];
blocks[0].text = 'two';
You should create a new object and assign it to state using React setState callback:
setBlocks(blocks => {
clonedBlocks = [ ...blocks];
clonedBlocks[1]= { ...blocks[1], text: "Go Home" };
return clonedBlocks;
});
Or any other way that does not mutate the state directly.
React state is immutable
if you mutate the state react will not know that state is changed
first you need to copy the state using Spread syntax and change it
const clickFunc = () => {
setBlocks(prevBlocks => {
const shallowCopy = [...prevBlocks]
shallowCopy[1].text = "Go Home";
return shallowCopy
});
}

What is the proper way to update multiple objects in an array of objects in react?

Making a watchlist app for stocks/crypto to learn react.
I have watchlist state in this format (using useState hook):
[
{ id: "1btc", name: "bitcoin", price: "7500" },
{ id: "1eth", name: "ethereum", price: "500" },
{ id: "2xmr", name: "monero", price: "200" },
{ id: "1ltc", name: "litecoin", price: "10" },
];
every 3 seconds server sends a batch of available price updates over websocket connection.
sometimes only a handful of coins have new info so update message might look like so:
[
{ id: "2xmr", price: "225" },
{ id: "1btc", price: "8600" },
];
Is it possible to update the watchlist so that only the updated coins in the list rerender as opposed to rerendering the entire list every time an update message is received? What is the best way to handle this situation?
You could let each object have its own component and then pass the object properties in the components and then use React.memo() so that it would only cause a render if it detects a change.
By default it will only shallowly compare the objects in the props which should be enough for you. But if its more complex, then you can also write a custom function.
As per docs
function MyComponent(props) {
/* render using props */
}
function areEqual(prevProps, nextProps) {
/*
return true if passing nextProps to render would return
the same result as passing prevProps to render,
otherwise return false
*/
}
export default React.memo(MyComponent, areEqual);

StencilJS passing in array of objects from React

I have a stencil component that takes an array of objects to display. I am trying to pass that array in from my react app, and have been following this example to do that. If i log the component with the array passed in, i can see that it appears to be formed correctly.
The actual component I built to create the reference to handle the array is where my problem is, and I just can't figure out where I made the mistake, as there is an issue with the example. My component inside my react app, which returns the stencil component is as follows
const ActionBar: React.FC<{ pageActions: pageActionsObj }> = ({ pageActions }) => {
const elementRef = useRef(null);
useEffect(() => {
(elementRef.current).pageActionList = pageActions;
}, [pageActions]);
return <pw-actionbar ref={elementRef}></pw-actionbar>;
};
and I try to use it as a regular component as such
<ActionBar pageActions={pageActionsObj}/>
Where the array that im passing as the prop is
export const pageActionsObj = [
{
name: "Users",
page: "partner",
icon: "pw-i-filled-multiple-users",
onpage: false
},
{
name: "Billing",
page: "billing",
icon: "pw-i-filled-billing",
onpage: false
},
{
name: "Helpdesk",
page: "help-desk",
icon: "pw-i-filled-help",
onpage: false
},
{
name: "My Profile",
page: "profile",
icon: "pw-i-filled-user",
onpage: true
},
{
name: "Sign Out",
page: "sign-out",
icon: "pw-i-filled-sign-out",
onpage: false
},
];
I very strongly believe that the way the component is set up, paired with the way I am passing in the array as a prop is causing the issue. I have been stuck for hours. What am i missing?

React - setting state with imported data keeps getting updated

I am importing an array of data like
import { menuData } from '../../data/menu-data';
data being:
export const menuData = [
{ id: 'all', title: 'Select all', icon: '', selected: false },
{ id: 'overview', title: 'Overview', icon: 'user-check', selected: false },
{
id: 'about',
title: 'About',
icon: 'info-circle',
selected: false,
partSelected: false,
}
];
I then initialise some state in my parent component like:
const [downloadMenu, setMenuState] = useState([...menuData]);
I pass "downloadMenu" state to my child compionent and absorb it as a prop and then create a copy of it which gets mutated i.e.
const updatedMenu = [...downloadMenu];
i then call this function which is in the parent and is passed down as a prop to the child component
onMenuChange(updatedMenu);
const handleMenuChange = (menuState) => {
setMenuState(menuState)
}
This works but when i try and reset the state to the initial menuData it doesnt work. MenuData is getting mutated aswell. Why is this?
I am calling this again - setMenuState([...menuData]);
but menuData is the same as the downloadMenu state.
Using ... only creates a shallow copy - a new list is created, but the items inside it remain the same. The objects in the list aren't copied, but remain as a reference to the original objects in menuData.
The solution is either to create a new object when you change menuData or create a deep copy.
A deep copy can be created using JSON - JSON.parse(JSON.stringify(menuData)) or a library like lodash - _.cloneDeep(menuData).

How to separate UI and application state in Redux

When writing a react-redux application, I need to keep both application and UI state in the global state tree. What is the best approach to design it's shape?
Lets say I have a list of todo items:
{
items: [
{ id: 1, text: 'Chew bubblegum' },
{ id: 2, text: 'Kick ass' }
]
}
Now I want to let the users select and expand the items. There are (at least) two options how to model the state shape:
{
items: [
{ id: 1, text: 'Chew bubblegum', selected: true, expanded: false },
{ id: 2, text: 'Kick ass', selected: false, expanded: false }
]
}
But this is mixing the UI state (selected and expanded) with the application state. When I save the todo list to the server, I want to save just the application state, not the UI state (in real-world app, UI state can contain state of modal dialogs, error messages, validation status etc).
Another approach is to keep another array for the UI state of the items:
{
items: [
{ id: 1, text: 'Chew bubblegum' },
{ id: 2, text: 'Kick ass' }
],
itemsState: [
{ selected: true, expanded: false },
{ selected: false, expanded: false }
]
}
Then you have to combine those two states when rendering an item. I can imagine that you can zip those two arrays in the connect function to make rendering easy:
const TodoItem = ([item, itemState]) => ...;
const TodoList = items => items.map(item => (<TodoItem item={item} />));
export default connect(state => _.zip(state.items, state.itemsState))(TodoList);
But updates to state can be painful, because items and itemsState must be kept in sync:
When removing an item, corresponding itemState must be removed.
When reordering items, itemsState must be reordered too.
When the list of todo items is updated from the server, it is necessary to keep ids in the UI state and do some reconciliation.
Is there any other option? Or is there any library that helps keeping the app state and UI state in sync?
Another approach inspired by normalizr:
{
ids: [12,11], // registry and ordering
data: {
11: {text: 'Chew bubblegum'},
12: {text: 'Kick ass'}
},
ui: {
11: { selected: true, expanded: false },
12: { selected: false, expanded: false }
}
}
I'm currently looking at this myself for a side project. I'm going to approach it similar to Rick's method above. The data{} serves as the source of truth and you use that to push local ui changes into (reflecting the most current state). You do need to merge the data and ui together before render, and I myself have tried that in a few places. I will say, as far as keeping in sync, it shouldn't be too bad. Basically, whenever you save/fetch data, you're updating data{} and clearing out ui{} to prepare for the next use case.

Resources