Responsive self-modifying component with proper state / props

I am currently working on a pet project and I decided to use React for presentation. I developed it quite far, but I'm just starting to understand that I don’t understand how to use React very well, because it seems that in order to do simple things, you have to take very extreme measures!

So, time for an arbitrary example! Here is the code working on JsFiddle

var Name = React.createClass({ render: function() { return <button onClick={this.props.onClick.bind(null, this.props.id, 'John')}>{this.props.value}</button> } }); var NameContainer = React.createClass({ getInitialState: function() { return { names: [ { id: 1, name: 'Fred' }, { id: 2, name: 'George' } ] }; }, handleClick: function(id, newName) { names = this.state.names; names.map(function(obj){ if (obj.id === id) { obj.name = newName; } return obj; }); this.setState({ names: names }); }, render: function() { var handleClick = this.handleClick; var children = this.state.names.map(function(obj){ return <Name key={obj.id} id={obj.id} value={obj.name} onClick={handleClick} />; }); return ( <div> {children} </div> ); } }); 

The idea is that I'm trying to create a component that can modify itself. In this example, this modification consists only in the fact that the component can change its name from its original value to "John".

Why is it so hard?

It seems like this will simplify a lot by simply setting some state in the Name components and letting them change themselves. Unfortunately, as far as I can tell, the only way to do this is to set the initial state based on the props, which violate the "Support in getInitialState Is Anti-Pattern" action.

Why not just make the components by hand?

In my real application, the data comes from the server, so all the data must be received immediately. That's why I used getInitialState to preload data, and not just to create several Name components manually. In addition, React docs state that the state should be stored in a common parent component, so this code follows this pattern.

Things start to get complicated

Problems begin when you need to change this state. Since it comes in the form of an array of objects (very typical for serializing data from a server), you cannot immediately capture a specific object corresponding to the component you are dealing with. As far as I can tell, the only way to find out is to pass the identifier to all the children so they can pass it back when the event handlers are fired to identify themselves. However, even then, you still have to scroll through each item in the state to find the one you are actually trying to change.

The difficulty is increasing ...

Worse, it seems that you cannot access the key property inside the component that has this property declared on it, so to have all the necessary information, you need to double this value (key and id in my example). It was there that the red flags really went to me, as it seems that the only way to achieve the goal, rather than breaking the pattern, is to bypass part of your system.

Another thing that I'm annoying is that you need to pass the event handler all the way from the state-supporting object to the child object. In this example, this is pretty simple, but in my actual application this ratio has four depth levels. It seems to me that all these intermediate steps simply increase the likelihood of introducing errors by skipping a step in the chain of passage of props.

So what is the answer?

Is that just how React is? Is there some kind of massive shortcut that I completely ignore? How can this be done easier while maintaining the limitations I have specified?

+8
javascript reactjs
source share
2 answers

There are, I think, a few problems with your example that make it more complex than necessary: ​​the Name component is a fuzzy abstraction, and the Name component knows too much. (In fact, they are a bit intertwined.)

Before solving these problems, I want to take a short detour to discuss my approach to component design. I find that mixing downstream and upstream approaches is very useful for building React applications. Use from top to bottom helps determine which components to create, starting with the idea of ​​the component and determining what its subcomponents are, and then identifying the subcomponents of the subcomponents, ad nauseam. After defining the components, the transition time to the approach is from bottom to top, where you take the most basic component (no new subcomponents needed to create it), and create it as stand-alone and independent, and then select the next basic component and implement it, and repeat until completion.

When you implement a component, you can think of the incoming details as an API for use by other components, and you want to concentrate as best as possible on what functionality your component should provide.

So back to the problems in your example.

Firstly, your Name component is a small abstraction. (Yes, I know that this is just an arbitrary example, but bear with me.) The component presents onClick as part of its API (since it is needed as a support). This is problematic because it talks about the components that want to use it: "Hey, I have a button here!" This is fine with your current implementation of Name, but what happens when you need to change Name to have different behavior? onClick no longer makes sense, and if you want to change that name, then that will change throughout the rest of your code. Instead, you might consider using the name "update", which seems like a reasonable operation for the Name component, to hide the fact that it contains a button inside. Of course, this can be seen as the “take care when naming things” argument, but I think that good naming is a valuable tool in creating components that are easy to use and easy to modify later.

Another problem is that the Name component knows too much about the parent implementation, which makes it difficult to use in other components later. Instead of running "bind" in the Name component, suppose that the owner container passes a function that takes one parameter - newName. Inside the name component, you only need to call this function with the new name you want. How this affects any other condition is not a concern for names, and you have effectively put off a solution to this problem until you have another choice. Then the Name component looks like this:

  var Name = React.createClass({ render: function() { return <button onClick={() => this.props.update('John')}>{this.props.name}</button>; } }); 

Note that you no longer need an identifier; you just accept and update the name.

Since you really need to worry about updating names in the NameContainer component (from where the data is), you can worry about it. To avoid enumerating all identifiers, create a function that takes a name object and a new name, and then binds each name object to a function, similar to what you had in the Name component. (What remains is a function that takes a single parameter newName, which we can use as an update function for Name!). The code looks like this:

 var NameContainer = React.createClass({ getInitialState: function() { return { names: [ { id: 1, name: 'Fred' }, { id: 2, name: 'George' } ] }; }, update: function(obj, newName) { obj.name = newName; this.setState({ names: this.state.names }); }, render: function() { var children = this.state.names.map(function(obj){ return <Name key={obj.id} name={obj.name} update={this.update.bind(null, obj)} />; }, this); return ( <div> {children} </div> ); } }); 

It really simplifies your components, and now Name can be used in a wider context. I updated your sample with the changes you described here , and for the kicks, I added another implementation of Name here . NameContainer did not need to be changed since the Name API has not changed, even though the name implementation is different from two.

Using this bottom-up approach helps you cope with your concern about transferring props through a chain of subcomponents. When you create and test a component, you only need to take care of the specific problem you are dealing with with this component; ensuring that you pass on the requisites to your subcomponents. Because you focus on a very specific issue, you are less likely to miss an important piece of data.

You can look at it and still wonder, “Why is it so difficult?” Consider an application in which you want to display the same data in different ways (for example, a graph and the corresponding data table). React gives you the ability to exchange data between components and synchronize them. If you saved all the data and processed it as locally as possible (as much as possible, as many as possible subcomponents), you will have to implement synchronization between the components yourself. By putting the data as high as possible in the component chain, you make it (efficiently) global for those components, and React may worry about the rest of the details. You worry about data transfer and how data is displayed, and React takes care of everyone else.

As a final note, if your application continues to grow in complexity, you can consider Flux . This is an architectural model that helps manage the complexity of your application. This is not a panacea, but I found it to be very good when I used it in some rather complex applications.

+5
source share

It looks like in your case you use the props to transfer the initial value, then you use the button to change this value. This use case does not fall into the anti-state support pillar.

From the React documentation :

However, this is not an anti-pattern, if you make it clear that synchronization is not the goal here

In which they give an example:

 var Counter = React.createClass({ getInitialState: function() { // naming it initialX clearly indicates that the only purpose // of the passed down prop is to initialize something internally return {count: this.props.initialCount}; }, handleClick: function() { this.setState({count: this.state.count + 1}); }, render: function() { return <div onClick={this.handleClick}>{this.state.count}</div>; } }); React.render(<Counter initialCount={7}/>, mountNode); 

So, I think that in this case you just use the details to pass the initial value of your Name component, set this value to getInitialState , and then click this.setState to change it to something else.

I believe that this solution answers most of the questions that you asked.

You have the right idea using the parent component to retrieve information on the server to find out how many Name components you need to create. Upon completion of this process, you use map to compile all Name components that you want to create, and let render() take it from there. That’s all the way I did it. Just keep in mind that there is nothing wrong with using props to give a component a default value and let the component state contain the current value.

+1
source share

All Articles