How to prevent rendering on change - react-native

I have the following script. I'm running on a pc using create-react-native-app. The console gives me the following warnings. I'm not sure what to do about them if anything.
Here are the warnings:
Warning: componentWillReceiveProps is deprecated and will be removed in the
next major version. Use static getDerivedStateFromProps instead.
Please update the following components: TouchableOpacity
Learn more about this warning here:
hxxxx/fb.me/react-async-component-lifecycle-hooks
- node_modules\react-native\Libraries\ReactNative\YellowBox.js:82:15 in warn
- node_modules\react-native\Libraries\Renderer\ReactNativeRenderer-
dev.js:5706:19 in printWarning
- ... 19 more stack frames from framework internals
19:26:44: Warning: componentWillMount is deprecated and will be removed in
the next major version. Use componentDidMount instead. As a temporary
workaround, you can rename to UNSAFE_componentWillMount.
Please update the following components: TouchableOpacity
Learn more about this warning here:
hxxx/fb.me/react-async-component-lifecycle-hooks
- node_modules\react-native\Libraries\ReactNative\YellowBox.js:82:15 in warn
- node_modules\react-native\Libraries\Renderer\ReactNativeRenderer-
dev.js:5706:19 in printWarning
- ... 19 more stack frames from framework internals
19:26:46: Warning: componentWillReceiveProps is deprecated and will be
removed in the next major version. Use static getDerivedStateFromProps
instead.
Please update the following components: Text, TextInput, View
Learn more about this warning here:
hxxx/fb.me/react-async-component-lifecycle-hooks
- node_modules\react-native\Libraries\ReactNative\YellowBox.js:82:15 in warn
- node_modules\react-native\Libraries\Renderer\ReactNativeRenderer-
dev.js:5706:19 in printWarning
- ... 21 more stack frames from framework internals
19:26:48: Warning: componentWillMount is deprecated and will be removed in
the next major version. Use componentDidMount instead. As a temporary
workaround, you can rename to UNSAFE_componentWillMount.
Please update the following components: TouchableHighlight
Learn more about this warning here:
hxxx/fb.me/react-async-component-lifecycle-hooks
- node_modules\react-native\Libraries\ReactNative\YellowBox.js:82:15 in warn
- node_modules\react-native\Libraries\Renderer\ReactNativeRenderer-
dev.js:5706:19 in printWarning
- ... 23 more stack frames from framework internals
I'm having the following 2 problems with the code:
On each change of the input field the app renders. I need to suppress the render until onSubmitEditing is called
When the code runs I get a yellow warning on my IOS device;
Warning:componentWillMount is deprecated...
import React from 'react';
import { TextInput,Button, StyleSheet, View,Text, ScrollView } from 'react-native';
import {Constants} from 'expo'
let id=0
const Todo = (props) => (
<Text>
{/* <input type='checkbox'
checked={props.todo.checked}
onClick={props.onToggle}
/> */}
<Button title='delete' button onPress={props.onDelete}></Button>
<Text>{props.todo.text}</Text>
</Text>
)
export default class App extends React.Component {
constructor(){
super()
this.state={
todos:[],
inputText:''
}
}
clearText(){
this.setState({inputText:''})
}
addTodo(text){
console.log(text)
this.setState({todos: [...this.state.todos,
{ id:id++,
text: text,
checked:false
}
]
})
this.setState({inputText:text})
}
toggle(id){
this.setState({todos: this.state.todos.map(todo=>{
if(id!==todo.id)return todo
return{
id:todo.id,
text:todo.text,
checked: !todo.checked}})})
}
removeTodo(id){
this.setState({todos: this.state.todos.filter(todo=>(todo.id!==id))})
}
render(){
console.log(this.state)
return(
<View style={styles.container}>
<Text >Count of Todos: {this.state.todos.length}</Text>
<Text >{"Todo's checked:"}
{this.state.todos.filter(todo =>(todo.checked===true)).length}</Text>
<TextInput
style={{height:25,borderColor:'red',borderWidth:1,textAlign:'center'}}
value={this.state.inputText}
placeholder={'add Todo'}
onSubmitEditing={()=>{this.clearText()}}
onChangeText={(text) => {this.addTodo(text)}}
/>
<ScrollView>
{this.state.todos.map(todo=>(
<Todo
onToggle={()=>(this.toggle(todo.id))}
onDelete={()=>(this.removeTodo(todo.id))}
todo={todo}
key={todo.id}
/>))}
</ScrollView>
</View>
)
}
}
const styles = StyleSheet.create({
container:{
flex:1,
flexDirection:'column',
height:50,
paddingTop:3*Constants.statusBarHeight,
}
})

Pure components defined as function will always re-render.
Convert the component to a class and prevent the re-render in shouldComponentUpdate() returning false.
The signature is shouldComponentUpdate(nextProps, nextState). Say you for not re-render is that the componet's params haven't changed:
shouldComponentUpdate(nextProps, nextState){
return !equals(nextProps, this.props);
}
i think that this example could help you as it's well explained with examples: https://reactjs.org/docs/faq-functions.html
after all it's about creating a class with methods inside and call it as usually in an OOP language.
Hope it helps you

Related

bad setstate() when redirecting user with react-navigation?

I'm coding an onboarding process that depends on a state. For each "page" the user completes the state increments and returns the next JSX page. At the last step, I'm navigating the user to a page called explore page.
Below is the bug I'm getting when I don't have the setTimeout method around the navigation.navigate function. The full error trace leads back to the navigate function.
This is a snippet of the error:
index.js:1 Warning: Cannot update a component (ForwardRef(BaseNavigationContainer)) while rendering a different component (OnboardingTemplate). To locate the bad setState() call inside OnboardingTemplate, follow the stack trace as described in https://facebook.me/setstate-in-render
Each step works fine and it returns the JSX but it seems like that because the view hasn't finished rendering it throws the above error while loading the explore page. The error occurs even though the explore page gets shown. I've tried with useEffect and everything I could think of but I can't figure it out. And it seems weird if setTimeout is the right solution.
Here is the code:
import React, {useEffect, useState} from 'react'
import {View,ScrollView,StyleSheet} from 'react-native'
import Button from '../ui/atoms/Button'
import { useNavigation } from '#react-navigation/native'
let currentStep : JSX.Element;
export default function OnboardingTemplate(){
const navigation : any = useNavigation()
const [loaded,setLoaded] = useState(false);
useEffect(() => {
setLoaded(true)
})
const [onboardingStep, setOnboardingStep]:any = useState(0);
if (onboardingStep == 0){
currentStep=(
<ScrollView>
<View style={styles.topDistance}>
<Button onPress={() => setOnboardingStep(1)}
style={styles.inputBtn} title="Fortsæt (1/2)"/>
</View>
</ScrollView>
)
} else if (onboardingStep == 1){
currentStep=(
<ScrollView>
<View style={styles.topDistanceSmall}>
<Button onPress={() => setOnboardingStep(2)}
style={styles.inputBtn} title="Opret profil"/>
</View>
</ScrollView>
)
} else if (loaded) {
navigation.navigate("explore");
}
return (currentStep) ? currentStep : null ;
}

use useState hook, however the setter function is undefined at runtime

I am using useState hook in my react-native project. I have a screen which renders my custom component named MyComponent. The setter function of state is called in MyComponent 's onSelected callback.
import React, {useState} from 'react';
import MyComponent from './components/MyComponent'
const MyScreen=()=> {
...
const {parts, setParts} = useState(initialParts);
return (<View>
<MyComponent onSelected={()=> {
...
setParts(newParts)
}}/>
</View>)
}
...
MyComponent looks like this, in the onPress callback of TouchableOpacity, it calls the passed in onSelected function:
const MyComponent= ({onSelected})=> {
...
return (<TouchableOpacity onPress={()=>{
onSelected();
...
}}>
...
</TouchableOpacity>)
}
When I run my app on iOS emulator, the screen shows, when I tap on MyComponent, I get error TypeError: setParts is not a function. (In setParts(newParts)), 'setParts' is undefined.
Why I get this error?
Your destructuring here seems wrong:
const {parts, setParts} = useState(initialParts);
Shouldn't be this:
const [parts, setParts] = useState(initialParts);
?
Hmmm, it seems like you have to read the React official documentation to know more about UseState.
here is fix to your code:
const MyScreen = () => {
const [parts, setParts] = useState(initialParts) // see this fix.
return (
<View>
<MyComponent
onSelected={() => {
setParts(newParts)
}}
/>
</View>
)
}
basically, it is in the form of array de-structuring instead of object like you wrote above.
learn more: https://reactjs.org/docs/hooks-state.html

React Native components seem to be sharing a state

I'm having an issue with React-native where I have a component TouchTimer which uses an AnimatedTimer component. This timer is supposed to start and stop when it is tapped, which it does, however all of the TouchTimer components I add to a page will start and stop whenever any of them are tapped, rather than only affecting the tapped component.
Below is a snippet of my component:
TouchTimer.tsx
export class TouchTimer extends React.Component<TouchTimerProps> {
state: {
...
paused: boolean,
}
constructor(props) {
super(props);
...
this.state = {
...
paused: true,
}
}
startStop() {
this.setState({paused: !this.state.paused});
}
render() {
const { time } = this.props;
return (
<TouchableHighlight onPress={() => this.startStop()}>
<View>
<AnimatedTimer
...
time={time}
pause={this.state.paused}
/>
<View style={styles.timeContainer}>
<Text style={styles.time}>{this.state.remaining}</Text>
</View>
</View>
</TouchableHighlight>
)
}
}
And here is a snippet of the screen containing these components:
Details.tsx
import { TouchTimer } from '../components/TouchTimer';
...
export class RecipeDetailsScreen extends React.Component<NavigationInjectedProps> {
...
{this.state.steps.map(step => (
<List.Item
key={step.id}
title={"Step " + step.index}
style={styles.step}
description={step.short_desc}
right={() => (step.time > 0 &&
<TouchTimer
time={step.time * 60000}
/>
)}
/>
)
}
I have tried wrapping the TouchTimer components in a View and changing the paused boolean to a prop, to no avail.
I have also tested to see if this issue appears when the components are not siblings, and when they are not produced as the result of a callback, and the issue still persists in both these cases.
If anybody has any advice or answers on how to make these timers independent I would very much appreciate it!
Curiously that component seems to be implemented with a global pauseFlag that applies to all component instances. See https://github.com/dalisalvador/react-native-animated-timer/blob/master/src/Components/AnimatedTimer.js#L34
So I don't think you're doing anything wrong here, this is a limitation of the library code that is coupling all instances of your timer to the same pauseFlag value.

Native Base Picker generating "Unsafe legacy lifecycles"

I created a very simple page in react native. However, I'm getting the warning:
Warning: Unsafe legacy lifecycles will not be called for components using new component APIs.
%s uses %s but also contains the following legacy lifecycles:%s%s%s
The above lifecycles should be removed. Learn more about this warning here:
https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html, Styled(PickerNB), getDerivedStateFromProps(), ,
UNSAFE_componentWillReceiveProps,
It is happening because the native-base Picker. If I remove the picker, I do not receive the warning.
...
class ChangeProperty extends Component {
constructor(props) {
super(props);
this.state = {
selectedProperty: '1'
};
}
componentDidMount() {
this.props.getProperties(); // It just loads a properties data from action component
}
onChangeProperty(value) {
this.setState({
selectedProperty: value
});
}
updatePropertyBTN = async () => {
await AsyncStorage.setItem('CurrentPropertyID', this.state.selectedProperty);
NavigationService.navigate('iRent');
}
...
<Picker
mode="dropdown"
iosHeader="Select Property"
placeholder="Property"
iosIcon={<Icon name="arrow-down" />}
selectedValue={this.state.selectedProperty}
textStyle={{ color: '#C0C0C0' }}
style={{ width: '100%' }}
onValueChange={(text) => this.onChangeProperty(text)}
>
{Object.keys(this.props.properties).map((key) => {
return (
<Picker.Item
label={this.props.properties[key]}
value={key}
key={key}
/>
);
})}
</Picker>
}
It is not causing any error in my code, but the warning message in the terminal is disturbing me because I do not know what is causing it.
Thanks
The warning is occurring because the NativeBase picker appears to be using legacy life cycle methods (eg componentWillReceiveProps like was mentioned in the warning) that are no longer supported by React - this has nothing to do with your code.
Ensure your NativeBase is updated to the latest package version and if it is you can raise an issue on their repo here

React Navigation: "Cannot Add a child that doesn't have a YogaNode to a parent without a measure function" when using Custom Navigators

I'm trying to make the UI for my app in the below picture:
My App's UI
I follow the instruction of React Navigation to make the Custom Navigator according to the UI but it doesn't work in Android. The red screen appears with the message "Cannot Add a child that doesn't have a YogaNode to a parent without a measure function". Here is my code:
import React, { Component } from 'react';
import { createStackNavigator } from 'react-navigation';
import TabAboutScreen from './TabAbout';
import TabMyLessonScreen from './TabMyLesson';
import TabTeacherScreen from './TabTeacher';
import { ScrollView, View, Text } from '../../../components';
import TabNavigator from './TabNavigator';
import TopBar from './TopBar';
import styles from './styles';
import CourseHeader from './CourseHeader';
import theme from '../../../theme';
import i18n from '../../../i18n';
export const CourseDetailStackNavigator = createStackNavigator({
TabAbout: TabAboutScreen,
TabMyLesson: TabMyLessonScreen,
TabTeacher: TabTeacherScreen,
}, {
headerMode: 'none',
initialRouteName: 'TabAbout',
});
export default class TabCourseDetail extends Component {
static router = CourseDetailStackNavigator.router;
constructor(props) {
super(props);
this._handleOnBackButtonPress = this._handleOnBackButtonPress.bind(this);
}
_handleOnBackButtonPress() {
// do something
}
render() {
return (
<View style={styles.container}>
<TopBar textButton={i18n.t('CMBack')} title={i18n.t('CDCourseDetail')} onPress={this._handleOnBackButtonPress} />
<ScrollView
style={styles.scrollContainer}
stickyHeaderIndices={[1]}
showsVerticalScrollIndicator={false}
alwaysBounceVertical={false}
>
<CourseHeader />
<TabNavigator />
<View style={styles.test}>
<CourseDetailStackNavigator navigation={this.props.navigation} />
</View>
</ScrollView>
</View>
);
}
}
My evironment: react-navigation: 2.12.1, react-native: 0.55.4
I found out that the problem was that I put inside component by following the document of react navigation. It works well in iOS but doesn't work in Android.
Have you ever faced this problem. I'm looking forward to your solutions. Best regard.
Make sure you have not left any commented code in the return method and also have not left any text (string) without Text tag of react native.