282

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?

David Schumann
  • 13,380
  • 9
  • 75
  • 96
Matt Huggins
  • 81,398
  • 36
  • 149
  • 218

6 Answers6

310

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.

jasonmerino
  • 3,220
  • 1
  • 21
  • 38
Valéry
  • 4,574
  • 1
  • 14
  • 25
  • So it sounds like it is called once when the React component is initialized and is receiving props. The props don't actually "change" once a component is created. Is that right? – Matt Huggins Jul 24 '14 at 23:49
  • 13
    The opposite. The [documentation](http://facebook.github.io/react/docs/component-specs.html#updating-componentwillreceiveprops) says: "Invoked when a component is receiving new props. This method is not called for the initial render." – Valéry Jul 25 '14 at 08:04
  • Thanks. This question came from an initial misunderstanding of React in that a component will be reused when rerendering the screen (or part of the screen). – Matt Huggins Aug 31 '14 at 16:06
  • So if a component has asynchronous data or any data that can change it should be state not props ? – landed Mar 11 '16 at 10:37
  • 1
    Yes. A component can listen to an event, and update its state each time the event fires. – Valéry Mar 11 '16 at 11:39
  • @Valéry, hi, although it is anti-pattern, just curious, what if we update the props in child, will it trigger the re-render in parent and also all the childs? – Sam YC Dec 01 '16 at 04:09
  • Worth to note that even when using something like redux and it's connect utility where you bind your model to props (and not to a state) it's more natural to do any modifications on the model through event dispatch, not through direct modification. Otherwise nothing in the layout won't updated. – kboom Jan 14 '17 at 18:00
  • There´s a good description of componentWillReceiveProps (and most other related stuff) here: https://facebook.github.io/react/docs/react-component.html#componentwillreceiveprops – Gunnar Forsgren - Mobimation Jan 26 '17 at 11:19
  • 18
    I come from the future: `componentWillReceiveProps` is outdated now: and replaced by a combination of `getDerivedStateFromProps` and `componentDidUpdate`. – bvdb Sep 21 '18 at 20:54
  • I would suggest reading this page: https://www.freecodecamp.org/news/how-to-update-a-components-prop-in-react-js-oh-yes-it-s-possible-f9d26f1c4c6d/ – Srijan Chaudhary Jan 14 '20 at 05:50
95

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.

Ali Adravi
  • 21,707
  • 9
  • 87
  • 85
  • 9
    @ali_adravi are those quotes copied from somewhere? If so, what is the reference? Or are those your words, and you just formatted them as quotes for emphasis? – Rob Bednark Sep 26 '18 at 21:14
  • @RobBednark I don't remember the exact source now, but sure that is true statement with a little bit modification in sentence from some book. – Ali Adravi Sep 26 '18 at 22:56
  • 1
    I found myself saying, "But I need both?!" I ended up refactoring some logic out of the child component and moved that into the parent that can get passed through as a property to the child component. It feels a bit wrong, but now it's functioning properly; this is like dependency injection on roids. It could get annoying if you had logic you always wanted the child component to express and didn't want to keep reimplementing that in the parent component wherever it was used. Surely there's more for me to learn about this scenario as well. – CTS_AE Nov 09 '20 at 11:46
30

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.

Joost Diepenmaat
  • 17,633
  • 3
  • 44
  • 53
7

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.

5

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,
  },
});
BenMorel
  • 34,448
  • 50
  • 182
  • 322
Abhishek Kumar
  • 2,136
  • 3
  • 24
  • 36
  • 6
    I'm thinking that initialize state with props is anti-pattern, should be avoided. here is the good link to read https://github.com/vasanthk/react-bits/blob/master/anti-patterns/01.props-in-initial-state.md. – tryHendri Oct 18 '18 at 08:13
0

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);
Zoe
  • 27,060
  • 21
  • 118
  • 148
ehacinom
  • 8,070
  • 7
  • 43
  • 65