State directly on "this" - reactjs

A lot of people use properties directly on this (e.g this.clickCount) instead of this.state object and sometimes there's like 20 different properties attached directly to this.
Is this.state purely for organization or is there something else about it? Why are so many people / projects not using this.state?
Both following examples work exactly the same way.
Code example with state:
class Clicker extends React.Component {
constructor() {
super()
this.state = {
clickCount: 0
}
this._onClick= this._onClick.bind(this)
}
render() {
return (
<button onClick={this._onClick}>
Clicked {this.state.clickCount} times
</button>
)
}
_onClick() {
this.setState({
clickCount: this.state.clickCount + 1
})
}
}
Code example without state:
class Clicker extends React.Component {
constructor() {
super()
this._onClick= this._onClick.bind(this)
}
render() {
return (
<button onClick={this._onClick}>
Clicked {this.clickCount ? this.clickCount : 0} times
</button>
)
}
_onClick() {
this.clickCount = (this.clickCount ? this.clickCount : 0) + 1
}
}

There are cases when you'll want just instance variables that are unrelated to state. For these just do something like this.<INSTANCE-VARIABLE>.
When you want your component to re-render whenever a value is changed, you're better off attaching that value to state and modifying it using this.setState(..).

The first example will trigger a re-render of the component when _onClick is called because the state is changed.
The second example will not trigger a re-render of the component when _onClick is called, so you might not see the updated value for some time (until something else makes the component render again).

Related

How can I pass state to grandparent so that uncle/aunt can use it?

I've been struggling for hours trying to get some code to work. I'm new with React, but I have spent a lot time looking for a solution to this as well, and updating this code as I understood with no success.
Basically my app is a component that splits into two components, with one of those splitting into 9 buttons. When I click one of those buttons, I want its uncle/aunt to recognize that, and use the id of the button that was pushed to create a message.
I figured I should be passing the button id up to the grandparent so that it can pass the id down to the uncle/aunt. But its the passing the id to the grandparent I'm struggling with.
This is the general set up below:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
"x" : " "
};
getX(x){
this.setState({"x": x})
}
render() {
return (
<div>
<A getX={this.getX}/>
<B x={this.state.x} />
</div>
)
}
}
const A = (props) => {
const getX = (x) => props.getX(x);
a = [];
for (let i=0; i<9; i++) {
a.push(<C id={i} getX={getX}/>);
return <div>{a}</div>
}
class C extends React.Component {
constructor(props) {
super(props);
this.state = {
"id" : props.id,
"getX" : (x) => props.getX(x)
}
this.handleMouseDown = this.handleMouseDown.bind(this);
}
handleMouseDown(e) {
this.state.getX(e.target.id);
}
render() {
<div />
}
}
class B extends React.Component {
constructor(props){
super(props);
this.state = {
"x" : props.x
}
}
render() {
return <div>{this.state.x}</div>
}
}
Firstly, the getX() method of the App component doesn't seem to be working how I expected it to. By that I mean, when I add getX("7"); to the render method of the App component, just before the return statement, the whole thing crashes. But if I replace this.setState({"x": x}) with this.state.x = x in the getX() method, then the state sucessfully passes down to the component B, which is something at least. But, I don't understand why.
Secondly, I can't work out how to modify the App component's state from within component A. The getX() method of the App component doesn't seem to be passed into component A as I expected. Again, if I insert getX("7"); before the return statement of component A, the whole thing crashes again. I expected the getX function of component A to be the same function as the getX method of the App component, and therefore update the state of the App component. But I've had no success with that at all. I've even tried inserting this.getX = this.getX.bind(this) into the constructor of the App component, but that didn't solve everything for me.
Lastly, as you can probably guess, I cant modify the App component's state from any of the C components.
Any ideas? I'm stumped.
I have modified your example so that it works. A few things:
Dont copy props to state, that is an antipattern and creates bugs (as you have seen). Dont copy the id or the function passed from component A to component C, or in component B. Just use the props values.
You had some syntax errors that I fixed.
You didnt return the array created in component A.
(This is my preference, but I will argue that you are setting a value, not getting, so i renamed getX to setX.)
There was nothing returned from component C. I was not sure what you was suppoosed to be returning from that component, so I just created a button with a click-handler.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
x: '',
};
this.setX = this.setX.bind(this);
}
setX(x) {
this.setState({ x: x });
}
render() {
return (
<div>
<A setX={this.setX} />
<B x={this.state.x} />
</div>
);
}
}
const A = (props) => {
let a = [];
for (let i = 0; i < 9; i++) {
a.push(<C id={i} setX={props.setX} />);
}
return <div>{a}</div>;
};
class B extends React.Component {
render() {
return <div>{this.props.x}</div>;
}
}
class C extends React.Component {
constructor() {
super();
this.handleMouseDown = this.handleMouseDown.bind(this);
}
handleMouseDown() {
this.props.setX(this.props.id);
}
render() {
return <button onClick={this.handleMouseDown}>Click me</button>;
}
}

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>
)
}
}

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.

React having to toggle state twice for response

I'm pretty new to React so I made sure to research this as well as I can.
But for some reason when I'm rendering, I have to initiate the state manually for a response.
Here is a portion of it:
export class exampleOne extends React.Component {
constructor(props){
super();
this.state = {
buttonToggleState: true,
secondToggleState: true
}
componentDidMount() {
this.setupPage();
}
buttonToggle() {
var newToggleState = !this.state.buttonToggleState;
this.setState({
buttonToggleState: newToggleState,
secondToggleState: !this.state.secondToggleState
});
}
render() {
return (
<input type="checkbox" onChange={this.buttonToggle.bind(this)}
defaultChecked={this.state.secondToggleState} />
It works, just that I have to click the checkbox twice for it to change to actually render. It changes states, just no rendering until the second click, then it works fine after.

react immutability helper to render only changed subset of data

Please see the example here http://jsfiddle.net/8xzxkteu/1/
I'm trying to only render part of the data which is changed. In this example, state of component Main, data, is indexed by id and I am using react immutability helper to set only the changed one. But, if you click on the output, it renders all the children, as indicated by the counter. I though using immutability helper react can detect only part of the data changed hence only render it. I probably could use shouldComponentUpdate and compare object values for each child, but is there a better way doing this with immutability helper.
class Child extends React.Component {
constructor(props) {
super(props);
this.onClick = this.onClick.bind(this)
this.state = {
count: 0
};
}
componentWillReceiveProps(nextProps) {
var count = this.state.count + 1;
this.setState({ count: count });
}
onClick() {
this.props.onClick(this.props.name);
}
render() {
return <p onClick={this.onClick}>{this.props.name}: {this.props.value} {this.state.count}</p>;
}
}
class Main extends React.Component{
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this)
this.state = {
data: {
"a" : "a",
"b" : "b",
}
};
}
handleChange(id) {
this.setState({
data: React.addons.update(this.state.data, { [id]: { $set: 'x' } })
});
}
render() {
const keys = Object.keys(this.state.data);
const children = keys.map(k => {
return <Child name={k} value={this.state.data[k]} onClick={this.handleChange}/>
})
return <div>
{children}
</div>;
}
}
React.render(<Main />, document.getElementById('container'));
When you change state of component react call shouldComponentUpdate of this component and if it is return true react call render of this component.
After that react call componentWillReceiveProps, then shouldComponentUpdate, then render (if shouldComponentUpdate return true) of all child component.
By default, if there no shouldComponentUpdate method, it is considered that it has returned true. It does not matter whether you use immutable data or not - react does not know about it.
If you have immutable data you want avoid rerender, you should use shouldComponentUpdate. You can use pure-render-decorator, for example – it's check component state and props.
But if you change your state in componentWillReceiveProps you still get rerender because componentWillReceiveProps is called before shouldComponentUpdate.

Resources