Learning React starts with several concepts developers run into – JSX syntax, props, and state. State is a familiar concept for developers, while JSX syntax and props confuse new people in React because they are never mentioned in any other web frameworks. JSX syntax is optional and you can develop apps in React without that syntax (however, JSX makes the development process much easier after learning it). On the other hand, props and state are one of the most important topics when learning React. Props provide communication between components while state expands the possibilities of your app beyond the display of static content.
First, we refer to the state and props definitions, then look at two different types of components: stateful and stateless. People who are in search of a fast answer can jump straight into a brief comparison abstract and look through the table with briefing memos about the differences between state and props.
Enjoy reading!
Props and state definition
Props are a JavaScript object that React components receive as an arbitrary input to produce a React element. They provide a data flow between the components. To pass the data (props) from one component to another as a parameter:
For a class component you need to define the custom HTML attributes to which you assign your data and then pass it with special React JSX syntax:
import React, { Component } from 'react';
class App extends Component {
render() {
const greeting = 'Welcome to React';
return (
<div>
<Greeting greeting={greeting} />
</div>
);
}
}
class Greeting extends Component {
render() {
return <h1>{this.props.greeting}</h1>;
}
}
export default App;
To receive props class components need to use JavaScript keyword this
.
For a functional component props are passed as an argument to a function:
import React, { Component } from 'react';
class App extends Component {
render() {
const greeting = 'Welcome to React';
return (
<div>
<Greeting greeting={greeting} />
</div>
);
}
}
const Greeting = props => <h1>{props.greeting}</h1>; // here an arrow function receives props with the name greetings
export default App;
In our example, our data was a string variable. But props can be anything: integers, objects, arrays, and even React components.
State:
State is a JavaScript object which contains data that influence how the component looks at a certain point in time. The second part is what makes the state different compared to props. State is just a snapshot of the app in a time. Every user interaction with your app may lead to changes in the underlying state and in the whole UI as a result. State changes over the lifetime of a React component. Examples of state:
For a class component you need to call the class constructor inside the React component:
import React, { Component } from 'react';
class Button extends Component {
constructor(props) {
super(props);
this.state = { counter: 1 };
}
render() {
return (
<button>{this.state.counter}</button>
);
}
}
export default Button;
For a functional component you need to use useState Hook:
import React from 'react';
function Counter() {
const [count, setCount] = React.useState(1);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
export default Counter;
State breathes life into your application and is a thing that makes your application dynamic and interactive. State could be Boolean, integers, strings, or complex JavaScript objects.
Stateful and stateless components
Stateless Component may contain only props, no state. Such a component can be associated with a function: it receives input ( ‘props’ object) and returns the result (React element). Stateless components are used when you want to represent the props and the component doesn’t need to be interactive. They are easy to use and easy to test.
Stateful Component may contain props but has to have state. A stateful component owns its state and can change it. When the component changes state, it re-renders. Stateful components help when the app needs to respond to user input and actions. They provide a dynamic user interface through client-server communication and help to create interactive pages. Traditionally functional components are stateless, while class components feature manipulations with state. However, it has changed with the introduction of Hooks
for functional components. State was one of the key advantages of class components, but nowadays Hooks added state
management and lifecycle methods
to functional components, so they are also can be called stateful components. You can read more about class and functional components in this article.
Brief comparison
Let’s begin with facts that are common both for props and state.
- React components re-render if props or state have changed. Any update from anywhere in the code triggers an automatic re-render of the appropriate part of the User Interface.
- Props and state are JS objects, which means they both contain as many properties and methods as we need.
- The same combination of props and state must produce the same output. This type of component is deterministic.
Here is a brief comparison between state and props.
State | Props |
---|---|
Should have an initial value. The initial value can be got from a parent component | Passed from a parent component. May be empty. We can set the default value for props if it is empty |
Read and write | Read-only |
Only the component that owns state can change it. State is private | The component can’t change incoming props. Only a parent is allowed to change props of the child component. |
Make the component interactive for users | Make non-interactive components |
Is also responsible for fetching remote data | Just display the incoming data |
We can pass state as props to child components within the render method of the parent component | Pass information from component to component |
Requires in higher-order components | Can provide the same functionality as higher-order components without using state and with fewer lines of code |
State vs. props – detailed comparison
State has to have the initial value, while props can be empty
State can’t exist without the initial value. When the component becomes stateful you need to declare the first value of state directly.
In class components using constructor()
:
class CalmDown extends React.Component {
constructor(props) {
super(props);
this.state = {
currentState: "not-panic",
}
}
}
In functional component we set the initial value via Hook useState()
import { useState } from 'react';
function TimeToPanic() {
// Declare a new state variable, which we'll call "mood"
const [mood, changeMood] = useState(‘calm’); // we declare a new state variable “mood” with the initial value equal to 0
return (
<div>
<p>You feel {mood}</p>
<button onClick={() => changeMood(mood=’the panic’)}>
Click me
</button>
</div>
);
}
In the example above changeMood
is a method that allows us to update the mood’s state. The component can get the initial state from the parent component. The initial state for props is not a necessary staff. We can pass an empty value or set the default value when the props are empty.
function CatComponent(props) {
return <div>{props.catName} Cat, Eye Color: {props.eyeColor}, Age: {props.age}</div>
}
CatComponent.defaultProps = {
catName: "Sandy",
eyeColor: "deep blue",
age: "120"
}
const cat = <CatComponent catName="Milk"/>
CatComponent renders the following string: “Milk Cat, Eye Color: deep blue, Age: 120”. Since we pass empty values for props attributes eyeColor and age the component uses the default values for them, while the attribute catName returns the value we have previously assigned. If CatComponent is called without the default values, it will simply render “Milk Cat, Eye Color: , Age:”
Both props and state initial values received from parents override default values defined inside a Component.
Props are immutable, while state is mutable
One significant limit to props is that they are read-only from inside the component. There is no way in React for a component to set a new value for its incoming props. That brings us to the concept of pure components that don’t change their inputs and return the same result for the same props. Trying to change the props will throw an error, so the following block of code will not work:
function Add(props) {
if (typeof props.n2 === 'undefined') {
props.n2 = 0
}
return (
<div>
{props.n1} + {props.n2} = {props.n1 + props.n2}
</div>
)
} // TypeError: Cannot add property n2, object is not extensible
That is totally consistent with the role props play: they just pass data from component to component. Although a component is not allowed to change its props, it is responsible for the props of its child components down the component tree.
On the other hand, state is mutable. A stateful component changes its state every time users interact with the app. Furthermore, a stateful component is responsible for management only its own state and has no impact on child components (a parent component can only set the initial state of its children components, we discuss it further below). We can say that state of every component is private. There are some rules on how to use and modify state correctly:
- No direct modification: command
this.state.name = ‘me’
; is allowed only for the initial initialization of state, inrender()
method we need to usesetState()
- State updates may be asynchronous
setState()
callbacks all happen together at the end of the state update phase in React, not immediately after that particular update happens. That may lead to confusion.
The role state and props play
Here we come to the difference in concepts of these two objects.
The role of state:
- Make the components interactive
- Provide users with an opportunity to modify the state
- Track data values over the lifetime of the component
- Update the UI when the state has been changed
- React when the time passes (interval or timeout)
The role of props:
- To display static non-interactive components and data
- To pass data from a component to component
An additional role of state that should be mentioned is fetching remote data. The component fetch data after it is mounted, so state allows us to update the component once the data from a third-party resource comes.
State can be passed as props to child components
The state of one component can be passed as a prop to child components. There is no difference for the child component whether the incoming prop is state of the parent components or a simple prop. The component gets the data and uses it in rendering – that is all. If the incoming props change (doesn’t matter because of the changes in parent component state or because the props were changed) the child component re-renders.So props and state are interrelated. We can pass state as props within the rendermethod of the parent component:
class CalmDown extends React.Component {
constructor(props) {
super(props);
this.state = {
currentState: "do not panic",
}
}
render() {
return <div>Just remember: {this.state.currentState}</div>;
<MyChild mood = {this.state.currentState}/>
}
}
class MyChild extends React.Component {
return <h1>Hello! I {this.props.mood}</h1>;
}
}
}
The parent’s state value of currentState
becomes the child’s this.props.mood
. For MyChild
component mood props is immutable. If we need to change the props in the child component, we need to change the state in the parent component:
this.setState({ currentState: ‘PANIC!’})
And then React passes the new value to MyChild
component and re-render both elements.
If we want to change the name of the prop, we should create a child event and parent callback. Guess that we have an event called onNameChanged
in the child that passes newMood
as an argument to the event callback. Let add a callback handler and the event handler to the parent:
MyChild mood ={this.state.currentState} onNameChanged={this.handleMood} />
handleMood: function(newMood) {
this.setState({ currentState: newMood });
}
And then we set a new name for child prop. We didn’t bend the rule that data should go from parent components to child components (downwards). The mood prop is managed by the parent CalmDown
component, only the parent can change the prop for child components and pass it once again. Basically, it is the way how React components communicate with each other and how props are passed. They go downwards from parent to child components. And the state of the parent can become the prop of the child.
Usage of state and props in higher-order components
To build a higher-order class component we have to use state, while the same component in terms of functionality that is built with functional components requires props only.
Higher-order components are functions that take a component as an input and transform it not into UI, but another component.
Let’s build a login higher-order class component with a form where we input email and password and the component returns the answer whether such a user is registered in the app or not.
import React from 'react';
import { withRouter } from 'react-router-dom';
import { connect } from 'react-redux';
class Example extends React.Component {
static propTypes = {
dispatch: PropTypes.func.isRequired,
};
constructor(props) {
super(props);
this.state = {
email: 'admin@flatlogic.com',
password: 'password',
};
this.doLogin = this.doLogin.bind(this);
}
doLogin(e) {
e.preventDefault();
this.props.dispatch(loginUser({ email: this.state.email, password: this.state.password }));
}
isLogin () {
return this.props.isAuthenticated
}
render() {
return (
<form onSubmit={this.doLogin}>
<button type="submit">Login</button>
</form>
);
}
}
function mapStateToProps(state) {
return {
isAuthenticated: state.auth.isAuthenticated,
};
}
export default withRouter(connect(mapStateToProps)(Login));
functional components:
We use state to take user input data and only after that step can we call the higher-order function isAuthenticated
with state as an argument.
Let’s examine exactly the same component in terms of functionality, but built using props only:
import React from "react";
import { useDispatch, useSelector } from "react-redux";
const Login = () => {
const dispatch = useDispatch(); const isAuth = useSelector(store => store.auth.isAuth)
const doLogin = () => {
if (isAuth) {
// some async logic
}
}
return (
<form onSubmit={() => dispatch(doLogin())}>
<button type="submit">Login</button>
</form>
);
};
export default Login;
Our new component has fewer lines of code but provides the same functionality. Thus, to make a login form we have to build higher-order functions with state in class components. In functional components we use Hook useDispatch()
and constants (props).
So when to use state and props?
Hope that we shed some light on the difference between the two React concepts. However, there is one more question left:
When are we supposed to use props and when resort to state?
Some basic rules:
- Components without state are preferable. State increases the common complexity of the app, makes the render result less predictable and testing less steady. Although you can’t avoid using state in the app because state is the basis for building interactive apps in React, just make sure that your app has as many Stateful components as possible.
- A wide-spread pattern of building apps is to make several stateful components on top of the hierarchy and create stateless components under them, passing all necessary information and staff via props from parents to child components. The point here is to isolate the logic of interaction on the top of the app in parent stateful components and to transfer the responsibility of rendering data to child stateless components.
When we should use stateless components:
- When we need to display the data
- When we build a non-interactive element in the UI
- When we have a stateful component above, we should check whether it’s possible to use state from above rather than introduce a new local state
When we should use stateful components:
- When the component is supposed to accept user input
- When we build an interactive UI element
- When the component needs to work with data that it can’t get as props from parent components
- When we work with fetching data
Thanks for reading!