Question

I was looking into facebook's react.js and found it very cool so far. I tried to make a simple folder sturcture, where you can open and close each folder. My structure looks like this

<Folder> <Header/> <Content/> </Folder>

Clicking on the Header results in the folder hiding/showing its content. That is very easily done via the state.

But now I want to have multiple folders and a 'toggle all' button. How do I get the button to toggle all children without creating a big clutter? I used refs to address them, but I think that is a bad practice as the Documentation states:

...your first inclination is usually going to be to try to use refs to "make things happen" in your app...

...think about where state should be owned in the component hierarchy. Often, it becomes clear that the proper place to "own" that state is at a higher level in the hierarchy.

I created a Fiddle to demonstrate the whole thing. It is working but I don't think that's a very good solution.

PS (Bonus question): Is it better to hide the content by just not rendering it (like done in the fiddle), or just add a 'display : none;' styletag?

Était-ce utile?

La solution

Correct way to reference children in reactjs -> Do not reference childrens to query their state. If you need to query that state from a parent, put that state in the parent and inject the state as props in the children.

Here is your code reworked: http://jsfiddle.net/t5fwn/7/

/** @jsx React.DOM */
var initialState = {
    "folders": [
    {
        "name": "folder1",
            "open": false,
            "files": [{
            "text": "content1 1"
        }]
    }, 
    {
        "name": "folder2",
            "open": false,
            "files": [{
            "text": "content2 1"
        }, {
            "text": "content2 2"
        }]
    }
    ]
};

var Folder = React.createClass({

    render: function() {
        var items = [];
        if( this.props.folderData.open ){
            this.props.folderData.files.forEach(function(file) {
                items.push( <div className="itemBox">{file.text}</div>);
            });
        }
        return (
            <div className="items_directory">
                <div className="folder_header" onClick={this.props.onFolderClick}>{this.props.folderData.name}</div>
                <div className="folder_content">
                    {items}
                </div>
            </div>
        );
    }
});

var FoldersManager = React.createClass({

    getInitialState: function() {
        return this.props.initialState;
    },

    render: function(){
        var self = this;
        var folderComponents = this.state.folders.map(function(folder,folderIndex) {
          var onFolderClick = function() {
              self.toggleFolder(folderIndex);
          };
          return <Folder folderData={folder} onFolderClick={onFolderClick}/>;
        });
        return (
            <div>
                <button onClick={this.toggleAll}>Toggle All</button>
                <div>
                    {folderComponents}
                </div>
            </div>
        );
    },

    toggleFolder: function(folderIndex) {
            var newState = this.state;
            newState.folders[folderIndex].open = !newState.folders[folderIndex].open;
            this.setState(newState);
    },

    toggleAll: function(){
        var newState = this.state;
        var newOpenToSet = this.isAllFoldersOpen() ? false : true;
        newState.folders.forEach(function(folder) {
          folder.open = newOpenToSet;
        });
        this.setState(newState);
    },

    isAllFoldersOpen: function() {
      return this.countFoldersOpen() == this.state.folders.length;
    },

    countFoldersOpen: function() {
      var i = 0;
      this.state.folders.forEach(function(folder) {
        if ( folder.open ) i++;
      });
      return i;
    }

});

React.renderComponent(<FoldersManager initialState={initialState} />, document.body);

It is better to have some kind of manager component that handle the state open/close of all folders. The Folder component could be used simply to render a folder but not to manage the state of a folder, or in your global toggleAll operation you will have to "query" the child components to know their state.


PS (Bonus question): I think it is more elegant to not render the content not displayed. But for performance reasons, if the hide/show state changes frequently, it may be better to use display : none;: it will produce the same visual effect but the DOM diff will be lighter. Do not optimize this unless you see a performance problem with not rendering the hidden elements.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top