I’m looking to create a small app, where some elements will be nested in another. While developing the app I’m trying to segment each component into its own file (children), returning a function with 2 components in each Renderer
and values
.
The goal of this approach is to
- Making the code more readable, and easier to develop
- make the data in the class easily available for exporting purposes.
However, I experienced 2 issues, in the process
- when the child-object uses
useState
, and renders an increasing amount of objects (say increasing an array size, rendering each element), the effect which increases the child-object size stops functioning (does nothing at all) - If the parent (so to speak) contains an array of child objects each with a render function, increasing the size of this array leads to dynamically increasing the number of hooks.
I don’t see a good way to get around this, and I’m hoping to get some clarity with this question
The simplest way to illustrate this is with a small example.
In the code below I’ve created a child component F, which holds a list of rows that it should render. This works all fine when F is the only element of the app. However, if the app contains a list of "F"’s, which may increase in size, this causes an increase in number of hooks.
What would be some possible ways around this, while maintaining the same or similar functionality?
import { useState } from 'react';
import { View, Text, SafeAreaView, Button } from 'react-native';
const F = () => {
const [rows, setRows] = useState([1]);
const handleAddRow = () => {
setRows((old) => [...old, old.length + 1]);
};
const render = () => {
return (
<View>
{rows.map((row, index) => (
<Text>"index"</Text>
))}
<Button title="Row button" style={{flex: 1, backgroundColor: '#aff'}} onPress={() => handleAddRow}/>
</View>
);
};
return ({values: rows, Renderer : render});
};
const App = () => {
const [fs, setFs] = useState([F()]);
const handleAddFs = () => {
setFs((old) => [...old, F()]);
};
return (
<SafeAreaView style={{ flex: 1, backgroundColor: '#fff' }}>
<View>
{fs.map((fi, index) => (
fi.Renderer()
))}
{fs.map((fi, index) => (
<Text>`{fi.values.length}`</Text>
))}
</View>
<Button
title="Press me"
color="#f194ff"
onPress={() => handleAddFs()}
/>
</SafeAreaView>
);
};
export default App;
2
Answers
Currently what’s being rendered is the App component. It has 1 state. Upon clicking button that calls
handleAddFs
, you’re calling a new function which creates another state[rows,setRows]
. This isn’t something that’s allowed in React. What you can do however is have the state be rendered together with the App and pass the state as a prop to yourF()
function like done below here :Ultimately, the problem is that you’re trying to invert the natural data flow of React which is that data flows from parent to child.
You’ve got
F
, which looks like a component because it containsuseState
– but it’s called as a function (which means it can’t actually have state) and you’re returning the values from the called function to the parent so the parent can render the state of the childThat’s inverting the data flow, just keep the necessary state in the parent and let it flow down the tree.
You’ll know when you’re trying to fight against React’s natural data flow, because that’s when stuff starts to get complicated and hard (like your original code). React is easy, it’s actually trivially simple, if you don’t fight it.
Here’s the re-written code that accomplishes the same goal:
It’s important to note that my code above ignores the fact that rendering JSX inside a map call always needs keys. That can be a topic for another question, or you can just read about why you need to do it in the docs.