Should the main App component be Pure or Stateless? - react-native

In React-Native, should we use Pure Component or Stateless Function for the main Component ?
Here is two ways of doing it:
import React from 'react';
import { Provider } from 'react-redux';
import store from './reducers/AppReducers';
import AppRoutes from './routes/AppRoutes';
// Pure Component
class App extends React.PureComponent {
render() {
return (
<Provider store={store}>
<AppRoutes />
</Provider>
);
}
}
// Stateless Function
const App = () => {
return (
<Provider store={store}>
<AppRoutes />
</Provider>
);
};

If your component is simple, use stateless. For simple components it is not required to use Pure Components
Let's say if you have a component that displays a text and you make it a pure component, Everytime it re-renders it will first do the shallow comparison.
In this situation a re-render would be performant then a shallow comparison.
It's upto you to decide weather your component would be performant if it checks for a shallow comparison or re-renders
TIP: If you have a very basic component, which only displays some basic stuff, use stateless.
https://medium.com/groww-engineering/stateless-component-vs-pure-component-d2af88a1200b
here it is explained in detail.
in your case, I would advice Pure Component as it contains your whole app, and a re-render would be more costly then a shallow comparison

Related

Testing react-native-testing-library with react-navigation v5

I'm looking to test react-navigation v5 with react-native-testing-library. The documentation says to do the following.
import React from 'react';
import { NavigationContainer } from '#react-navigation/native';
import { render, fireEvent } from '#testing-library/react-native';
describe('Testing react navigation', () => {
test('page contains the header and 10 items', async () => {
const component = (
<NavigationContainer>
<AppNavigator />
</NavigationContainer>
);
const { findByText, findAllByText } = render(component);
const header = await findByText('List of numbers from 1 to 20');
const items = await findAllByText(/Item number/);
expect(header).toBeTruthy();
expect(items.length).toBe(10);
});
});
https://callstack.github.io/react-native-testing-library/docs/react-navigation
https://medium.com/#dariaruckaolszaska/testing-your-react-navigation-5-hooks-b8b8f745e5b6
This medium article suggests I create a MockedNavigator component. would this be a mockedNavigator component for all of my screens? Is this reusable?
import React from 'react';
import {NavigationContainer} from '#react-navigation/native';
import {createStackNavigator} from '#react-navigation/stack';
const Stack = createStackNavigator();
const MockedNavigator = ({component, params = {}}) => {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="MockedScreen"
component={component}
initialParams={params}
/>
</Stack.Navigator>
</NavigationContainer>
);
};
export default MockedNavigator;
The answer I'm looking for is. Am I to use the real AppNavigator component within all my unit tests or am I to use a MockedAppNavigator?
Furthermore, how am I to pass props?
The documentation is unclear and I am looking for clarity. Many of the new components we are working with use hooks and the react-navigation-v5 is unable to access certain props.
There are two recommended approaches for RNTL tests involving React Navigation.
Either you test the same navigators you use in your app. This does not have to be the whole AppNavigator if your tests contain nested navigators. It can be only the one you want to test.
Or you test single screen not connected to any navigator. This would probably require refactor of that screen into the React Navigation-aware XxxScreen that would not be tested and XxxScreenContent that would receive relevant props from XxxScreen but would be indenpendend of XxxScreen.
You can find more details in the official React Navigation example in RNTL repo.
The mocked navigator approach is possible but not really advised as it's mocking too much, as such tests frequently have to rely on implementation details not visible to the users like route names, navigator nesting, etc.

props automaticaly given as parameter

I would like to use a custom component and I need it to have the navigation props in order to navigate inside this component.
For now I'm passing navigation as a classic prop:
<MyVideo videoSource={require('../Image/S01E01DB.mp4')}
thumbnailSource={require('../Image/S01E01DB.mp4')}
navigation={this.props.navigation}/>
But is it possible to always have him set as it was given as a prop?
I need that in order to simplify the usage of my prop.
As said in the comments section by #jonrsharpe, the navigation props is only automatically given to routes declared with createStackNavigator or the others react-navigationnavigators`
There's a tool you can use from react-navigation to connect any component to your parent navigator:
import { withNavigation } from 'react-navigation';
class MyBackButton extends React.Component {
render() {
return <Button title="Back" onPress={() => { this.props.navigation.goBack() }} />;
}
}
// withNavigation returns a component that wraps MyBackButton and passes in the
// navigation prop
export default withNavigation(MyBackButton);
In case of nested navigators, the withNavigation function will get the closest parent of your component.
You will find more informations on this link

React.Children.only expected to receive a single React element child

import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View
} from 'react-native';
import Login from './js/components/Login';
import userReducers from './js/reducers/user';
import { createStore, combineReducers } from 'redux';
import { Provider } from 'react-redux';
let store = createStore(combineReducers({userReducers}));
class App extends Component {
render() {
return (
<Login />
);
}
}
export default class Testextends Component {
render() {
return (
<Provider store = {store}>
<App />
</Provider>
// <View>
// <Text>jhdgf</Text>
// </View>
);
}
}
AppRegistry.registerComponent('Test', () => Test);
It gives me following error:
I'm trying one basic example of react-native with redux. If I remove TouchableHighlight also , the error still persists. Any ideas what is wrong here?
Its doesn't seems you're doing something wrong, on the code pasted, are you sure the error came from this part of your code ?
Maybe try to put between () your TouchableHighlight component, but I don't think that's going to change something...
Or try a ternary rather than a binary operation.
Also be sure that you import TouchableHighlight and Text from react-native.
Try to delete all not necessary white space
<View>{ !this.props.user.loggedIn &&
<TouchableHighlight onPress={this.onLoginButtonPress}>
<Text>Login</Text>
</TouchableHighlight>
}</View>
Because in the following example
<View> {...}</View>
you have two children: first white space and second all in curved brackets.
Had the same issue with the following React Native code:
import {Link} from 'react-router-native'
<Link to={props.url}>{props.title}</Link>
Spend few hours trying to understand why the similar code works in WEB but not in native view. There is no viable error messaging or hints from the React Native runtime on which component causes this failure, so it's insanely hard to identify the issue.
Managed to fix this crash by doing the following:
import {Link} from 'react-router-native'
import {Text} from "react-native";
<Link to={props.url}><Text>{props.title}</Text></Link>
Looks like the title text was treated as multiple elements, so we ended up with such an error.
This is not a solution for the subject. But, because there are so many ways to fail with this error in React Native, it may be useful to put this another example of such a crash for those who Googled and ended here.
First off, "export default class Testextends Component" should actually be "export default class Test extends Component"
the reason why that error is thrown is because the Provider component takes only one element.
for Example:
wrong:
<provider store = {store}>
<div/>
<div/>
<provider>
correct:
<provider store = {store}>
<App/>
<provider>
which you did correctly, if you commented the component. Try the class error and see if it works.

How to reference custom components in react-native?

So I have a custom component like this:
class MyComponent extends React.Component {
render() {
return (
<TouchableHighlight style={this.props.buttonStyle}>
<Text style={styles.buttonText}>Button</Text>
</TouchableHighlight>
);
}
}
And I use the component like this:
class RootView extends React.Component {
render() {
return (
<View>
<MyComponent/>
<MyComponent/>
</View>
);
}
}
The RootView is resizable. What I want to do is shrinking it's children MyComponent when RootView is small enough. And I need to shrink each MyComponent at a time: when RootView is small enough, shrink the first MyComponent, and when the first MyComponent reach a minimal size, shrink the second MyComponent.
I know there's refs in react-native, but it does not seem to work for custom component.
You can add a ref to a custom component:
<Separator ref='sep' style={styles.offsetSeparator} />
So you should be able to do what you need.
However, when you find yourself having a strong dependency between different components, it means that it's probably a good time to start using Flux or something similar and then hold the data in a Store which will hold all the information needed for all 3 components. More about Flux here: https://facebook.github.io/flux/

Change view in Navigator in React Native

I'm new to react native.
I was using NavigatorIOS but it was too limiting so I'm trying to use Navigator. In NavigatorIOS I can change a view by calling this.props.navigator.push() but it doesn't work in Navigator, it seems to be structured differently. How do I change views in using Navigator?
That's the minimal working navigator - it can do much more (see at the end):
You need your main "navigator" view to render Navigator component
In the Navigator you need to specify how you should render scenes for different routes (renderScene property)
In this "renderScene" method you should render View (scene) based on which route is being rendered. Route is a plain javascript object, and by convention scenes can be identified by "id" parameter of the route. For clarity and separation of concerns I usually define each scene as separate named component and use the name of that components as "id", though it's just a convention. It could be whatever (like scene number for example). Make sure you pass navigator as property to all those views in renderScene so that you can navigate further (see below example)
When you want to switch to another view, you in fact push or replace route to that view and navigator takes care about rendering that route as scene and properly animating the scene (though animation set is quite limited) - you can control general animation scheme but also have each scene animating differently (see the official docs for some examples). Navigator keeps stack (or rather array) of routes so you can move freely between those that are already on the stack (by pushing new, popping, replacing etc.)
"Navigator" View:
render: function() {
<Navigator style={styles.navigator}
renderScene={(route, nav) =>
{return this.renderScene(route, nav)}}
/>
},
renderScene: function(route,nav) {
switch (route.id) {
case "SomeComponent":
return <SomeComponent navigator={nav} />
case "SomeOtherComponent:
return <SomeOtherComponent navigator={nav} />
}
}
SomeComponent:
onSomethingClicked: function() {
// this will push the new component on top of me (you can go back)
this.props.navigator.push({id: "SomeOtherComponent"});
}
onSomethingOtherClicked: function() {
// this will replace myself with the other component (no going back)
this.props.navigator.replace({id: "SomeOtherComponent"});
}
More details here https://facebook.github.io/react-native/docs/navigator.html and you can find a lot of examples in Samples project which is part of react-native: https://github.com/facebook/react-native/tree/master/Examples/UIExplorer
I find that Facebook examples are either to simplistic or to complex when demonstrating how the Navigator works. Based on #jarek-potiuk example, I created a simple app that will switch screens back and forth.
In this example I'm using: react-native: 0.36.1
index.android.js
import React, { Component } from 'react';
import { AppRegistry, Navigator } from 'react-native';
import Apple from './app/Apple';
import Orange from './app/Orange'
class wyse extends Component {
render() {
return (
<Navigator
initialRoute={{screen: 'Apple'}}
renderScene={(route, nav) => {return this.renderScene(route, nav)}}
/>
)
}
renderScene(route,nav) {
switch (route.screen) {
case "Apple":
return <Apple navigator={nav} />
case "Orange":
return <Orange navigator={nav} />
}
}
}
AppRegistry.registerComponent('wyse', () => wyse);
app/Apple.js
import React, { Component } from 'react';
import { View, Text, TouchableHighlight } from 'react-native';
export default class Apple extends Component {
render() {
return (
<View>
<Text>Apple</Text>
<TouchableHighlight onPress={this.goOrange.bind(this)}>
<Text>Go to Orange</Text>
</TouchableHighlight>
</View>
)
}
goOrange() {
console.log("go to orange");
this.props.navigator.push({ screen: 'Orange' });
}
}
app/Orange.js
import React, { Component, PropTypes } from 'react';
import { View, Text, TouchableHighlight } from 'react-native';
export default class Orange extends Component {
render() {
return (
<View>
<Text>Orange</Text>
<TouchableHighlight onPress={this.goApple.bind(this)}>
<Text>Go to Apple</Text>
</TouchableHighlight>
</View>
)
}
goApple() {
console.log("go to apple");
this.props.navigator.push({ screen: 'Apple' });
}
}
I was having the same trouble, couldn't find a good example of navigation. I wanted the ability to control views to go to a new screen but also have the ability to go back to the previous screen.
I used the above answer by Andrew Wei and created a new app then copied his code. This works well but the .push will keep on creating new layers over each other (Apple > Orange > Apple > Orange > Apple > Orange etc.). So I used .pop in the Orange.js file under goApple() instead of .push.
This works like a "back" button now, which was what I was looking for, while teaching how to navigate to other pages.