React Fundamentals: Props vs State

Photo by Jason Leung

What's the difference between props and state in React?

Let's compare props and state. Here's a definition of each:

"props" (short for "properties") is an object of arbitrary inputs a React function component accepts as the first argument.

"state" is data that changes over the lifetime of a specific instance of a React component.

Let's dive into each.

Props

Think of props as arguments to a function. React components are functions which return JSX (or more generally something that's renderable like React elements, null , a string, etc.). Typically when you have a piece of code that you would like to reuse, you can place that code into a function and any dynamic values that code used before can be accepted as arguments (for example const five = 2 + 3 could be extracted to a function and accept arguments like so const five = add(2, 3) ).

The same is true of a piece of JSX, except instead of calling it like a normal function ( add(2, 3) ) you use JSX syntax ( <Add n1={2} n2={3} /> ). The "attributes" supplied in the JSX are what are called props and they are placed together in a single object and passed to the Add component function as the first argument like so:

1 function Add ( props ) { 2 return ( 3 < div > 4 { props . n1 } + { props . n2 } = { props . n1 + props . n2 } 5 </ div > 6 ) 7 }

If I were to use this like so:

1 < Add n1 = { 2 } n2 = { 3 } />

Here's how that would be rendered:

2 + 3 = 5

NOTE: Props can be anything. In our example they're numbers, but they can also be (and often are) strings, arrays, objects, functions, etc.

Let's say we want to default the n2 to 0 in the event someone doesn't provide it (like <Add n1={2} /> ). One limit to props is that you're not allowed to change them. So you couldn't do something like this:

1 function Add ( props ) { 2 if ( typeof props . n2 === 'undefined' ) { 3 props . n2 = 0 4 } 5 return ( 6 < div > 7 { props . n1 } + { props . n2 } = { props . n1 + props . n2 } 8 </ div > 9 ) 10 }

If we try to do this, we'll get the following error:

1 TypeError: Cannot add property n2, object is not extensible

This is simple enough to solve though:

1 function Add ( props ) { 2 let n2 = props . n2 3 if ( typeof n2 === 'undefined' ) { 4 n2 = 0 5 } 6 return ( 7 < div > 8 { props . n1 } + { n2 } = { props . n1 + n2 } 9 </ div > 10 ) 11 }

Or, often, you'll find people use destructuring syntax with default values as well (this is my personal preference):

1 function Add ( { n1 , n2 = 0 } ) { 2 return ( 3 < div > 4 { n1 } + { n2 } = { n1 + n2 } 5 </ div > 6 ) 7 }

This is awesome, but what if I want to dynamically change the props value? Let's say I wanted to build something like this:

2 + = 2

Without state, here's how I might try to accomplish that:

1 function AddWithInput ( props ) { 2 function handleInputChange ( event ) { 3 const input = event . target 4 const newN2 = Number ( input . value ) 5 props . n2 = newN2 6 } 7 return ( 8 < div > 9 { props . n1 } + { ' ' } 10 < input type = " number " value = { props . n2 } onChange = { handleInputChange } /> = { ' ' } 11 { props . n1 + props . n2 } 12 </ div > 13 ) 14 }

However, this will not work for two reasons:

React doesn't know that we've updated the n2 value of our props object, so it wont update the DOM when we change props.n2 , so we wont see our changes anyway We'll get the TypeError warning as before

This is where state comes in.

State

State is data that changes over time, and that's perfect for our situation:

1 function AddWithInput ( props ) { 2 const [ n2 , setN2 ] = React . useState ( 0 ) 3 4 function handleInputChange ( event ) { 5 const input = event . target 6 const newN2 = Number ( input . value ) 7 setN2 ( newN2 ) 8 } 9 10 return ( 11 < div > 12 { props . n1 } + { ' ' } 13 < input type = " number " value = { n2 } onChange = { handleInputChange } /> = { ' ' } 14 { props . n1 + n2 } 15 </ div > 16 ) 17 }

That will work, and this is precisely what React state is intended to be used for. It's intended to track data values over the lifetime of the component (so long as the component exists on the page).

However, users of the AddWithInput component can no longer set the initial value of n2 anymore. With the way that component is implemented currently, it's not referencing props.n2 at all. But we can make this work by using props when we initialize our state:

1 function AddWithInput ( props ) { 2 const [ n2 , setN2 ] = React . useState ( props . n2 ) 3 4 5 }

Now if someone were to do this: <AddWithInput n1={2} n2={3} /> then the result would look like this (notice that the initial input value is 3 ):

2 + = 5

So our props are "arguments" or "inputs" that we can pass to a component, and state is something that is managed within the component and can change over time.

Let me just clean this component up a little bit and I'll explain my changes:

1 function AddWithInput ( { n1 , initialN2 = 0 } ) { 2 const [ n2 , setN2 ] = React . useState ( initialN2 ) 3 4 function handleInputChange ( event ) { 5 const input = event . target 6 const newN2 = Number ( input . value ) 7 setN2 ( newN2 ) 8 } 9 10 return ( 11 < div > 12 { n1 } + < input type = " number " value = { n2 } onChange = { handleInputChange } /> = { ' ' } 13 { n1 + n2 } 14 </ div > 15 ) 16 }

I changed to destructuring with defaults for the props, and I changed the prop from n2 to initialN2 . When I'm using a prop value to initialize a state value, I typically like to give it the prefix initial to communicate that changes in that prop will not be taken into account. If that's what you want, then you'll want to Lift State Up.

Conclusion

I hope this helps clear up the difference between props and state in React for you. It's a foundational concept. Go ahead and test yourself on this little app below. Where's the state, where are the props?

I hope that's helpful! Good luck!