React component remounts when state object is reordered - reactjs

I have a react-redux application, and am using react-transition-group for transitions when child components position changes in a state change. However, if the order of my redux state object changes, components will unmount and then remount, rather than updating. This prevents the component from smoothly transitioning to its new position. Is there a way around this without introducing new state?
My parent component looks like this:
class ParentComponent extends Component {
...
render() {
return (
<div>
<div>
<CSSTransitionGroup>
<ChildComponents childComponentsToRender=
{this.props.someChildComponents}/>
<CSSTransitionGroup/>
</div>
</div>
)}}
ChildComponents looks like this:
export default function ChildComponents(props) {
const childrenToReturn = _.map(props.childComponentsToRender,
(childComponent) =>
<ChildComponent
key={childComponent.uniqueId}
position={childComponent.position} .../>
return (
<Fragment>
{childrenToReturn}
</Fragment>
)
}
And an individual ChildComponent looks like this:
export default function ChildComponent(props) {
return (
<div style={{transform: `translate(${props.position.x}px,
${props.position.y}px)`}}>
</div>
)
}
My redux state that renders the components looks like:
{
...,
someChildComponents: {
childComponent1: {
id: someUniqueId,
position: {x: int, y: int}
},
childComponent2: {
id: someUniqueId,
position: {x: int, y: int}
},
...
},
...
}
The problem would occur if an action was dispatched and the state would change from, for instance:
{
someChildComponents: {
childComponent1: {
id: someUniqueId,
position: {x: 100, y: 100}
},
childComponent2: {
id: someUniqueId,
position: {x: 200, y: 200}
}
}
}
To:
{
someChildComponents: {
childComponent2: {
id: someUniqueId,
position: {x: 300, y: 300}
},
childComponent1: {
id: someUniqueId,
position: {x: 150, y: 150}
}
}
}

If you need to relate on specific position of element, you better need array, not object. But I don't recommend to convert someChildComponents to array. In most cases, object is preferred to array when storing distinct data.
You may create array on the fly based on your someChildComponents object, for example using selectors. The only thing you need is some unique identifier within someChildComponents object, which can be used to sort your array.
Here is example code
const getChildrenComponents = state => state.someChildComponents;
export function makeArrayFromChildrenComponents() {
return createSelector(
[getChildrenComponents],
(ChildrenComponents) => (
Object.keys(ChildrenComponents).map(key => ChildrenComponents[key])
.sort((e1, e2) => {
if (e1.id === e2.id) return 0;
return e1.id < e2.id ? -1 : 1;
});
)
)
}
Then use it to provide stable array of children components to ParentComponent like this
const makeMapStateToProps = () => {
const getChildrenComponentsArray = makeArrayFromChildrenComponents();
const mapStateToProps = (state) => ({
ChildrenComponents: getChildrenComponentsArray(state)
// ... other mapped props
});
return mapStateToProps;
}
Selector will update ChildrenComponents whenever state changes. But array itself will hold the same order of elements.
You may also take a look at diffing algorithm used by React to decide whether to build new tree or hold existing.

Related

React functional component to accept rendering function

I would like to find out how to write a functional component that would accept an array of objects as prop (dataSource), then render all the array objects based on a RenderFunction which I would pass as another prop. I want the render function to accept a parameter that would represent an object from the array.
Check this code snippet and the comments:
// This is my data
var dummyData = [{
"id": 1,
"name": "item1"
},
{
"id": 2,
"name": "item2"
},
{
"id": 3,
"name": "item3"
},
{
"id": 4,
"name": "item4"
},
{
"id": 5,
"name": "item5"
},
{
"id": 6,
"name": "item6"
}
]
// Functional Component :
function ListComponent(props) {
return (
<div className={"ListContainer"}>
{props.dataSource.map((x) => {
return (
<div key={x.id} className="ListItemContainer">
{props.itemRender}{" "}
</div>
);
})}{" "}
</div>
);
}
// This is my render function which I'm passing as a prop.
// I would like it to accept an argument which would become
// an object of dummy data array.
// For example if I passed x to it, the x inside the function on the first
// .map iteration would become {"id": 1,"name": "item1"}
function itemRenderTest() {
return (
<p> This is item </p>
// I want to be able to render it like this
// <p> This is {x.name} </p>
)
}
// passing props and rendering
ReactDOM.render(
<ListComponent
dataSource={dummyData}
itemRender={itemRenderTest()}
/>,
document.getElementById("root")
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
The snippet illustrates some of the desired functionality. I can write a render function, but don't know how can I change the code so the render function could accept a parameter which would represent an object from the array.
I want to be able to write the render function like this:
function itemRenderTest(x){
return(
<p>This is {x.name}</p>
)
}
The Component would receive 2 props. The first - dataSrouce would specify the JSON array. The second - render function would define how the child list components are being rendered
<ListComponent
dataSource={dummyData}
itemRender={itemRenderTest}
/>
I'm trying to recreate a reusable component similar to what a lot of DevExtreme react components do. They basically just accept a render function definition like this renderItem={renderItemFunction} and it just works. I want to write my List component so it does the same This is a good example of one of their components
Is this possible with React? Code snippets would be really helpful.
That's 100% possible in React and a super common pattern. If I understand your question correctly -- what I typically do in this situation is
Define a parent component for the list items. It will handle fetching or otherwise retrieving the array of objects data, the overall state of that data, and the render logic for the individual list components
ListItem component, which is stateless (pure component) and simply renders reusable components based on data passed in as props. That's how component libraries create reusable components, like the one you mentioned
const ItemsList = () => {
// state variable which will store the list data
const [listData, setListData] = useState([])
// let's assume the list items are fetched from an API on initial render
useEffect(() => {
// fetch logic for list items, then update state variable
// with resulting data
const listItems = axios("https://yourapi.com/listitems")
.then(({ data }) => setListData(data)
.catch(err => console.info("Fetch error", err))
)
}, [])
const renderList = useMemo(() => listData.map(
(listItemData) => <ListItem data={listItemData}/>),
[listData])
return (
<div>
{renderList}
</div>
)
}
const ListItem = (props) => {
const { data } = props;
return (
// populate the component with the values from the data object
)
}
A few things to point out here:
useEffect hook with [] dependency will only run once on first render, retrieve the necessary data, and update the state variable initialized with the useState hook. This can be done in other ways too
useMemo hook allows us to define the render logic for the individual list components, and memoize the evaluated result. That way, this function won't run on every render, unless the value of the listData variable changes. The function provided to the useMemo hook iterates through the array of objects, and renders a ListItem components with the respective data
The ListItem component then simply receives the data as a prop and renders it
Edit based on the updated answer:
I haven't tested it but this approach should work.
const ItemsList = (props) => {
const { data, itemRender: ItemRender } = props;
const renderItems = () => data.map((itemData) => <ItemRender data={itemData}/>)
return (
<div>
{renderItems()}
</div>
)
}
const ListItem = (props) => {
const { data } = props;
return (
// populate the component with the values from the data object
)
}
const App = () => {
const data = retrieveData()
return (
<ItemsList data={data} itemRender={ListItem}/>
)
}
App component retrieves the data, decides on the component that it will use to render the individual item (ListItem) and passes both of those as props to ItemsList
ItemsList then simply maps the data to each individual component
Edit 2: basic working snippet
const ItemsList = (props) => {
const { data, itemRender: ItemRender } = props;
const renderItems = () => data.map((itemData, i) => <ItemRender data={itemData.val} key={i}/>)
return (
<div>
{renderItems()}
</div>
)
}
const ListItem = (props) => {
const { data } = props;
console.info(data)
return (
<div
style={{
width: 100,
height: 100,
border: "2px solid green",
display: "flex",
alignItems: "center",
justifyContent: "center"
}}>
{data}
</div>
)
}
const App = () => {
const data = [{val: 1}, {val: 2}, {val: 3}]
return (
<div
style={{
width: "100vw",
height: "100vh",
backgroundColor: "white",
display: "flex",
alignItems: "center",
justifyContent: "center"
}}>
<ItemsList data={data} itemRender={ListItem}/>
</div>
)
}
export default App;

My state changes between the reducer and the consuming component

App purpose: The purpose of this React app is to handle scoring of a very specific dart-game. 2 players, each having to reach 33 hits in the fields 20-13, Tpl's, Dbls's and Bulls. No points, only the number of hits are counted. The hits are added manually by the players (no automatiion required :)).
Each targetfield has a row of targets and 2 buttons for adding and removing a hit of that target field.
I have implemented the useContext-design for maintaining state, which looks like this:
export interface IMickeyMouseGameState {
player1 : IPlayer | null,
player2 : IPlayer | null,
winner : IPlayer | null,
TotalRounds : number,
GameStatus: Status
CurrentRound: number
}
Other objects are designed like this :
export interface IGame {
player1?:IPlayer;
player2?:IPlayer;
sets: number;
gameover:boolean;
winner:IPlayer|undefined;
}
export interface IPlayer {
id:number;
name: string;
targets: ITarget[];
wonSets: number;
hitRequirement : number
}
export interface ITarget {
id:number,
value:string,
count:number
}
export interface IHit{
playerid:number,
targetid:number
}
So far so good.
This is the reducer action with the signature:
export interface HitPlayerTarget {
type: ActionType.HitPlayerTarget,
payload:IHit
}
const newTargets = (action.payload.playerid === 1 ? [...state.player1!.targets] : [...state.player2!.targets]);
const hitTarget = newTargets.find(tg => {
return tg.id === action.payload.targetid;
});
if (hitTarget) {
const newTarget = {...hitTarget}
newTarget.count = hitTarget.count-1;
newTargets.splice(newTargets.indexOf(hitTarget),1);
newTargets.push(newTarget);
}
if (action.payload.playerid === 1) {
state.player1!.targets = [...newTargets];
}
if (action.payload.playerid === 2) {
state.player2!.targets = [...newTargets];
}
let newState: IMickeyMouseGameState = {
...state,
player1: {
...state.player1!,
targets: [...state.player1!.targets]
},
player2: {
...state.player2!,
targets: [...state.player2!.targets]
}
}
return newState;
In the Main component i instantiate the useReducerHook:
const MickeyMouse: React.FC = () => {
const [state, dispatch] = useReducer(mickeyMousGameReducer, initialMickeyMouseGameState);
const p1Props: IUserInputProps = {
color: "green",
placeholdertext: "Angiv Grøn spiller/hold",
iconSize: 24,
playerId: 1,
}
const p2Props: IUserInputProps = {
playerId: 2,
color: "red",
placeholdertext: "Angiv Rød spiller/hold",
iconSize: 24,
}
return (
<MickyMouseContext.Provider value={{ state, dispatch }} >
<div className="row mt-3 mb-5">
<h1 className="text-success text-center">Mickey Mouse Game</h1>
</div>
<MickeyMouseGameSettings />
<div className="row justify-content-start">
<div className="col-5">
{state.player1 ?<UserTargetList playerid={1} /> : <UserInput {...p1Props} /> }
</div>
<div className="col-1 bg-dark text-warning rounded border border-warning">
<MickeyMouseLegend />
</div>
<div className="col-5">
{state.player2 ? <UserTargetList playerid={2} /> : <UserInput {...p2Props} /> }
</div>
</div>
</MickyMouseContext.Provider>
);
}
export default MickeyMouse;
Now the reducer-action correctly subtracts 1 from the target's count (the point is to get each target count to 0 and the new state correctly shows the target with 1 less than the old state, but when the Consumer (in this case a tsx-component called UserTargets, which is respnsible for rendering each target with either a circle or an X) the state of the target is 2 lower, even though the reducer only subtracted 1....
After adding a single hit to player 'Peter' in the 20-field - the rendering (with consoloe-logs) looks like this:
So I guess my question is this : Why is the state mutating between the reducer and the consumer and what can I do to fix it?
If further explanation is needed, please ask, if this question should be simplpified, please let me know...
I usually don't ask questions here - I mostly find anwers.
The project i available on github: https://github.com/martinmoesby/dart-games
Issue
I suspect a state mutation in your reducer case is being exposed by the React.StrictMode.
StrictMode - Detecting unexpected side effects
Strict mode can’t automatically detect side effects for you, but it
can help you spot them by making them a little more deterministic.
This is done by intentionally double-invoking the following functions:
Class component constructor, render, and shouldComponentUpdate methods
Class component static getDerivedStateFromProps method
Function component bodies
State updater functions (the first argument to setState)
Functions passed to useState, useMemo, or useReducer <--
The function being the reducer function.
const newTargets = (action.payload.playerid === 1 // <-- new array reference OK
? [...state.player1!.targets]
: [...state.player2!.targets]);
const hitTarget = newTargets.find(tg => {
return tg.id === action.payload.targetid;
});
if (hitTarget) {
const newTarget = { ...hitTarget }; // <-- new object reference OK
newTarget.count = hitTarget.count - 1; // <-- new property OK
newTargets.splice(newTargets.indexOf(hitTarget), 1); // <-- inplace mutation but OK since newTargets is new array
newTargets.push(newTarget); // <-- same
}
if (action.payload.playerid === 1) {
state.player1!.targets = [...newTargets]; // <-- state.player1!.targets mutation!
}
if (action.payload.playerid === 2) {
state.player2!.targets = [...newTargets]; // <-- state.player2!.targets mutation!
}
let newState: IMickeyMouseGameState = {
...state,
player1: {
...state.player1!,
targets: [...state.player1!.targets] // <-- copies mutation
},
player2: {
...state.player2!,
targets: [...state.player2!.targets] // <-- copies mutation
}
}
return newState;
state.player1!.targets = [...newTargets]; mutates and copies in the update into the previous state.player1 state and when the reducer is run again, a second update mutates and copies in the update again.
Solution
Apply immutable update patterns. Shallow copy all state the is being updated.
const newTargets = (action.payload.playerid === 1
? [...state.player1!.targets]
: [...state.player2!.targets]);
const hitTarget = newTargets.find(tg => tg.id === action.payload.targetid);
if (hitTarget) {
const newTarget = {
...hitTarget,
count: hitTarget.count - 1,
};
newTargets.splice(newTargets.indexOf(hitTarget), 1);
newTargets.push(newTarget);
}
const newState: IMickeyMouseGameState = { ...state }; // shallow copy
if (action.payload.playerid === 1) {
newState.player1 = {
...newState.player1!, // shallow copy
targets: newTargets,
};
}
if (action.payload.playerid === 2) {
newState.player1 = {
...newState.player2!, // shallow copy
targets: newTargets,
};
}
return newState;

React & Deck.GL: Add default props to each child component

I'm working on a configurable set of map layers with Deck.GL & React. I have a BaseMap component that I'll pass layers of data to as react children.
Currently, I have this:
BaseMap:
export const BaseMap = ({ latitude = 0, longitude = 0, zoom = 4, children }) => {
const deckProps = {
initialViewState: { latitude, longitude, zoom },
controller: true
};
return (
<DeckGL {...deckProps}>
{children}
<StaticMap />
</DeckGL>
);
};
And it's used like this:
<BaseMap>
<ScatterplotLayer
data={scatterData}
getPosition={getPositionFn}
getRadius={1}
radiusUnits={'pixels'}
radiusMinPixels={1}
radiusMaxPixels={100}
filled={true}
getFillColor={[255, 255, 255]}
/>
<TextLayer
data={textData}
getPosition={getPositionFn}
getColor={[255, 0, 0]}
getText={getTextFn}
/>
</BaseMap>
This is okay, but I want to add default props to each child.
Attempt 1
I've tried this in BaseMap, but get the error cannot assign to read only property props of object #<Object>:
...
return (
<DeckGL {...deckProps}>
{React.Children.map(children, (c) => {
const defaultProps = {
loaders: [CSVLoader]
}
c.props = { ...defaultProps, ...c.props };
return c;
})}
</DeckGL>
);
Attempt 2
I've also tried creating a wrapper component for each type of layer, but get the error Cannot call a class as a function:
wrapper:
export const ScatterplotLayerWrapper = (props) => {
const defaultScatterProps = {
loaders: [CSVLoader]
};
const scatterLayerProps = {
...defaultScatterProps,
...props
};
return <ScatterplotLayer {...scatterLayerProps} />;
};
used like this:
<BaseMap>
<ScatterplotLayerWrapper
data={scatterData}
getPosition={getPositionFn}
/>
</BaseMap>
I suspect the problem with this second attempt has something to do with the caveat here.
Solution?
I can imagine two types of solutions (and obviously, there may be others!):
correct method for checking the layer type & modifying child props depending on the type, or something similar - is this possible?
or
Some way to convince react/deck.gl that ScatterplotLayer will be a child of Deck.GL, even if it isn't in ScatterplotLayerWrapper. (This one seems less likely)
The confusion came from a mis-understanding of how deck.gl's React component works & what those <ScatterplotLayer> components really are (they're not react components).
Deck.gl's react component, DeckGL, intercepts all children and determines if they are in fact "layers masquerading as react elements" (see code). It then builds layers from each of those "elements" and passes them back to DeckGL's layers property.
They look like react components, but really aren't. They can't be rendered on their own in a React context. They can't be rendered outside of the DeckGL component at all, because they're still just plain deck.gl layers.
The solution here is to create a new map layer class just like you might in any other context (not a React component wrapping a layer). Docs for that are here.
class WrappedTextLayer extends CompositeLayer {
renderLayers() { // a method of `Layer` classes
// special logic here
return [new TextLayer(this.props)];
}
}
WrappedTextLayer.layerName = 'WrappedTextLayer';
WrappedTextLayer.defaultProps = {
getText: (): string => 'x',
getSize: (): number => 32,
getColor: [255, 255, 255]
};
export { WrappedTextLayer };
This new layer can then be used in the BaseMap component (or the un-wrapped DeckGL component`) like this:
<BaseMap>
<WrappedTextLayer
data={dataUrl}
getPosition={(d) => [d.longitude, d.latitude]}
/>
</BaseMap>
In addition, the exact same layer can be passed to DeckGL as a layer prop:
<DeckGL
layers={[
new WrappedTextLayer({
data: dataUrl,
getPosition: (d) => [d.longitude, d.latitude]
})
]}
></DeckGL>
Modifying the BaseMap component a little will allow it to accept layers either as JSX-like children, or via the layers prop as well:
export const BaseMap = ({ latitude = 0, longitude = 0, zoom = 4, children, layers }) => {
const deckProps = {
initialViewState: { latitude, longitude, zoom },
controller: true,
layers
};
return (
<DeckGL {...deckProps}>
{children && !layers ? children : null}
<StaticMap />
</DeckGL>
);
};

ReactJS sending ref to global useContext state (Konva)

I am using useContext as a global state solution. I have a Store.jsx which contains my state, and a reducer.jsx which reduces. I am using Konva to create some shapes on an HTML5 Canvas. My goal is when I click on a shape I want to update my global state with a reference to what is active, and when I click again, to clear the reference.
My Full Code can be found here:
https://codesandbox.io/s/staging-platform-2li83?file=/src/App.jsx
Problem:
The problem is when I update the global state via the onClick event of a shape, its says that the reference is 'null', but when I console.log the reference in the onClick I can see the correct reference.
I think I am missing an important point to how useRef works.
This is how the flow appears in my head when I think about this:
I create a canvas, and I map an array of rectangle properties. This creates 4 rectangles. I use a wrapper component that returns a rectangle.
{rectArray.map((rectangle, index) => {
return (
<RectWrapper key={index} rectangle={rectangle} index={index} />
);
})}
Inside the RectWrapper, I create a reference, pass it to the ref prop of the Rect. In the onclick function, when I console log 'shapeRef' I see the refence ONLY when dispatch is commented out. If I uncomment dispatch then it shows as null, and if I console log the state, the reference is always null.
const RectWrapper = ({ rectangle, index }) => {
const shapeRef = React.useRef();
return (
<Rect
x={rectangle.x + index * 100}
y={5}
width={50}
height={50}
fill="red"
ref={shapeRef}
onClick={() => {
console.log("ShapeRef: ");
console.log(shapeRef); // This correctly identifies the rect only when dispatch is uncommented
dispatch({
type: "active_image",
payload: {
index: index,
reference: shapeRef
}
});
}}
/>
);
};
perhaps I am going about this to wrong way with hooks. I am just trying to keep a global state of whats been clicked on because components in another file would rely on this state.
The problem is happening because you are creating RectWrapper component as a functional component within your App component causing a new reference of the component to be created again and again and thus the reference is lost
Move your RectWrapper into a separate component declared outside of App component and pass on dispatch as a prop to it
import React, { useEffect, useContext, useState, Component } from "react";
import { Stage, Layer, Rect, Transformer } from "react-konva";
import { Context } from "./Store.jsx";
import "./styles.css";
const RectWrapper = ({ rectangle, index, dispatch }) => {
const shapeRef = React.useRef();
return (
<Rect
x={rectangle.x + index * 100}
y={5}
width={50}
height={50}
fill="red"
ref={shapeRef}
onClick={() => {
console.log("ShapeRef: ");
console.log(shapeRef);
dispatch({
type: "active_image",
payload: {
index: index,
reference: shapeRef
}
});
}}
/>
);
};
export default function App() {
const [state, dispatch] = useContext(Context);
console.log("Global State:");
console.log(state);
const rectArray = [
{ x: 10, y: 10 },
{ x: 10, y: 10 },
{ x: 10, y: 10 },
{ x: 10, y: 10 }
];
return (
<div className="App">
<Stage height={500} width={500}>
<Layer>
{rectArray.map((rectangle, index) => {
return (
<RectWrapper
dispatch={dispatch}
key={index}
rectangle={rectangle}
index={index}
/>
);
})}
</Layer>
</Stage>
</div>
);
}
Working demo
I don't think you need to create a ref in RectWrapper, because onClick has one event parameter. And the ref of the element that was clicked can be found in the event:
onClick={(e) => {
const thisRef = e.target;
console.log(thisRef );
...
Here is a working version without useRef: https://codesandbox.io/s/peaceful-brook-je8qo

React native no re-render after updating state array

I have the following code (full example):
import React, { useState, useEffect } from 'react';
import { SafeAreaView, View, Button, StyleSheet, Animated } from 'react-native';
import { PanGestureHandler, State } from 'react-native-gesture-handler';
const App = () => {
const [blocks, setBlocks] = useState([]);
const CreateBlockHandler = () => {
let array = blocks;
array.push({
x: new Animated.Value(0),
y: new Animated.Value(0)
});
setBlocks(array);
RenderBlocks();
};
const MoveBlockHandler = (index, event) => {
Animated.spring(blocks[index].x, { toValue: event.nativeEvent.x }).start();
Animated.spring(blocks[index].y, { toValue: event.nativeEvent.y }).start();
};
const RenderBlocks = () => {
return blocks.map((item, index) => {
return (
<PanGestureHandler key={index} onGestureEvent={event => MoveBlockHandler(index,event)}>
<Animated.View style={[styles.block, {
transform: [
{ translateX: item.x },
{ translateY: item.y }
]
}]} />
</PanGestureHandler>
)
});
};
return (
<SafeAreaView style={styles.container}>
<View style={styles.pancontainer}>
<RenderBlocks />
</View>
<Button title="Add block" onPress={CreateBlockHandler} />
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center'
},
pancontainer: {
width: '95%',
height:'75%',
borderWidth: 1,
borderColor: 'black'
},
block: {
width: 50,
height: 50,
backgroundColor: 'black'
}
});
export default App;
What does this code do? It's a big square, and a button below it. When I click on the button, a new black square (50x50) is made in the big square. I do this by creating a new array element (the array = blocks). This is done in the function CreateBlockHandler. This does not work correctly!
The function MoveBlockHandler makes the little squares movable. This works!
What does not work? When I create a new black square, the black square is not rendered on the screen. Only when I refresh, the square is rendered. The square is created through CreateBlockHandler, because when I do a console.log(blocks) in that function, I can see that a new array element is added.
How can I force this code to do a full re-render with all the array elements? I tried to wrap the render of the square in a separate function (RenderBlocks) and I'm calling this function every time a new square is made (last line in CreateBlockHandler). The function is called (I can check this with a console.log()) but no squares are rendered.
When you assign blocks to array the reference gete copied which mutates the state, so it doesn't re-render on setState.
const CreateBlockHandler = () => {
let array = [...blocks];
array.push({
x: new Animated.Value(0),
y: new Animated.Value(0)
});
setBlocks(array);
RenderBlocks
There are multiple issues with your code.
As kooskoos pointed out, your state remains referentially equal (it's the same array, only the elements change). This will not trigger re-render.
Also, you are manipulating state of the App component. RenderBlocks component's props and state remain unchanged which implies that they don't need to be re-rendered. Since the component is an anonymous function and is recreated during every render of App, it probably gets re-rendered anyways.
In addition, you are directly calling RenderBlocks, which looks like a component. That is unnecessary and will do nothing here, but if it had any hooks, it would cause problems.
You should probably also conform to the convention that components are PascalCase capitalised and callbacks snakeCase capitalised.

Resources