Faster React state management for complex components - reactjs

Recently, I tried to use both Redux and MobX state management libraries for React, however, if you're implementing more complex pages with a large amount of bindings (i.e. 1000) it comes a bit slow to re-render whole VDOM for a single property change of the state. Therefore, I've tried to implement library that would re-render only those components that listens to used binding.
In a ViewModel you can define Observable objects, arrays and actions. To change value any value you can use this.set function (similar to redux action) that will set the value of an observable, but components that listens to this binding will be re-rendered later on this.applyChanges call.
export class ArrayViewModel extends ViewModel {
todo: Observable<string> = new Observable("");
todos: ObservableArray<string>
= new ObservableArray(
[
new Observable("milk"),
new Observable("carrot")
]
);
addTodo = () => {
this.set(this.todos, [ ...this.todos.get(), new Observable(this.todo.get())]);
this.set(this.todo, "");
this.applyChanges();
}
}
You would need to extend Component type and attach store (similar to redux) with your ViewModel (state). To print any value you can use this.bind function that will register component to updates of the property.
export class ArrayComponent extends Component<ArrayViewModel, ArrayComponentProps, {}> {
constructor(props: ArrayComponentProps) {
super(props);
}
componentDidMount() {
}
render() {
return (
<div>
<p>
<Textbox store={this.store} text={this.vm.todo} />
<button onClick={this.vm.addTodo}>
Add Todo
</button>
</p>
<ul>
{this.bind(this.vm.todos).map(todo => {
return (
<li key={todo.id}>
<Literal store={this.store} text={todo} />
</li>
);
})}
</ul>
<ul>
{this.bind(this.vm.todos).map(todo => {
return (
<li key={todo.id}>
<Textbox store={this.store} text={todo} />
</li>
);
})}
</ul>
</div>
);
}
}
In a component, the set action on store can be easily called on change (re-renders only current component) and applied on blur (will re-render all components that uses the same binding);
export class Textbox<TProps extends TextboxProps, TState>
extends Component<ViewModel, TProps, TState> {
constructor(props: TProps & ComponentProps) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(e: React.ChangeEvent<HTMLInputElement>) {
this.change(this.props.text, e.target.value);
if (this.props.onChange) {
this.props.onChange(e);
}
}
render() {
return (
<input
type="text"
value={this.bind(this.props.text)}
onChange={this.handleChange}
onBlur={this.applyChanges}
/>
);
}
}
It won't be faster with adding, deleting or sorting the array, but it will be much faster to render changes of any array item values.
I understand that all things could be done easier by using decorators (don't have this skill yet), but I'd like to ask whether you think that this approach could have a performance impact on complex components written in React. I'm pretty new with React and I might be missing something, so please let me know your opinion.

Don't reinvent the wheel.
More complex use cases require a deeper knowledge of the way react works internally. Two things are the most important: avoiding render and making render result 'stable'/pure (the same result for the same props) - avoiding real DOM updates as a result of reconciliation (diffing VDOM/real DOM).
You should start from the begining - normal react state and standard optimalizations (docs): shouldComponentUpdate, PureComponent etc.
You can use this.props.onChange directly in render (use propTypes to make it required). Binding in render harms performance - every render call creates a new ref and forces real DOM update.

I think #xadm has got most of it covered. Few more additions to the same.
When it comes to complex problems having a second look at, structure, state and performance concepts is very important. React provides concepts which help you to better your application performance.
It takes the lifting state up approach to share state between components. Here is where you feel a requirement of a better structure to manage inter-component states. Although it's not a bad idea taking that approach, you would need to know react's performance concepts before you do. React.PureComponent in above case.
I have been using Rootz JS since quite some time on pretty much complex situations. As it handles complex state changes quite well. It does help me structuring my app too.
There is a sample Todo App made with Rootz JS. In case you might want to have a glance.
Here's how the source code looks like.
I like the Concept of Contracts which you might be interested in.

Related

Sharing state among subcomponents in React

I'm currently developing an app that uses React in some parts of its UI to implement some complex feature that are very interactive. Some of these parts of the app, that are in fact React components, have become very complex in size.
This has led me to organize the code by dividing them in multiple subcomponents (and those in their own subcomponents, and so forth). The main problem is that some of the grandchildren of the main component might have the need to modify the state of another grandchildren that they're not related to. To do so, I end up having to have the state in the common parent and passing a lot of event handlers (methods of the parent component) to their children, and those children would need to pass it to their grandchildren.
As you can imagine, this is becoming some kind of a mess.
// MyComponent.js
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
list: [1, 2, 3, 4],
selected: '',
}
this.add = this.add.bind(this)
this.handleChange = this.handleChange.bind(this)
}
add() {
const newNumber = this.state.list[this.state.list.length - 1] + 1,
list = [...this.state.list, newNumber]
this.setState({list})
}
handleChange({target}) {
this.setState({
selected: target.value,
})
}
render() {
return (
<>
<List items={this.state.list} selected={this.state.selected} />
<Button onClick={this.add} />
<input type="text" value={this.state.selected} onChange={this.handleChange} />
</>
)
}
}
// Button.js
class Button extends React.Component {
render() {
return (
<button onClick={this.props.onClick}>Click me!</button>
);
}
}
// List.js
class List extends React.Component {
constructor(props) {
super(props)
this.refs = props.items.map(_ => React.createRef())
}
render() {
return (
<ul>
{this.props.items.map((item, key) =>
(<li ref={this.ref[key]} key={key}>{item}</li>)
)}
</ul>
);
}
}
In the previous dummy code you can see how I need to define the add() method in the MyCompoent component so that an action that happens in the Button component can modify what is being shown in List. Even tho this might seem like the obvious way to do it, my component has a big component tree, and a lot of methods, and most of then are lost in the tree, passing from parent to child until it reaches the component that should be expected.
I have done some research on the internet and it turns out this is a very common problem. In most sites, using Redux or other state management library is recommended. However, all the tutorials and guides I've seen that implement Redux with React seem to assume you're only using React to build your app, in Single Page Application sort of way. This is not my case.
Is there any way to share the state of a component to avoid this kind of problem? Is there, maybe, a way to use Redux multiple times for multiple components in the same app, where one store saves only the state for MyComponent and can be accessed by either List or any of its possible children?
Redux doesn't require your entire site to be in React. It implements a higher-level component that you can use with any React components even if they are embedded in another site.
You can look at React hooks to solve similar problems. Specifically, check out useContext() and useState().
You've used a lifting state up pattern in react in your example.
It's quite common you good approach but when you app is growing you need to pass all bunch of props throu the tree of components. It's difficult to maintain.
In this case you need to check out redux with separated store or useContext() hook.

How does one React component call a method in another React component?

My page contains two completely separate React components (different files, different classes, no parent-child relationship).
How can one component call an instance method in another component? The problem seems to be obtaining the instance of the target component.
EDIT: Both components share the same parent (i.e. they are rendered in the same render() method) but I still don't know how to pass the reference of the target component to the calling component.
The short answer is: they don't.
It's not clear what you're trying to accomplish, so I can't speak to the specifics of your case, but the way React components "communicate" with one another is via state and props. For example, consider a Page component that has two child components, CompA and CompB, rendered something like this:
<Page>
<CompA />
<CompB />
</Page>
If CompA needs to pass something to CompB, this is done through state on the Page component, with that state exposed as props on CompA and CompB, something like this:
class Page extends React.Component {
constructor(props) {
super(props);
this.state = {
sharedValue: 42,
};
}
onChangeSharedValue(newValue) {
this.setState({ sharedValue: newValue });
}
render() {
return (
<div>
<CompA
sharedValue={this.state.sharedValue}
onChange={this.onChangeSharedValue}
/>
<CompB
sharedValue={this.state.sharedValue}
onChange={this.onChangeSharedValue}
/>
</div>
);
}
}
If CompA needs to change the shared value, it calls the onChange handler, which will change the state on the Page component. That value will then be propagated down to the CompB component.
There is no direct communication between components like you're describing; it is all done via state and props.
"Props down, Events up."
If you provide us a specific example of what you're looking for, I can update this post with a more specific response.
But in general, there are a couple of strategies that you can take. Some of them are presented here.
The preferred approach is to simply move your calling method to the parent component. It's a common strategy in React.
If you're not able to, then the next step would be to write an event handler for the parent, and then pass this event down to the first child component.
Use this event to pass information up to the parent, so that when it gets triggered, data can be passed as props down to the second component.
I only recently started doing React development and I found a solution for this problem that suits me. Admittedly, I haven't seen it referenced anywhere and when I showed it to a colleague who's been doing React for years, he kinda furrowed his brow and felt that it wasn't "right", but he couldn't really articulate to me why it's "wrong". I'm sure I'll be shouted down for it here, but I thought I'd share anyway:
File #1: objects.js
let objects= {};
export default objects;
File #2: firstComponent.js
import React from 'react';
import objects from 'objects';
class FirstComponent extends React.Component {
constructor(props) {
super(props);
objects['FirstComponent'] = this; // store a reference to this component in 'objects'
}
doSomethingInFirstComponent() {
console.log('did something in first component');
}
render() {
return (<div></div>);
}
}
export default FirstComponent;
File #3: secondComponent.js
import React from 'react';
import objects from 'objects';
class SecondComponent extends React.Component {
render() {
objects.FirstComponent.doSomethingInFirstComponent(); // call the method on the component referred to in 'objects'
return (<div></div>);
}
}
export default SecondComponent ;
When SecondComponent renders, it will trigger the console.log() in FirstComponent.doSomethingInFirstComponent(). This assumes, of course, that FirstComponent is actually mounted.
The "React Guys" that I know seem to think this approach is somehow evil. It uses a simple JavaScript object outside the normal React scope to maintain a reference to any existing objects that I choose to store there. Other than them telling me that "this isn't the way you do things in React", I haven't yet found a good explanation for how this will break or otherwise screw-up my app. I use it as a low-grade replacement for massive-overkill state-management tools like Redux. I also use it to avoid having to pass properties down through dozens of layers of React components just so something at the last level can trigger something waaaaay up in the first level.
That's not to say this approach doesn't have it's problems:
It creates an obvious dependency between the generic objects object, any component that is designed to store a reference to itself inside objects, and any component that wishes to utilizes those references. Then again, using any kind of global state-management solution creates a similar dependency.
It's probably a bad solution if you have any doubt that FirstComponent will be mounted before you try to call it from within SecondComponent.
I've found that just having the reference to a React component won't allow you to do all the things that React components can do natively. For example, it won't work to call objects.FirstComponent.setState(). You can call a method in FirstComponent, which in turn can invoke its own setState(), but you can't invoke FirstComponent's setState() directly from within SecondComponent. Quite frankly, I think this is a good thing.
You can, however, directly access the state values from the components referenced in objects.
This should only be done with "global" components (components that functionally serve as singletons). If, for example, you had a simple UI component called BasicSpan that did little more than render a basic span tag, and you proceeded to use that component over and over again throughout your React app, I'm sure it would quickly become an unmanageable nightmare to try to place references to these simple components in the objects object and then try to intelligently manage calls to those components' internal methods.
you can send an event as props and call it from other component.
Say you have a class
Class A{
handleChange(evt)
{
this.setState({
name:evt.target.value
})
}
render{
return(
<div>
<ComponentB name={this.state.name}{ onChange={this.handleChange}/>
</div>
);
}
}
Child Component
Class B{
handleChange()
{
//logic
}
render{
return(
<div>
<input type="text" onChange={this.props.onChange}/>
{this.props.name}
</div>
);
}
Here in Component B when you change the input it will call the method
of class A and update state of A.
Now getting the updated state as props in component B will give you
the changed text that you just entered

Make sure a new instance of certain component is used

I have a feeling this is a "wrong" question to ask, but here goes anyway:
I'm making some sort of quiz app (using redux for state management). (showing the important bits here)
quiz.js
<Slider {...sliderSettings} slideIndex={currentQuestionIndex}>
<Start onStart={() => onNextQuestion()} topicId={topicId} />
{
questions.map((question, ndx) => {
return (
<Question {...question} done={onDone} key={`question-${question.id}`} />
);
})
}
<Result score={score} onRestart={() => onRestart()}/>
</Slider>
question.js
<div className="question">
<h2 className="question__text">{ question }</h2>
<MultipleChoice options={answers} onChange={done} />
</div>
multiple-choice.js
const
initialState = {
selectedValue: null
};
class MultipleChoice extends Component {
constructor(props) {
super(props);
this.state = initialState;
}
handleChange(value, correct) {
this.setState({
selectedValue: value
});
this.props.onChange(correct);
}
render() {
const
{ options } = this.props,
getStateClass = (option, ndx) => {
let sc = '';
if (this.state.selectedValue !== null) {
if (this.state.selectedValue === ndx) {
sc = option.correct ? 'is-correct' : 'is-incorrect';
} else if (option.correct) {
sc = 'is-correct';
}
}
return sc;
};
return (
<ul className="multiple-choice">
{ options.map((option, ndx) => {
return (
<li key={`option-${ndx}`} className={cx('multiple-choice__option', getStateClass(option, ndx))}>
<button className="multiple-choice__button" onClick={() => this.handleChange(ndx, option.correct)}>{option.answer}</button>
</li>
);
}) }
</ul>
);
};
}
export default MultipleChoice;
The problem lies within the rendering of MultipleChoice. It uses internal state to show which answer is wrong and right.
in quiz.js, onRestart dispatches a redux action which updates the store to fetch some new questions and reset the currentQuestionIndex to 0. This all works.
But somehow, sometimes the MultipleChoice element is "reused" and is still showing the state it had in the previous round of questions. In other words, most of the time a new MultipleChoice gets mounted, but sometimes it isn't. This is react reconciliation, if I understand correctly?
But how do I solve this problem? In my view, MultipleChoice needs its internal state. So should I reset this state somehow? Or make sure a new MultipleChoice gets mounted everytime? Or am I asking the wrong questions here?
I looked at your repository, and the issue, as correctly noted in another answer is that your <Question> (and thus inner <MultipleChoice>) components never unmount, so they keep their state.
Normally this doesn’t come up often in React because people usually want the state to be preserved while the component is in the tree. When the state is no longer needed, people usually stop including components in the render() method, and React unmounts them. Next time they are rendered, their state gets reset.
The state does not get reset in your example because you always keep the <Question>s visible, even between the quiz runs. You can see that <Question>s are already mounted before we begin the quiz, and stay mounted after it ends:
So how can we force React to reset their state? There are three options:
You may cause them to unmount. Next time they get mounted, they’ll have a new state. This is usually the simplest solution, because you don’t actually display the questions on the initial “start quiz” page. For example, you can do this by adding currentQuestionIndex > 0 && guard before rendering questions.map(...) in the render() method of <Questions>.
You may pass new keys to them that don’t match previous keys. You are currently using question-${question.id} as the key right now but that will produce the same key for the same question even if you retry the quiz. To solve this, you could introduce a new state variable (either in Redux or in top-level component state), for example, quizAttemptIndex, and increment it on any new attempt. Then you could change the key to be question-${quizAttemptIndex}-${question.id}. This way attempting a quiz another time would reset the internal state of the question (as well as cause it to remount).
Finally, if you’d rather not destroy the DOM completely by passing a different key, you could pass quizAttemptIndex (explained in the previous section) as a prop to <MultipleChoice>. Then, inside it, you could this.setState({ selectedValue: null }) inside componentWillReceiveProps(nextProps) if nextProps.quizAttemptIndex !== this.props.quizAttemptIndex.
You can choose either solution depending on how important it is for you to keep the questions mounted all the time.
As far as I understood, you want your MultipleChoice component to refresh its state when you want it to do so. But as you are using react state in your component, as long as your component doesn't unmount, your react state in your MultipleChoice keeps its latest state.
This behaviour is expected from react state, because mostly you want to use it for internal behaviour. Maybe you want to toggle some ui data in your component, when some button or something triggers your component to do so. Or you want to control your input forms etc.
But what you expect should be accomplished in redux state. Your component should be reusable which doesn't care where it mounted. You pass your mounted-place-related data to your MultipleChoice with props, which is taken from redux. So now you have a reusable component. Don't spend much time about mounting etc. You might check react-redux repositories on github to get familiar with how to shape your project's data flow. When to use redux state to make desicions or handle state in your component by react state.

setting this.state in react leaf component when using redux, anti-pattern? benefits / disadvantages

Is setting and keeping the state in a Leaf component that doesn't ever touch Redux an antipattern?
For example, suppose a project has a leaf node with some states like modalIsOpen and buttonIsDisabled and countOfObjects.
This leaf node has functions that keep track of these states and modifies them as necessary. In this way, it's a simple react component before Redux came along. It's completely modular and reusable since you don't have to reimplement any Redux Actions such as a toggleModal function that you would need to connect to your component if you were to throw this component in another project. (Let's assume no other components need these simple states)
So given this example, is this an antipattern? What are the benefits and disadvantages of doing it this way? I want to assess any benefits + disadvantages before I decide if I want to make my leaf nodes like this.
It's not an antipattern. You can use state in Leaf components if it's affects only on visual template of component and not related to some shared data. So, do not be very fanatic about Redux patterns and React-ways.
Write elegant and readable code, which can be supported by you and your teammates after the time, and make a product))
Absolutely not! Redux is great for managing application data, but when it comes to managing UI related state such as "active" class, it's advised that you use React's native state.
For example- If you want to store state of a button, if it is enabled or disabled, you won't necessarily need Redux for that! You can still use React's native state. Like this-
class Button extends React.Component {
constructor(props) {
super(props);
this.state = {
active: true
};
this.toggle = this.toggle.bind(this);
}
toggle() {
this.setState({
active: !this.state.active
});
}
render() {
return (
<div>
<button disabled={this.state.active} onClick={this.toggle}>
{this.state.active ? 'Click me!' : 'You cannot click me'}
</button>
<div>
);
}
}
See? You don't need Redux here at all! Good luck!

Prevent Re-Render when React Element passed as Prop

In my React app, I am using a few libraries (i.e. Material UI and React Intl) to pass React elements as props from higher-level components down to "dumb components" that have one job: to render.
Smart component:
import ActionExplore from 'material-ui/svg-icons/action/explore';
import { FormattedMessage } from 'react-intl';
export class SmartComponent extends Component {
render() {
return (
<div>
<DumbComponent
text={<FormattedMessage id="en.dumbComponent.text" />}
icon={<ActionExplore/>}
/>
<AnotherDumbComponent {props.that.are.changing} />
</div>
);
}
}
Dumb component:
import shallowCompare from 'react-addons-shallow-compare';
export class DumbComponent extends Component {
shouldComponentUpdate(nextProps, nextState) {
return shallowCompare(this, nextProps, nextState);
}
render() {
return (
<div>
<h1>{this.props.text}</h1>
{this.props.icon}
</div>
);
}
}
The benefit of doing this, is that the DumbComponent does not need to know anything about application logic (material ui, internationalization, etc.). It simply renders, leaving SmartComponent to take care of all the business logic.
The downside I am encountering with this approach is performance: DumbComponent will always re-render, even when AnotherDumbComponent's props change instead of its own, because shouldComponentUpdate always returns true. shouldComponentUpdate is unable to do an accurate equality check between React elements in the above example.
How can React elements be equality-checked in shouldComponentUpdate? Is this too costly to perform? Is passing React elements as props to dumb components a bad idea? Is it possible to not pass down React elements as props, yet keep components dumb? Thanks!
Whether it is performant or not is going to depend on your use case. As a rule of thumb, I think this kind of logic is best used when you expect user input to only impact children of the "dumb component", rather than it's peers.
As an example, Material-UI's Dialog has almost identical structure to what you suggest for it's action buttons and title. (https://github.com/callemall/material-ui/blob/master/src/Dialog/Dialog.js#L292). But it works well in that case because those elements are on the modal, which itself doesn't modify unless you are opening or closing it.
As another potential workaround, what if you passed in the objects needed to create the children elements, without passing in the entire element? I haven't tried this, so I'm not sure how well it will work, but it might be worth a try. Something to the extent of;
<DumbComponent
textComponent={FormattedMessage}
textProps={{id: "en.dumbComponent.text"}}/>
//In DumbComponent;
render(){
return (
<div>
<h1>
<this.props.textComponent {...this.props.textProps}/>
</h1>
</div>
);
}
Might give you some more concrete things to play around with when determining if you should update or not.
I solved this by converting these plain React elements into Immutable.js maps, and passing those as props instead.
This article was very helpful: https://www.toptal.com/react/react-redux-and-immutablejs
Immutable.js allows us to detect changes in JavaScript objects/arrays without resorting to the inefficiencies of deep equality checks, which in turn allows React to avoid expensive re-render operations when they are not required.

Resources