React Attach State Directly to Instance - reactjs

Why is it good practice to assign state variables to React's state? I'm a react beginner. I'm trying to understand the purpose of a separate state object. Naively, it seems just as good (and a little shorter), to write in a component:
this.foo = 3;
instead of:
this.state.foo = 3;
I understand that setState triggers a rerender, but it seems you could just easily set the state you want and then trigger a rerender directly. I've been using this.setState({}).
In all other cases, React appears to adhere to principles of simplicity. React tends to favor native JavaScript functionality over additional React-defined functionality except where additional functionality is absolutely required. Why did the creators of React go out of their way to give React state?
In-Depth Example
As a longer example, I can define a component Counter that increases its count every second. The first way uses react's state property. The second attaches its state directly to the instance.
Using state:
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {count: 0};
setInterval((() => {
this.setState(previousState => ({
count: previousState.count + 1 }));
}), 1000);
}
render() {
return <h1>{this.state.count}</h1>;
}
}
Attaching properties directly to the instance:
class Counter extends React.Component {
constructor(props) {
super(props);
this.count = 0;
setInterval((() => {
this.count += 1;
this.setState({})
}), 1000);
}
render() {
return <h1>{this.count}</h1>;
}
}

Related

How can I force a component update with non-component class objects?

I have a React component with a non-component class object in its state. Right now, I am using the React component to display some of the other object's fields. I also want to make it so that every time the displayed field in the non-component class object changes, the react component is re-rendered to show that new updated field. How can I do this?
class ReactComponentClass extends React.Component {
constructor(props) {
super(props);
this.state = {
myObj: new SimpleJSClass()
}
}
render() {
return (
<div>
<p>Value: {this.state.myObj.value}</p>
</div>
);
}
}
class SimpleJSClass {
constructor() {
this.value = 1
}
}
I have heard of this.forceUpdate() but is that the only option? If it is possible, I would like to only update value instead of everything.
In React a component rerenders when its state changes. React will notice the change if the this.setState method is called.
You can create a class variable with the new instance
constructor(props) {
super(props);
this.simpleClassInstance = new SimpleJSClass();
this.state = {
myObj: this.simpleClassInstance
}
}
So when the class content upgraded call a setState with it.
this.setState({myObj: this.simpleClassInstance})
On the other hand I rarely use classes for data. I always use objects. Mutating the state is considered harmful. So calling setState with a new object when a data is changed is the common solution.

Containing provider re-rendered by changes in a render props child using Redux

I have something like:
const higherOrderComponent = (WrappedComponent) => {
return class extends React.Component {
render() {
<CustomProvider
render={({isLoading, playNote, playNoteAtTime, stopNote}) => {
return <WrappedComponent />;
}
/>
}
};
I have a single redux store higher up in my app and when the user interacts with 'WrappedComponent', it dispatches an action to the store, which causes 'higherOrderComponent' to re-render.
This hasn't been a problem before, as I have been favoring stateless components, and letting the new state wash through the full app on state change worked with no performance issues.
The Custom provider is asynchronously loading some files though, so now there is a noticable issue.
An idea for a solution is to introduce state in the component at the 'higherOrderComponent' level. When the component is first loaded, I will populate the state from the store props.
Then I could save up and dispatch actions to update the store later at an opportune time.
I'm not sure if I have created an anti-pattern with the above though (I'm using a Higher Order Component with 'Render props' at the same time).
Or if anyone can recommend a better solution?
edit
I was asked to show what CustomProvider is doing so I have added part of it here:
class SampleProvider extends React.Component {
static propTypes = {
instrumentNames: PropTypes.array.isRequired,
audioContext: PropTypes.instanceOf(window.AudioContext),
render: PropTypes.func
};
static defaultProps = {
currentInstrument: 'mooga'
};
constructor(props) {
super(props);
this.playNote = this.playNote.bind(this);
this.state = {
activeAudioNodes: {},
instrument: null
};
}
componentDidMount() {
sampleLoader.init(this.props.audioContext, sampleFilePaths).then(loadedSamplesBuffers => {
this.samplesBuffers = loadedSamplesBuffers;
console.log('End: loading samples');
});
}
playNote = midiNumber => {
let play = samplePlayer(this.props.audioContext, this.props.mainOutput, this.samplesBuffers);
play(midiNumber, 0, null, null, this.props.currentInstrument);
};

ReactJS: Deep nested state

Let's say I have this React component:
class SomeComponent extends Component {
constructor(props) {
super(props);
this.state = {
topObject: {
childObject1: {
grandChildObj1: {
attr1: this.props.val1,
attr2: this.props.val2
}
},
childProp: 1
},
topProp: 2
};
}
render() {
return (
<div>
<span>{this.state.topObject.childObject.grandChildObject.attr1}
</span>
</div>
// ...
)
}
changeDeepNestedStateValue(val) {
// need code here to change the state
// set topObj.childObject.grandChildObject.attr1
// to the 'val' argument
}
}
What code would I need inside the function 'changeDeepNestedStateValue' so that it changes the state immutably so that React detects the change and re-renders?
Are deep-nested state values a bad practice or anti-pattern? If so, is there an optimal structure to a state, a flat one maybe?
Deep nesting is not necessarily an anti-pattern but just makes your code harder to maintain and reason about. In order to trigger a re-render and update component state, all you have to do in the changeDeepNestedStateValue function is call this.setState({ topObject: {...} }) with whatever new state you want to update. Optionally, this.setState also takes a function that exposes the previous state of the component as seen below.
this.setState((prevState) => {
if (prevState.topProp === val) {
doSomething();
}
});
I'd recommend having a look at immer
https://github.com/mweststrate/immer
It provides a super easy way to work with nested objects in terms of immutablilty
But yes, flatter state with out of the box react state management is better practice

React subscriptions which depend on state

We are currently refactoring to use higher-order components. For the most part this is making everything much simpler.
We have HOCs for fetching data and listening to stores. For example, we have connectStores, which takes a list of stores to subscribe to and a function to fetch the data (to pass as extra props):
connectStores(FooComponent, [FooStore], function (props) {
return {
foo: FooStore.get(props.id),
};
});
However, there are a few places where the process of fetching the data from the store depends upon the state. For example, we have a SelectFooPopup the presents the user with a list of items to select from. But there is also a search box to filter the list, so at the moment the component listens directly to the store and then fetches the data itself like this:
componentDidMount() {
var self = this;
this.listenTo(FooStore, 'change', function () {
self.forceUpdate();
});
}
render() {
var items = FooStore.search(this.state.searchText);
// render...
}
(this.listenTo is a mixin which we're trying to replace with HOCs so we can use ES6 classes)
I can think of a few options, but I don't like any of them:
Option 1: Remove listenTo and cleanup the listener manually
componentDidMount() {
var self = this;
this.listener = function () {
self.forceUpdate();
};
FooStore.on('change', this.listener);
}
componentWillUnmount() {
if (this.listener) {
FooStore.removeListener('change', this.listener);
}
}
render() {
var items = FooStore.search(this.state.searchText);
// render...
}
I really hate having to do this manually. We did this before we had the listenTo mixin and it's far too easy to get wrong.
This also doesn't help when the subscription has to fetch the data from the server directly rather than using a pre-filled store.
Option 2: Use connectStores but don't return any extra data
class SelectFooPopup extends React.Component {
render() {
var items = FooStore.search(this.state.searchText);
}
}
connectStores(SelectFooPopup, [FooStore], function (props) {
// Just to forceUpdate
return {};
});
This just feels wrong to me. This is asking for trouble when we start optimising for pure components and suddenly the child component doesn't re-render anymore.
Option 3: Use connectStores to fetch all the data and then filter it in render
class SelectFooPopup extends React.Component {
render() {
var items = filterSearch(this.props.items, this.state.searchText);
}
}
connectStores(SelectFooPopup, [FooStore], function (props) {
return {
items: FooStore.getAllItems(),
};
});
But now I have to have a completely separate filterSearch function. Shouldn't this be a method on the store?
Also, it doesn't make much difference in this example, but I have other components with a similar issue where
they are fetching data from the server rather than subscribing to a pre-filled store. In these cases the
data set is far too large to send it all and filter later, so the searchText must be available when fetching the data.
Option 4: Create a parent component to hold the state
Sometimes this is the right solution. But it doesn't feel right here. The searchText is part of the state of this component. It belongs in the same place that renders the search box.
Moving it to a separate component is confusing and artificial.
Option 5: Use a "parentState" HOC
function parentState(Component, getInitialState) {
class ParentStateContainer extends React.Component {
constructor(props) {
super();
this.setParentState = this.setParentState.bind(this);
if (getInitialState) {
this.state = getInitialState(props);
} else {
this.state = {};
}
}
setParentState(newState) {
this.setState(newState);
}
render() {
return <Component {...this.props} {...this.state} setParentState={ this.setParentState } />;
}
}
return ParentStateContainer;
}
// Usage:
parentState(SelectFooPopup, function (props) {
return {
searchText: '',
};
});
// In handleSearchText:
this.props.setParentState({ searchText: newValue });
This also feels really wrong and I should probably throw this away.
Conclusion
In React we have 2 levels: props and state.
It seems to me that there are actually 4 levels to think about:
props
data that depends on props only
state
data that depends on props and state
render
We can implement layer 2 using HOCs. But how can we implement layer 4?

Is it possible to make initial state empty object in React?

Each time I'm dealing with internal component state in React I must first define empty object for state property or else I will get runtime errors throwing this.state is undefined
If I was about to do this:
render() {
const { someProperty } = this.state;
render <div>{someProperty}</div>
}
I'm gonna get error.
But the cure is quite simple:
constructor(props) {
super(props);
this.state = {};
}
However this is very annoying to do this over and over for each component that I create.
Is it somehow possible to force react add initial state as an empty object globally as to avoid all the boilerplate for empty state definition?
P.s. (maybe rhetorical question) Why isn't this done in the same React core?
Is it somehow possible to force react add initial state as an empty object globally as to avoid all the boilerplate for empty state definition?
You could create your own »component base class« extending from React.Component which implements this and then derive all of your components from this class instead.
import { React } from 'react';
class StatefulComponent extends React.Component {
constructor(...args) {
super(...args);
this.state = {};
}
}
export default StatefulComponent;
.
class MyComponent extends StatefulComponent {
render() {
const { something } = this.state;
return (<div>Hello, {something}</div>);
}
}
(Live Demo)
Typically extending components in React is discouraged as it's not idiomatic; and really no harm in adding this.state = {} in (every) component that requires state. But if you want to go that route, you can do it.
P.s. (maybe rhetorical question) Why isn't this done in the same React core?
Because many (or most) components don't require state, thus you'd be allocating wasted memory. This has been discussed in react#1851:
syranide: For components that don't need state (which could easily be the majority), avoiding an allocation is a win.
Destructuring values that are not an object, array, or iterable
When you try to use destructuring on null or undefined, you get a type error:
var {data} = null;
// TypeError: null has no properties
var {property} = this.state
// TypeError: state is not defined
So you could instead do
const { someProperty } = this.state || {};
However its even better to defined an initial state since its just one time.

Resources