Why setState re-renders even if state stays the same? - reactjs

class App extends React.Component {
state = {
index: 0,
}
render() {
alert('rendered');
return(
<div>
<h1>{this.state.index}</h1>
<button onClick={() => this.setState({index: 0})}>test</button>
</div>
);
}
}
I know we can define a shouldComponentUpdate to check if this.state === nextState, but why don't React check this by default? Same goes for props.

React defaults to always re-rendering because then React by default is always correct regardless of what you have in your state.
Iterating the keys of this.state and using === works in your example, but with complex state it's not possible to use simple equality. Take the following example, the indices Array is the same Array so === would return true:
class App extends React.Component {
state = {
indices: [0],
}
render() {
alert('rendered');
return(
<div>
<h1>{this.state.index}</h1>
<button onClick={() => {
this.state.indices.push(2);
this.setState({indices: this.state.indices})
}>test</button>
</div>
);
}
}
State can have Arrays, Objects, etc., and React does not try to infer what "equality" means for them. If you want to optimize your components, you can implement shouldComponentUpdate because you know what "equality" means for your own state.
If you are sure your component's state & props can be compared using simple equality, like in your example, you can extend React.PureComponent to get the behavior you describe.

Related

Proper use of React getDerivedStateFromProps

In this example
https://codepen.io/ismail-codar/pen/QrXJgE?editors=1011
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
static getDerivedStateFromProps(nextProps, prevState) {
console.log("nextProps", nextProps, "\nprevState", prevState)
if(nextProps.count !== prevState.count)
return {count: nextProps.count};
else
return null;
}
handleIncrease(e) {
this.setState({count: this.state.count + 1})
}
handleDecrease(e) {
this.setState({count: this.state.count - 1})
}
render() {
return <div>
<button onClick={this.handleIncrease.bind(this)}>+</button>
{this.state.count}
<button onClick={this.handleDecrease.bind(this)}>-</button>
</div>;
}
}
class Main extends React.Component {
constructor(props) {
super(props);
this.state = { initialCount: 1 };
}
handleChange(e) {
this.setState({initialCount: e.target.value})
}
render() {
return <div>
<Counter count={this.state.initialCount} />
<hr/>
Change initial:<input type="number" onChange={this.handleChange.bind(this)} value={this.state.initialCount} />
</div>
}
}
ReactDOM.render(
<Main/>,
document.getElementById("root")
);
Expected:
Clicking + / - buttons and textbox change must be update count
Currently:
Main component stores initialCount in own state and passes initial count to child Counter Component.
If handleChange triggered from textbox and initialCount is updated also child Counter component is updated correctly because getDerivedStateFromProps static method provides this.
But changing count value in Counter component with updating local state via handleIncrease and handleDecrease methods it prolematic.
Problem is getDerivedStateFromProps re-trigger this time and resets count value. But I did not expect this because Counter component local state updating parent Main component is not updating. UNSAFE_componentWillReceiveProps is working this way.
Summary my getDerivedStateFromProps usage is incorrect or there is another solution for my scenario.
This version https://codepen.io/ismail-codar/pen/gzVZqm?editors=1011 is good with componentWillReceiveProps
Trying to "sync" state to props like you do is extremely error-prone and leads to buggy applications.
In fact even your example with componentWillReceiveProps has a bug in it.
If you re-render the parent component more often, you will lose user input.
Here is a demo of the bug.
Increment counter, then click “demonstrate bug” and it will blow away the counter. But that button’s setState should have been completely unrelated.
This shows why trying to sync state to props is a bad idea and should be avoided.
Instead, try one of the following approaches:
You can make your component fully "controlled" by parent props and remove the local state.
Or, you can make your component fully “uncontrolled”, and reset the child state from the parent when necessary by giving the child a different key.
Both of these approaches are described in this article on the React blog about avoiding deriving state. The blog post includes detailed examples with demos so I strongly recommend to check it out.
I'm not sure if I understood correctly but if you want to use the prop as a "seed" for the initial value to do it in the constructor and you don't even need getDerivedStateFromProps. You actually don't need to duplicate state:
class Counter extends React.Component {
render() {
return <div>
<button onClick={this.props.handleIncrease}>+</button>
{this.props.count}
<button onClick={this.props.handleDecrease}>-</button>
</div>;
}
}
class Main extends React.Component {
constructor(props) {
super(props);
this.state = { count: 1 };
}
handleIncrease() {
this.setState(prevState => ({count: prevState.count + 1}))
}
handleDecrease() {
this.setState(prevState => ({count: prevState.count - 1}))
}
render() {
return (
<div>
<Counter count={this.state.count} />
<hr/>
Change initial:
<input
type="number"
handleIncrease={this.handleIncrease.bind(this)}
handleDecrease={this.handleDecrease.bind(this)}
count={this.state.count}
/>
</div>
)
}
}

React dumb component with UI state

I want to build a select input component with React.
The select should be dumb component as it's only a UI Component,
but it also have it's own state (Whether to show the options list, or not)
How should I manage this state?
return (
const Select = (props) => {
<div>
<label>{placeholder}</label>
{/*some toggle state*/ && <div>props.children</div>}
</div>
}
)
thanks!
You should not get too confused by the fact that "it's only a UI component". Anything that has an internal state should be a class.
Your code, a dropdown, is my go-to example of when you should use internal state.
Manage your state with setState().
Now your component is stateless, but you need a stateful.
For example:
class Select extends React.Component {
constructor(props) {
super(props);
this.state = {value: '', toggle: false};
}
render() {
return (
<div>
<label>{placeholder}</label>
{this.state.toggle && <div>this.props.children</div>}
</div>
);
}
}
And you should change state with setState function.
For more information, check this article.
According to your code, what you are rendering is a stateless component, so it will not have any state.
What you can do is pass the state from the parent to this component like so:
constructor(props) {
this.state = { showDumbComponent:true }
}
render() {
<DumbComponent show={this.state.showDumbComponent} />
}

Tabs only mount Tab content on the first time it becomes active

I would like to load the tab content only on the first time it becomes active, after that the content stays in the DOM
This is what I have
<Tabs defaultActiveKey={1} animation={false} id="my-tabs" mountOnEnter unmountOnExit>
<Tab eventKey={1}>
<div>content1</div>
</Tab>
<Tab eventKey={2}>
<div>content1</div>
</Tab>
</Tabs>
it works fine, but there is a lag between switching tabs, since the content I have is quite large and I would like to render it only once, on the first time the tab becomes active.
Is there a way to achieve that? I'm using react-bootstrap 0.30.10
UPDATE:
apparently mountOnEnter must be used with animation, otherwise it will not work as intended. I made the change and it works fine now
Old answer:
so I have come up with this wrapping component as follow
class TabsLazyLoad extends Component {
constructor(props) {
super(props);
this.state = this.getInitialState();
this.handleSelect = this.handleSelect.bind(this);
}
getInitialState() {
return {
key: this.props.key || this.props.defaultActiveKey,
rendered: [],
};
}
addRenderedTab(key) {
const newState = _.cloneDeep(this.state);
newState.rendered.push(key);
this.setState(newState);
}
handleSelect(key) {
this.setState({ key });
}
render() {
return (
<Tabs activeKey={this.state.key} onSelect={this.handleSelect} {...this.props}>
{_.map(this.props.children, (tabComponent) => {
if (_.includes(this.state.rendered, tabComponent.props.eventKey)) {
return tabComponent;
}
if (tabComponent.props.eventKey === this.state.key) {
this.addRenderedTab(this.state.key);
}
// if it's not rendered, return an empty tab
const emptyTab = _.cloneDeep(tabComponent);
emptyTab.props.children = null;
return emptyTab;
})}
</Tabs>
);
}
}
TabsLazyLoad.propTypes = Tabs.propTypes;
It seems to be working fine, but I reckon this is a bit hacky, but it's the best I can come up with for now.
It sounds like a good use case for the "Avoid Reconciliation" option that React provides.
Here's a link to the relevant section in the documentation.
Essentially, there's a lifecycle event called shouldComponentUpdate that defaults to true. When you change it to false, it tells React not to run the component through the standard Reconciliation process (i.e. the "diff" checks).
Like with any lifecycle method, you can create a conditional statement for it.
For a component that should be made completely static after its first render, this is really all you need:
class YourComponent extends React.Component {
...
shouldComponentUpdate() {
return false;
}
...
}
However, for a more general use case, you'd want to write a conditional statement based on the props and/or the state of the component:
class YourComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
// Your state
};
}
shouldComponentUpdate(nextProps, nextState) {
// A conditional statement to determine whether
// this component should check for updates or not
}
render () {
return (
<div>
{/* Your JSX*/}
</div>
)
}
I don't use React Boostrap but I guess it's based on the Component design,
example, the rendered content used TabIndex state. Take a closer look at this sample code:
renderActiveTabContent() {
const { children } = this.props
const { activeTabIndex } = this.state
if (children[activeTabIndex]) {
return children[activeTabIndex].props.children
}
}
So the content component render every time Tab state is indexed.
You could use https://github.com/reactjs/react-tabs for your solution other wise take a look of those codes to write a simple one, the Component is rendered once and show/hide state via display: style attribute.
Hope it's help.

Update same variable from two different components

I want to use the same state variable say count and update and retrieve the updated one.
I wrote the following code as a higher order component consisting of one button and one label . Both updates the count but they have separate instances. So how can I re-align my code to keep the same copy of the variable count.
const HOC = (InnerComponent) => class extends React.Component{
constructor(){
super();
this.state = {
count: 0
}
}
update(){
this.setState({count: this.state.count + 1})
}
render(){
return(
<InnerComponent
{...this.props}
{...this.state}
update = {this.update.bind(this)}
/>
)
}
};
class App extends React.Component {
render() {
return (
<div>
<Button>Button</Button>
<hr />
<LabelHOC>Label</LabelHOC>
</div>
);
}
}
const Button = HOC((props) => <button onClick={props.update}>{props.children} - {props.count}</button>)
class Label extends React.Component{
render(){
return(
<label onMouseMove={this.props.update}>{this.props.children} - {this.props.count}</label>
)
}
}
const LabelHOC = HOC(Label)
export default App;
You need to do some "thinking-in-react".
React is just a rendering library, it renders the state, so you need to do some thinking about where that state should live. It's common for your scenario to start look at some sort of Flux library that can handle this "one source of truth" (keep you state in one place only), like Redux for example. If you're using Redux then the Redux store would hold the "count" state for both components and they could both update and read it, so that would be my suggestion in the long run. But to solve your immediate question, you must let a higher component hold the state and then of course also modify that state, you do that by passing down the state and a update function as props to the children.
This is snippet of how it could look, just send the state (count) and the update function down to the child components. I excluded HOC component because i think it just adds to your confusion here. But i'm sure you can imagine how it would work. :)
class App extends React.Component {
constructor(){
super();
this.state = {
count: 0
}
this.update = this.update.bind(this); //Bind it once
}
update(){
this.setState({count: this.state.count + 1})
}
render() {
return (
<div>
<Button count={this.state.count} update={this.update}>Button</Button>
<hr />
<LabelHOC count={this.state.count} update={this.update}>Label</LabelHOC>
</div>
);
}
}
Good reads from the docs:
Components and props
Data flows down

React: To put simple logic in Container or Presentational component?

I have a container component which passes an array of objects down to a presentational component to output.
In the presentational component, I need to display the count of a number of these objects that meet certain criteria. Is it best practice to perform the count in the container component and pass it down to the presentational component or is it OK to do this count in the presentational component.
ie:
export class ResultsPage extends React.Component {
constructor(props){
super(props);
}
countSexyObjects(){
const matching = this.props.allObjects.filter((obj)=>{
return obj.sexy === true;
});
return matching.length
}
render(){
return (
<PresentationalComponent allObjects={this.props.allObjects}
numberOfSexyObjects={this.countSexyObjects()} />
);
}
}
let PresentationalComponent = (props) => {
return (
<div>
There are {props.numberOfSexyObjects} sexy objects
</div>
);
};
OR
export class ResultsPage extends React.Component {
constructor(props){
super(props);
}
render(){
return (
<PresentationalComponent allObjects={this.props.allObjects} />
);
}
}
let PresentationalComponent = (props) => {
const countSexyObjects = () => {
const matching = this.props.allObjects.filter((obj)=>{
return obj.sexy === true;
});
return matching.length
};
return (
<div>
There are {countSexyObjects()} sexy objects
</div>
);
};
Ideally state is considered an evil in React. I understand that React is built upon the concept of state but less state is more preferred, which means try to structure the code with mostly functions that are pure in nature.
IMHO in your first example is more correct. The ResultsPage is your Container Component(smart component) while the other is dumb. Dumb component doesn't manage state and just takes care of how the UI looks. You can put all the html, bootstrap logic in there.
The reason why this pattern is good is because lets say now you want to fetch the matching criteria from an XHR call, your code in the second case would be
export class ResultsPage extends React.Component {
constructor(props){
super(props);
}
getSexyMatcher() {
/* make ajax call here */
return results;
}
render(){
return (
<PresentationalComponent allObjects={this.props.allObjects} sexyMatcher={getSexyMatcher()}/>
);
}
}
let PresentationalComponent = (props) => {
const countSexyObjects = () => {
const matching = this.props.allObjects.filter((obj)=>{
return obj.sexy.match(props.sexyMatcher)
// return obj.sexy === true;
});
return matching.length
};
return (
<div>
There are {countSexyObjects()} sexy objects
</div>
);
};
Notice how you had to change two components for the same business logic? Much worse, what if someone else used that PresentationalComponent elsewhere in the codebase?
In the first case things are much simpler. Just have to add the ajax function in the smart component and pass down the results to the UI component.
I would use the first format for a few reasons:
The smart component should a better idea of what a "SexyObject" is. If its a field in the object, that's pretty simple and could be argued either way. If it relies on a web service or some more complex logic to determine if it is sexy or not, you would never want that in the presentational layer. Simple has a way of turning complex, so I'd use the structure that supports the complexity initially.
Testing the code will be simpler with the logic in the smart component. You can prime your component and then check the output variables from your fixed data set.
If the criteria for "SexyObject" can change by the component, you would retain the ability to reuse your presentational component if you kept the selection logic separate.
Just my $0.02

Resources