React - Inner functional components - javascript

Imagine a simple form, which renders multiple custom components applying some layout tweaks.
The custom components are: <UsernameInput />, <PasswordInput />, <DateTimePicker />, <FancyButton />, <Checkbox />
And the JSX of the form component looks like:
return (
<View style={globalStyles.flex}>
<View style={styles.inputsContainer}>
<UsernameInput
... // a lot of props
/>
<PasswordInput
... // a lot of props
/>
<DateTimePicker
... // a lot of props
/>
</View>
<View style={styles.footer}>
<Checkbox />
<FancyButton type="submit" onPress={...} />
</View>
</View>
);
What if we try to split the component's render method into multiple inner sub-render functions?
const LoginForm = ({ ... }) => {
...
/**
* Renders the inputs of the form.
*
* #returns {React.ReactElement} The inputs.
*/
const renderInputs = () => (
<View style={styles.inputsContainer}>
<UsernameInput
... // a lot of props
/>
<PasswordInput
... // a lot of props
/>
<DateTimePicker
... // a lot of props
/>
</View>
);
/**
* Renders the submit button.
*
* #returns {React.ReactElement} The footer of the form.
*/
const renderFooter = () => (
<View style={styles.footer}>
<Checkbox />
<FancyButton type="submit" onPress={...} />
</View>
);
return (
{renderInputs()}
{renderFooter()}
);
};
Those helper methods returns JSX! They are not simple vanilla functions, they are components! WE ARE RECREATING COMPONENTS ON EACH RENDER!
In order to handle this, what I do is converting the inner functions to inner memoized components, like this:
/**
* Renders the submit button.
*
* #type {React.FC}
* #returns {React.ReactElement} The footer of the form.
*/
const Footer = useCallback(() => (
<View style={styles.footer}>
<Checkbox />
<FancyButton type="submit" onPress={...} />
</View>
), [deps]);
return (
<View style={globalStyles.flex}
<Inputs />
<Footer />
</View>
);
Is this considered an anti-pattern? I mean, should we avoid splitting the JSX when there is no reason to create a new component in the global scope of the app (in a new module or outside the component)?

Is this considered an anti-pattern?
There's no point in using useCallback on those functions if you don't provide those functions as props to other components that optimize their rendering by not re-rendering when their props don't change. (Lots of people misunderstand useCallback, it's a bit subtle.) You're still recreating them every time (you have to create the function to pass it into useCallback), you're just then doing even more work (processing deps) to decide whether to use the newly-created function or the previously-created function. It's overhead without purpose, and not what useCallback is for. The purpose of useCallback is to create stable or stable-as-possible function references for passing to child components that may be able to avoid re-rendering if you don't change the functions they receive.
Instead, either:
Make those components (or just helper functions, depending) in your module. (No need to export them if they're only used by your component.) That way, they're only created once (when the module is loaded).
If you think about it, this is like any other function that's gotten too big: You break it into parts, and put those parts into smaller functions you call. Same concept.
Or
Create the functions once, on the first render (ensuring of course that they don't close over anything they need; pass it to them instead), storing the result on an object on a ref via useRef:
const fnRef = useRef(null);
if (!fn.Ref.current) {
fnRef.current = {
Footer: /*...*/,
SomethingElse: /*...*/,
// ...
};
}
const { Footer, SomethingElse /*, ...*/ } = fnRef.current;
// ...
That way, they truly are created only once, on mount.
I would strongly lean toward #1.

Related

React - how to change properties of a component in another component

function DatTable() {
return (
<DataTable
theme="dark"
columns={columns}
selectableRows
data={FakeData}
/>
);
};
I've called this component from another file in a dashboard
</Box>
<DatTable></DatTable>
</Box>
Everything works properly if I change the properties in the original function. What I'm trying to achieve is set a useTheme hook for the component and for that I want to edit the theme inside of the dashboard like so :
</Box>
<DatTable
theme="dark"
></DatTable>
</Box>
I've tried changing the properties inside of the dashboard component but it has no effects. I'm also unsure how to turn regular components into React components since the webpage goes white and breaks if I try it that way.
// default data
let columns={};
let data={}
function DatTable(theme="dark", columns=columns, selectableRows='somedefaultdata',data=FakeData) {
return (
<DataTable
theme=theme
columns={columns}
selectableRows
data={FakeData}
/>
);
};
Now
<DatTable
theme="white"
/>
If you this now:
you don't need to send props at all.
If you send props those props will overwrite those default values
Since you pass a theme prop to <DatTable> you need to extract it and then use it as a prop again in <DataTable>
function DatTable({theme}) {
return (
<DataTable
theme={theme}
columns={columns}
selectableRows
data={FakeData}
/>
);
};

How to implement custom component that accepts a nested component?

I would like to implement a custom component that accepts a nested component. Something that can be used like this.
<MyCustomComponent>
<AnyNestedComponent/>
</MyCustomComponent>
I searched about this but only found the use of this.props, which is not what I expect.
How do I implement MyCustomComponent in React Native version 0.68?
Note: MyCustomComponent will consist of View(s).
Its fairly simple in RN,
Your customComponent should be like =
const CumstomComp = ({props = {}, children = null}) => {
return(
<View style={{backgroundColor:"red"}} >
{children}
</View>
)
}
and then you use it like this
App.js or whatever file
const App = () => {
return(
<View>
<CustomComp>
<Flatlist />
<View />
</CustomComp>
</View>
)
}
Hope it helps. feel free for doubts

Why I can not use inline function in component prop? Can you explain in detail by giving an example? What means saying losing all state?

Note: The component prop accepts component, not a render function. Don't pass an inline function (e.g. component={() => }), or your component will unmount and remount losing all state when the parent component re-renders. See Passing additional props for alternatives.
This warning from the simulator.
enter image description here
function HomeScreen(props: Object) {
return (
<Navigator initialRouteName="Empty1">
<Screen
name="Empty1"
component={() => {
return (
<View>
<Text>Example Text</Text>
</View>
);
}}
/>
<Screen name="Home1" component={HomeScreen1} />
</Navigator>
);
}
It's warning you that component is going to be re-rendered every time screen is re-rendered. This is because every time the component arg is evaluated it returns a fresh inline function. (the prop changes every time thus it must always re-render)
Evaluation of the same inline function twice yields different objects.
Eg.
let funcs = []
for (let i =0 ; i<2; i++) {
funcs.push(() => 1)
}
funcs[0] == funcs[1]
>>> false
You can wrap this is useCallback to get a stable function that only updates when it needs to (never in your case since it captures no state). More on hooks here
function HomeScreen(props: Object) {
let component = useCallback(() => {
return (
<View>
<Text>Example Text</Text>
</View>
);
}, [] // no dependencies. This lambda will only be evaluated once.
);
return (
<Navigator initialRouteName="Empty1">
<Screen
name="Empty1"
component={component}
/>
<Screen name="Home1" component={HomeScreen1} />
</Navigator>
);
}
Or since this function doesn't depend on anything stateful you could also just declare it globally at the top of this file.
let component = () => {
return (
<View>
<Text>Example Text</Text>
</View>
);
};
function HomeScreen(props: Object) {
return (
<Navigator initialRouteName="Empty1">
<Screen
name="Empty1"
component={component}
/>
<Screen name="Home1" component={HomeScreen1} />
</Navigator>
);
}
You need to understand what is hiding behind JSX syntax.
When you use <SomeComponent/> it is added to the React Virtual DOM and exists there until the tree is changing and component has to be unmounted. It survives rerenders, the DOM is updating, but component lives.
When you use () => <SomeComponent /> this component is a new object on every rerender of the parent. If it has state (via setState hook) it will be reinitialized. If it has some code in useEffect(() => {}, []) hook it will be called again.

React Native, Redux and Navigation: handling state updates when passing params from navigation

I have an app with a component which maps an array of posts (records, rows or whatever you like) from a redux slice into buttons to navigate to a detail component, the navigate action passes the post param so the detail component receives it.
// ParentComponent.js
// Redux selector
const posts = useSelector((state) => state.posts);
// The array is mapped like this
posts.map((post, index) => {
return (
<TouchableOpacity
onPress={() => {
navigation.navigate('TabNavigation', {
screen: 'PostEditor',
params: {post},
});
}}
key={Post.slug}
<PostDetail post={post} />
</TouchableOpacity>
);
});
A post view is composed by a large form, so I had to make the detail component divided into sections (child components), this means I'm passing down the received param to the child components:
// Detail View / PostEditor
const {post} = route.params;
return (
<SafeAreaView>
<ScrollView>
<View style={styles.block}>
<PostFormOne post={post} />
<PostFormTwo post={post} />
<PostFormThree post={post} />
<PostFormFour post={post} />
</View>
</ScrollView>
</SafeAreaView>
);
The problem is when I make a change to the redux store on every child component, the other components are not updated, I guess because the child components are referencing the navigation param post and not the redux post store.
If so, then my question is how would you reference the redux store using the navigation param?
If this is not correct then what would be a better approach to this functionality?
Thanks in advance.
you create another store variable for the selected post along with an action that updates the current post. Then in your TouchableOpacity onPress, you call the action to update the current post.
<TouchableOpacity
onPress={() => {
navigation.navigate('TabNavigation', {
screen: 'PostEditor',
});
dispatch(updateCurrentPost(post));
}}
key={Post.slug}
<PostDetail post={post} />
</TouchableOpacity>
This way, you don't need to pass the post at all, and just do:
useSelector((state)=> state.currentPost);
in all of your sub components. You will need to call the updateCurrentPost action in all your sub components to keep the current post in sync.

Pass props nicely to screens in react navigation

I want to pass a prop to the screen. When I try that inline e.g (props) => <Comp {...props} {...customProps} /> I get a warning message, that I shouldn't parse functions to that component property. Okay. I thought I'll just create functions for every component which needs custom props. It is working, but is there a better solution? Here is my component:
export default function Loading() {
const [loggedIn, setLoggedIn] = React.useState(false);
const Stack = createStackNavigator();
const authService: AuthService = new AuthService();
const authProps: IAuthProps = {
authService
};
/**
* Bind neccessary props to the login component
* #param props Props
*/
function LoginWithProps(props) {
return <Login {...props} {...authProps} />;
}
/**
* Bin neccessary props to the registration component
* #param props Props
*/
function RegistrationWithProps(props) {
return <Registration {...props} {...authProps} />;
}
return (
<>
{/*Show the app, when logged in*/}
{loggedIn === true ? (
<View>
<Text>Test</Text>
</View>
) : (
<Stack.Navigator
initialRouteName="Login"
screenOptions={{ headerShown: false, animationEnabled: false }}
>
<Stack.Screen name="Login" component={LoginWithProps} />
<Stack.Screen name="Registration" component={RegistrationWithProps} />
</Stack.Navigator>
)}
</>
);
}
```
Yours is not a good solution to the problem because new types of LoginWithProps and RegistrationWithProps components will be created every render, meaning old ones will be unmounting and new ones mounting every time. The same thing that happens when you pass a function, but without a warning
You can't pass props to these screens, as it is not the Loading component that is a direct parent to these screens. If you want to pass data to customize those components, you need to do it through navigation params, in 2 ways:
when navigating to a screen navigation.navigate('Login', { param: 'hello' })
by proviging initial params
.
<Stack.Screen
name="Login"
component={Loaing}
initialParams={{ param: 'hello' }}
/>
And read it in Login with props.route.params
Note though this is called initialParams for a reason - they are not reactive, changing them after component is mounted has no effect (it is also possible to change them from inside component). If you want to pass reactive params, use React Context or Redux
Passing parameters to routes

Categories

Resources