It's time to learn how to make components reusable. For this goal, we will use the best tool in our hands: the presentational component pattern. It decouples components from logic and makes them flexible.
I like to explain that the presentational component pattern is a website's world. For a long time now, there has been three leading blocks for every website: CSS, HTML, and JavaScript. React, however, introduced a bit of a different approach, that is, the automated generation of HTML based on JavaScript. HTML became virtual. Hence, you may have heard of the Virtual Document Object Model (Virtual DOM). This separation of concerns—HTML (view), CSS (styles), and JavaScript (logic, sometimes called the controller)—should remain untouched in our JavaScript-only world. Therefore, use presentational components to mimic HTML and container components for logic.
Approach this problem in the same fashion in React Native applications. The markup you write should be separated from the logic it consumes.
Let's see this in action. Do you remember Example 4_Stateful expandable component? It has one presentational component already:
const HelloText = ({children, ...otherProps}) => (
<Text {...otherProps}>{children}</Text>
);
This component does not introduce any logic and contains only markup, which is very short in this case. Any logic that can be useful is hidden within props and passed along, as this component does not need to consume it. In more complex examples, you may need to destructure props to pass them to the right components; for example, when using the spread operator above, all props that are not destructured are being passed.
But, instead of focusing on this simple example, let's start refactoring the App component. First of all, we will move the markup to the separate presentational component:
// src/ Chapter_1_React_component_patterns/
// Example_9_Refactoring_to_presentational_component/ App.js
// Text has been replaced with "..." to save space.
export const HelloBox = ({ isExpanded, expandOrCollapse }) => (
<View style={styles.container}>
<HelloText onPress={() => expandOrCollapse()}>...</HelloText>
<HelloText onPress={() => expandOrCollapse()}>...</HelloText>
{
isExpanded &&
<HelloText style={styles.text}>...</HelloText>
}
</View>
);
Now, we need to replace the render function in the App component with the following:
render = () => (
<HelloBox
isExpanded={this.state.expanded}
expandOrCollapse={this.expandOrCollapse}
/>
);
However, if you run the code now, you will end up with an error on the HelloText press event. This is due to how JavaScript handles the this keyword. In this refactor, we pass the expandOrCollapse function to another object, and there, this refers to a completely different object. Therefore, it cannot access state.
There are a few solutions to this problem, and one is by using the arrow function. I will stick to the best approach performance-wise. It comes down to adding the following line to your constructor:
this.expandOrCollapse = this.expandOrCollapse.bind(this);
There we go; the application is fully functional, just as before. We have refactored one component into two—one presentational and one responsible for logic. Sweet.
Would we identify the problem with the this keyword?
Perhaps not. This simple gotcha may catch you in big projects, where you will be too busy to rethink every single component. Watch out and remember integration tests.