Can I update a component's props in React.js? - javascript

After starting to work with React.js, it seems like props are intended to be static (passed in from the parent component), while state changes based upon events. However, I noticed in the docs a reference to componentWillReceiveProps, which specifically includes this example:
componentWillReceiveProps: function(nextProps) {
this.setState({
likesIncreasing: nextProps.likeCount > this.props.likeCount
});
}
This seems to imply that the properties CAN change on a component based upon the comparison of nextProps to this.props. What am I missing? How do props change, or am I mistaken about where this gets called?

A component cannot update its own props unless they are arrays or objects (having a component update its own props even if possible is an anti-pattern), but can update its state and the props of its children.
For instance, a Dashboard has a speed field in its state, and passes it to a Gauge child thats displays this speed. Its render method is just return <Gauge speed={this.state.speed} />. When the Dashboard calls this.setState({speed: this.state.speed + 1}), the Gauge is re-rendered with the new value for speed.
Just before this happens, Gauge's componentWillReceiveProps is called, so that the Gauge has a chance to compare the new value to the old one.

PROPS
A React component should use props to store information that can be
changed, but can only be changed by a different component.
STATE
A React component should use state to store information that the
component itself can change.
A good example is already provided by Valéry.

Props can change when a component's parent renders the component again with different properties. I think this is mostly an optimization so that no new component needs to be instantiated.

Much has changed with hooks, e.g. componentWillReceiveProps turned into useEffect+useRef (as shown in this other SO answer), but Props are still Read-Only, so only the caller method should update it.

Trick to update props if they are array :
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View,
Button
} from 'react-native';
class Counter extends Component {
constructor(props) {
super(props);
this.state = {
count: this.props.count
}
}
increment(){
console.log("this.props.count");
console.log(this.props.count);
let count = this.state.count
count.push("new element");
this.setState({ count: count})
}
render() {
return (
<View style={styles.container}>
<Text>{ this.state.count.length }</Text>
<Button
onPress={this.increment.bind(this)}
title={ "Increase" }
/>
</View>
);
}
}
Counter.defaultProps = {
count: []
}
export default Counter
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
instructions: {
textAlign: 'center',
color: '#333333',
marginBottom: 5,
},
});

If you use recompose, use mapProps to make new props derived from incoming props
Example:
import { compose, mapProps } from 'recompose';
const SomeComponent = ({ url, onComplete }) => (
{url ? (
<View />
) : null}
)
export default compose(
mapProps(({ url, storeUrl, history, ...props }) => ({
...props,
onClose: () => {
history.goBack();
},
url: url || storeUrl,
})),
)(SomeComponent);

Related

When creating a reusable component in React, how do I pass all other props to be accessible later when the component is being used?

I'm creating some custom components for my application and they essentially have some base styling done to them for light/dark modes. My goal is to be able to use those components with all their props later when the custom component is being used to stay flexible. How do I achieve this?
For example if I style a custom input component and use it, I want to be able to tap into the secureTextEntry prop when needed. Here is an example of what I have right now for my CustomText. I want to be able to style this further when needed.
import { Text, useColorScheme } from 'react-native';
import React from 'react';
type CustomTextProps = {
text: string;
};
const CustomText = ({ text }: CustomTextProps) => {
const isDarkMode = useColorScheme() === 'dark';
return <Text style={{ color: isDarkMode ? '#fff' : '#000' }}>{text}</Text>;
};
export default CustomText;
react-native expose interfaces for each component.
so you need to extend your interface with TextProps:
import { Text, TextProps } from 'react-native';
interface CustomTextProps extends TextProps {
text: string;
};
By extending those interfaces (e.g. TextProps) in CustomTextProps we can have all text element props passed to this component.
Instead of having to declare each one we can just use a spread attribute ...rest
const CustomText = ({ text, ...rest }: CustomTextProps) => {
const isDarkMode = useColorScheme() === 'dark';
return <Text style={{ color: isDarkMode ? '#fff' : '#000' }} {...rest}>{text}</Text>;
};
export default CustomText;
This sounds like a job for React Context which acts as a store for global state that you can access using useContext hook

Create Generic Custom Component React

This is react-native question but similar concepts can be applied to react.
I want to create a CustomView in react-native. I am using typescript.
So far, I have:
const styles = StyleSheet.create({
container: {
backgroundColor: '#ffffff',
borderRadius: 10,
}
});
type CustomViewProps= {
width: number,
height: number,
marginTop?: string | number,
}
const CustomView = ({ width, height, marginTop }: CustomViewProps) => (
<View style={[styles.container, { height, width, marginTop }]} />
);
This is ok so far because only 3 props are being used: width, height and marginTop.
However, this is not reusable and it can become verbose if I need to add many more props.
So, the question is: How can I make CustomView receive any props as a native component View could receive?
My guess is I should delete CustomViewProps. Then, I should make the props inherit from the same type that the native component View does. However, I am struggling with it.
Since you are creating CustomViewProps, I assume that you want to add some specific behaviours to your native component above the already written behaviours of that component.
Let's create an example.
I want to create a button with some specific behaviours but i want it to behave, in other cases, like a normal TouchableOpacity component. For example, i want to add a "loading" state which will show a loader inside instead of its content.
So the logic is: create your custom props and merge you custom props with native's default props
import React, { FC, ReactElement } from 'react'
import { ActivityIndicator, TouchableOpacity, TouchableOpacityProps, StyleSheet } from 'react-native'
type MyProps = {
loading?: boolean
children: ReactElement
}
const MyButton: FC<MyProps & TouchableOpacityProps> = (props: MyProps & TouchableOpacityProps) => (
<TouchableOpacity {...props} disabled={props.disabled || props.loading} style={[styles.button, props.style]}>
{props.loading ? <ActivityIndicator /> : props.children}
</TouchableOpacity>
)
const styles = StyleSheet.create({
button: {
backgroundColor: 'yellow',
borderColor: 'black',
borderWidth: 1,
borderRadius: 10,
padding: 10
},
})
export default MyButton
The loading prop will be responsible for both content of the button or is disabled prop. The TouchableOpacity component will receive every compatible prop (autosuggest will be enabled because you have assigned the TouchableOpacityProps). The styles.button will behave like default style but will be overwritten if you specify something different in your style prop. That's it!

Where to call AsyncStorage.getItem( ) function to load saved data in react-native?

I have saved one data inside one class of my react-native project. With saving data I start a new screen. In that screen I am retrieving that saved data. For that purpose, I call AsyncStorage.getItem('token') function inside the render function.
Here is the code for that class-
import React from 'react';
import { StyleSheet, Text, View, Image, AsyncStorage } from 'react-native';
import {Icon, Button, Container, Header, Content, Left} from 'native-base';
import CustomHeader from './CustomHeader';
class NoteMeHome extends React.Component {
state = {
text:'',
storedValue:'',
getValue: ''
};
static navigationOptions = ({navigation}) => ({
title: "Home",
headerLeft: <Icon name="ios-menu" style={{paddingLeft:10}}
onPress={()=>navigation.navigate('DrawerOpen')}/>,
drawerIcon:
<Image source={require('../assets/icon.png')}
style={styles.icon}
/>
})
render() {
const {storedValue} = this.state;
AsyncStorage.getItem('token').then(value =>
//AsyncStorage returns a promise so adding a callback to get the value
this.setState({ getValue: value })
//Setting the value in Text
);
return(
<Container>
<CustomHeader
title="Home"
drawerOpen={()=>this.props.navigation.navigate('DrawerOpen')}
/>
<Content contentContainerStyle={{flex:1, alignItems:'center',
justifyContent:'center', padding:10}}>
<Button full onPress={()=> this.props.navigation.navigate('Settings')}>
<Text style={{color:'white'}}>{storedValue}</Text>
</Button>
<Text>{this.state.getValue}</Text>
</Content>
</Container>
)
}
}
const styles = StyleSheet.create({
icon:{
height: 24,
width: 24
}
})
export default NoteMeHome;
After that, while running the project, in the above mentioned class, when I click any of my drawer items to go to another screen, it shows the following error in the console-
wanrning: can't call setState(or forceUpdate) on an unmounted
component. This is no-op, but it indicates a memory leak in your
application. To fix, cancel all subscriptions and asyncshronous tasks
in the componentWillUnmount method.
I guess something goes wrong with AsyncStorage.getItem('token') function calling because if I remove the function it doesn't show any warning.
So, it would be very nice if someone helps me to know where should I call the following code-
AsyncStorage.getItem('token').then(value =>
//AsyncStorage returns a promise so adding a callback to get the value
this.setState({ getValue: value })
//Setting the value in Text
);
to remove the warning ?
Asif,
Here is what I had in mind :
import React from 'react';
import { StyleSheet, Text, View, Image, AsyncStorage } from 'react-native';
import {Icon, Button, Container, Header, Content, Left} from 'native-base';
import CustomHeader from './CustomHeader';
class NoteMeHome extends React.PureComponent {
state = {
text:'',
storedValue:'',
getValue: ''
};
static navigationOptions = ({navigation}) => ({
title: "Home",
headerLeft: <Icon name="ios-menu" style={{paddingLeft:10}}
onPress={()=>navigation.navigate('DrawerOpen')}/>,
drawerIcon:
<Image source={require('../assets/icon.png')}
style={styles.icon}
/>
});
componentDidMount() {
const token = await AsyncStorage.getItem('token');
this.setState({ getValue: token });
}
render() {
const {storedValue, getValue} = this.state;
return(
<Container>
<CustomHeader
title="Home"
drawerOpen={()=>this.props.navigation.navigate('DrawerOpen')}
/>
<Content contentContainerStyle={{flex:1, alignItems:'center',
justifyContent:'center', padding:10}}>
<Button full onPress={()=> this.props.navigation.navigate('Settings')}>
<Text style={{color:'white'}}>{storedValue}</Text>
</Button>
<Text>{getValue}</Text>
</Content>
</Container>
)
}
}
const styles = StyleSheet.create({
icon:{
height: 24,
width: 24
}
})
export default NoteMeHome;
I don't know if in your case you should actually try to handle the update of the component since you don't have any props.
Regards,
IMO you should use componentDidMount for anything that you want to do in the beginning of a screen. To use AsyncStorage remember that is an asynchronous function so you have to wait for the function to complete so you can get the value.
For more information about react native's components please see this
For more information about 'waiting' for AsyncStorage using async please see this examples
In react component render() should always remain pure. One should never set state in render function, it a very bad pracice, in a simple component it might work fine. It only works because of the asynchronicity.
You should use componentDidMount lifecycle method to fetch data from local storage.
componentDidMount() {
const token = await AsyncStorage.getItem('token');
this.setState({ getValue: token });
}

React Native / Redux - Can only update a mounted or mounting component

I am just fetching the items from server and displaying them in the list component. The component structure is like
ServiceScreen(parent) -> ServicesList(child- used inside ServiceScreen) -> ServiceItem (child used inside ServicesList)
Every thing works well and it displays the services fetched but it gives me the following warning
Warning: Can only update a mounted or mounting component. This usually
means you called setState, replaceState, or forceUpdate on an
unmounted component. This is a no-op. Please check the code for the
YellowBox component.
Code is as follows
ServiceScreen.js
constructor(props) {
super(props);
this.props.actions.fetchServices();
}
render() {
const { isFetching } = this.props;
return (
<View style={styles.container}>
<ServicesList services={this.props.services} navigation={this.props.navigation} />
</View>
);
}
ServicesList.js
render() {
return (
<View style={{ flex: 1 }}>
<FlatList
data={this.props.services}
renderItem={
({ item }) =>
<ServiceItem navigation={this.props.navigation} item={item} />
}
/>
</View>
);
}
ServiceItem.js
render() {
const { item } = this.props;
return (
<TouchableOpacity onPress={this.singleService}>
<Text>{item.service_name}</Text>
</TouchableOpacity>
);
}
As I am using redux for state management, I have mapped the services
state to my ServiceScreen component. And I am passing down it to child
component.
You should dispatch the action in componentDidMount. The constructor is called before the component is mounted, which is why you're seeing the error.
Here's a diagram with the react lifecycle. You should only call methods that change state in the "commit" phase.
This error is firing exactly when said in the yellowBox. Somewhere you trying to update already unmounted component.
I handled this issue by using temporary variable componentIsMounted: bool and wrapping setState method:
class SomeClass extends Component {
constructor(props) {
this._isComponentMounted = false;
}
componentDidMount() {
this._isComponentMounted = true;
}
componentWillUnmount() {
this._isComponentMounted = false;
}
setState(...args) {
if (!this._isComponentMounted) {
return;
}
super.setState(...args);
}
}
Also, you should remember that you should not call state updates before the component get mounted (it`s not an alternative way - just an addition).

React Native cloned child component is not being rerendered as props change

I'm creating a custom Navigator component. I need to provide Navigator's stack components a navigator prop to allow them to push and pop scenes like this:
this.props.navigator.push(<Product id='4815'>)
this.props.navigator.pop()
In order to achieve this result, inside my Navigator's class, I've used React.cloneElement():
class Navigator extends Component {
constructor(props) {
super(props)
this.state = { stack: [this._addNavigator(props.children)] }
}
_addNavigator(scene) {
return React.cloneElement(scene, {
navigator: {
push: this.push,
pop: this.pop,
popToRoot: this.popToRoot
}
})
}
push(scene) {
this.setState({
stack: [...this.state.stack, this._addNavigator(scene)]
})
}
...
}
Everything works just fine, except for a particular scenario.
class App extends Component {
constructor(props) {
super(props)
this.state = { toggle: false }
}
render() {
return (
<View>
<TouchableOpacity onPress={() => {
this.setState({ toggle: !this.state.toggle })
}}>
<Text>Toggle</Text>
</TouchableOpacity>
<Navigator>
<SampleScene backgroundColor={this.state.toggle ? 'green' : 'black'} />
</Navigator>
</View>
)
}
When I pass some mutable prop to the Navigator children, as soon as the prop changes, the child component does not rerender. In the example above, SampleScene's backgroundColor stays black (because App class initial state for toggle is set to false), despite the user pressing the Toggle button. It seems like the SampleScene's render() method is called just once. How could I troubleshoot this behaviour?
Problem solved. Inside Navigator, I had to intercept new props via componentWillReceiveProps. Setting the stack to newProps' children via setState method made the Navigator rerender properly.

Categories