Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React self-modifying component with proper state/props

I'm working on a pet project right now and I decided to use React for the view. I'm fairly far into developing it but I'm just starting to realize that I might not understand how to use React very well because it seems like in order to do simple things you have to go to some very extreme measures!

So, time for an arbitrary example! Here's 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 make a component that can modify itself. In this example that modification is just having a component be able to change its name from its original value to "John".

Why is this so complicated?

It seems like this would be made much simpler by just setting some state in the Name components and letting them modify themselves. Unfortunately, as far as I can tell the only way to do that would be by setting the initial state based on props which violates the "Props in getInitialState Is an Anti-Pattern" thing.

Why not just make the components manually?

In my real application the data is coming from the server, so all of the data must be fetched at once. This is why I used getInitialState to pre-load the data instead of just creating multiple Name components manually. Also, they say in the React docs that state should be stored in a common parent component, so this code follows that pattern.

Things start to get complex

The problems start when you need to modify that state. Since it comes in the form of an array of objects (very typical for serializing data from server), you can't just immediately grab the specific object that corresponds to the component you're dealing with. As far as I can tell the only way to know that is to pass an ID around to all child elements so they can pass that back when event handlers are fired to identify themselves. Even then though, you still have to loop through each element in the state to find the one you're actually trying to modify.

The complexity intensifies...

Worse yet, it appears that you can't access the key property from inside a component that has that property declared on it, so in order to have all the information you need you have to double-set that value (key and id in my example). This is where the red flags really went up for me since it seems like the only way to accomplish the goal while not breaking pattern is to bypass a part of their system..

Another thing I'm annoyed with is that you have to pass an event handler all the way from the state-holding object to the child object. In this example that's pretty simple but in my actual application that relation is four levels deep. It seems to me that all these intermediate steps just increase the likelihood of introducing bugs by missing a step in the props passing chain.

So what is the answer?

Is this just how React is? Is there some massive shortcut I'm completely ignoring? How can this be done simpler while still staying in the constraints I've specified?

like image 983
Trappar Avatar asked Jun 13 '15 09:06

Trappar


People also ask

Can a component modify its own props?

A component cannot update its own props unless they are arrays or objects (having a component update its own props even if possible is an anti-pattern), but can update its state and the props of its children.

Why should you avoid copying the values of props into a component state?

Don't “copy props into state.” It creates a second source of truth for your data, which usually leads to bugs. One source of truth is best. Components will already re-render when their props change, so there's no need to duplicate the props as state and then try to keep it up to date.

Can props be overwritten by state?

In a nutshell, it appears that the props I'm passing to a functional component are being overwritten by a state change (which is most definitely not being propagated up to the parent).

Should you modify props React?

Whether you declare a component as a function or a class, it must never modify its own props. React is pretty flexible but it has a single strict rule: All React components must act like pure functions with respect to their props. Props are never to be updated.


2 Answers

There are, I think, a couple of issues with your example that makes it more complex than necessary: the Name component is a leaky abstraction, and the Name component knows too much. (These are actually a bit intertwined.)

Before tackling these issues, I want to take a small detour to discuss my approach to designing components. I find mixing top-down and bottom-up approaches to be very useful in building React applications. Using top-down helps you to identify what components to build by starting with the idea of a component and identifying what its subcomponents should be, then identifying the subcomponents of the subcomponents, ad nauseam. After identifying the components, it's time to switch to a bottom-up approach, where you take the most basic component (no new subcomponents required to build it) and build it as self-contained and independent as possible, and then pick the next most basic component and implement it, and repeat until done.

When implementing a component, you can think of the incoming props as an API for other components to use, and you want to keep it as focused as possible on what functionality your component should provide.

So back to the issues in your example.

The first is that your Name component is a bit of a leaky abstraction. (Yes, I know this is just an arbitrary example, but bear with me.) The component presents onClick as part of its API (since it's required as a prop). This is problematic because it tells components that want to use it, "Hey, I've got a button in here!" That's fine with your current implementation of Name, but what happens when you need to change Name to have other behavior? onClick might not make sense anymore, and if you want to change that prop name, then that change ripples throughout the rest of your code. Instead, you might consider using the name 'update', which seems like a reasonable operation for a Name component to do while hiding the fact that it contains a button internally. Sure, this can be looked at as a "take care when naming things" argument, but I think good naming is a valuable tool in building components that are both easy to use and easy to modify later.

The other problem is that the Name component knows too much about the parent implementation, making it harder to use in other components later. Instead of running 'bind' in the Name component, let's assume that the owning container passes a function that takes a single parameter - newName. Inside the name component, you then only have to call that function with the new name you want. How this affects any other state is not Name's concern, and you've effectively deferred addressing that problem until you have no other choice. The Name component then looks like:

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

Notice you no longer need the id; you just accept and update a name.

Since you actually have to worry about updating the names in the NameContainer component (since that's where the data is), you can worry about there. To avoid looping over all the ids, let's create a function that takes a name object and a new name, and then bind each name object to the function, similar to what you had in your Name component. (What's left is a function that takes a single parameter newName, that we can use as the update function for Name!). The code looks like:

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

This really simplifies your components, and now Name can be used in a wider variety of contexts. I have updated your sample with the changes described here, and for kicks I've added a different implementation of Name here. The NameContainer didn't have to change since the Name API didn't change, even though the implementation of Name is pretty different between the two.

Using this bottom-up approach helps deal with your concern about passing props through a chain of subcomponents. As you build and test a component, you only have to be concerned with the very specific problem you are addressing with that component; ensuring that you pass appropriate props to its subcomponents. Since you are focused on a very specific problem, you are less likely to miss passing a crucial piece of data around.

You may look at this and still wonder "Why is this so complicated?". Consider an application where you want to show the same data in different ways simultaneously (e.g., a graph and its corresponding table of data). React gives you a way to share data across components and keep them in sync. If you held all the data and manipulated them as locally as possible (as far down the subcomponent chains as possible), then you would have to implement the synchronization between components yourself. By placing the data as high in the component chain as possible, you make it (effectively) global to those components, and React can worry about the rest of the details. You worry about passing data and how to render the data, and React takes care of the rest.

As a final note, if your application continues to grow in complexity, you might consider taking a look at Flux. It is an architectural pattern that helps manage your application complexity. It's no panacea, but I found it to be pretty good when I used it on some moderately complex apps.

like image 69
Chad Taylor Avatar answered Nov 14 '22 23:11

Chad Taylor


It looks like, in your case, you're using props to pass down an initial value, then you're using a button to change that value. This use-case doesn't fall into the "props to set state" anti-pattern.

From React's documentation:

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

In which they give the 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 would just use props to pass down the initial value of your Name component, set that value in getInitialState, and then have the button call this.setState to change it to something else.

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

You have the right idea with using the parent component to retrieve the information to the server in order to figure out how many Name components to create. After that process finishes, you use map to compile all of the Name components you want to create, and let render() take it from there. This is all exactly how I would do it. Just keep in mind that there isn't anything wrong with using props to give a default value to a component, and letting the component's state contain the current value.

like image 21
Michael Parker Avatar answered Nov 14 '22 23:11

Michael Parker