单击类别时如何显示不同的项目列表

时间:2016-06-01 10:34:30

标签: javascript html reactjs

我是React JS的新手,我需要构建这个简单的UI。我基本上有一个类别列表,如果我点击一个类别,项目列表将显示在该类别下。如果我点击另一个类别,它将隐藏项目列表。

我提供了两个API,一个包含类别的JSON,另一个包含项目。

我设法从API中获取数据并将其吐出DOM。但是我发现很难将组件拼凑在一起,只在单击类别时显示正确的项目。

我正在使用Babel来转换我的JSX语法并使用axios来获取数据。目前我的页面只会吐出所有项目和所有类别。理解国家对我来说很难。

对新手Reactjs更精简的任何建议?谢谢!

我的两个API可以在我的代码中找到,因为我没有足够的重复点来发布链接。

我的JSX:

var React = require('react');
var ReactDOM = require('react-dom');
var axios = require('axios');



var NavContainer = React.createClass({

  getInitialState: function() {
    return {
      category: [],
      items: []
    }
  },

  // WHAT IS CURRENTLY SELECTED
    handleChange(e){
        this.setState({data: e.target.firstChild.data});
    },

  componentDidMount: function() {
    // FETCHES DATA FROM APIS
    var th = this;
    this.serverRequest = 
      axios.all([
        axios.get('https://api.gousto.co.uk/products/v2.0/categories'),
        axios.get('https://api.gousto.co.uk/products/v2.0/products?includes[]=categories&includes[]=attributes&sort=position&image_sizes[]=365&image_sizes[]=400&period_id=120')
      ])
      .then(axios.spread(function (categoriesResponse, itemsResponse) {
        //... but this callback will be executed only when both requests are complete.
        console.log('Categories', categoriesResponse.data.data);
        console.log('Item', itemsResponse.data.data);
        th.setState({
            category: categoriesResponse.data.data,
            items : itemsResponse.data.data,
          });
      }));


  },

  componentWillUnmount: function() {
    this.serverRequest.abort();
  },

  render: function() {
    return (

        <div className="navigation">
            <h1>Store Cupboard</h1>
            <NavigationCategoryList data={this.state.category} handleChange={this.handleChange}/>
            <NavigationSubCategoryList data={this.state.category} subData={this.state.items} selected_category={this.state.data} />
        </div>
    )
  }
});

var NavigationCategoryList = React.createClass({
    render: function () {
            var handleChange = this.props.handleChange;

        // LOOPS THE CATEGORIES AND OUTPUTS IT
        var links = this.props.data.map(function(category) {
            return (
                <NavigationCategory title={category.title} link={category.id} handleChange={handleChange}/>
            );
        });
        return (
            <div>
                <div className="navigationCategory">
                    {links}
                </div>
            </div>
        );
    }   
});

var NavigationSubCategoryList = React.createClass({
    render: function () {
            var selected = this.props.selected_category;
        var sub = this.props.subData.map(function(subcategory) {
            if(subcategory.categories.title === selected)
            return (
                <NavigationSubCategoryLinks name={subcategory.title} link={subcategory.link}   />
            );
        });                     
        return (
            <div className="subCategoryContainer">
                {sub}
            </div>
        );
    }
});

var NavigationSubCategoryLinks = React.createClass({
    render: function () {
        return (
            <div className="navigationSubCategory" id={this.props.name}>
            {this.props.name}
            </div>
        );
    }
});   



var NavigationCategory = React.createClass({
    render: function () {
            var handleChange = this.props.handleChange;
        return (
            <div className="navigationLink">
                <a href={this.props.link} onClick={handleChange}>{this.props.title}</a>
            </div>
        );
    }
});



ReactDOM.render(<NavContainer />, document.getElementById("app"));

以下是我目前在网页上的内容截图。一切都只是在屏幕上转储。蓝色链接是类别。

Screenshot of current web page

2 个答案:

答案 0 :(得分:8)

我相信我有适合你的版本。为了清晰起见,我更改了一些语法和变量/道具名称,并添加了解释更改的注释。

const React = require('react');
const ReactDOM = require('react-dom');
const axios = require('axios');

// These should probably be imported from a constants.js file
const CATEGORIES_ENDPOINT = 'https://api.gousto.co.uk/products/v2.0/categories';
const PRODUCTS_ENDPOINT = 'https://api.gousto.co.uk/products/v2.0/products?includes[]=categories&includes[]=attributes&sort=position&image_sizes[]=365&image_sizes[]=400&period_id=120';

const NavContainer = React.createClass({
  // All your state lives in your topmost container and is
  // passed down to any component that needs it
  getInitialState() {
    return {
      categories: [],
      items: [],
      selectedCategoryId: null
    }
  },

  // Generic method that's used to set a selectedCategoryId
  // Can now be passed into any component that needs to select a category
  // without needing to worry about dealing with events and whatnot
  selectCategory(category) {
    this.setState({
      selectedCategoryId: category
    });
  },

  componentDidMount() {
    this.serverRequest = axios.all([
      axios.get(CATEGORIES_ENDPOINT),
      axios.get(PRODUCTS_ENDPOINT)
    ])
    .then(axios.spread((categoriesResponse, itemsResponse) => {
      console.log('Categories', categoriesResponse.data.data);
      console.log('Item', itemsResponse.data.data);

      // This `this` should work due to ES6 arrow functions
      this.setState({
        categories: categoriesResponse.data.data,
        items : itemsResponse.data.data
      });
    }));
  },

  componentWillUnmount() {
    this.serverRequest.abort();
  },

  render() {
    // ABD: Always Be Destructuring
    const {
      categories,
      items,
      selectedCategoryId
    } = this.state;

    return (
      <div className="navigation">
        <h1>
          Store Cupboard
        </h1>

        <NavigationCategoryList
          categories={categories}
          // Pass the select function into the category list
          // so the category items can call it when clicked
          selectCategory={this.selectCategory} />

        <NavigationSubCategoryList
          items={items}
          // Pass the selected category into the list of items
          // to be used for filtering the list
          selectedCategoryId={selectedCategoryId} />
      </div>
    );
  }
});

const NavigationCategory = React.createClass({
  // Prevent natural browser navigation and
  // run `selectCategory` passed down from parent
  // with the id passed down from props
  // No querying DOM for info! when props have the info we need
  handleClick(e) {
    const { id, selectCategory } = this.props;
    // Handle the event here instead of all the way at the top
    // You might want to do other things as a result of the click
    // Like maybe:
    // Logger.logEvent('Selected category', id);
    e.preventDefault();
    selectCategory(id);
  },

  render() {
    const { id, title } = this.props;
    return (
      <div className="navigationLink">
        <a href={id} onClick={this.handleClick}>
          {title}
        </a>
      </div>
    );
  }
});
const NavigationCategoryList = React.createClass({
  // If you put your mapping method out here, it'll only
  // get instantiated once when the component mounts
  // rather than being redefined every time there's a rerender
  renderCategories() {
    const { selectCategory, categories } = this.props;

    return categories.map(category => {
      const { id, title } = category;
      return (
        <NavigationCategory
          // Every time you have a list you need a key prop
          key={id}
          title={title}
          id={id}
          selectCategory={selectCategory} />
      );
    });
  },

  render() {
    return (
      <div>
        <div className="navigationCategory">
          {this.renderCategories()}
        </div>
      </div>
    );
  }
});

const NavigationSubCategoryLink = React.createClass({
  render() {
    const { name } = this.props;
    return (
      <div className="navigationSubCategory" id={name}>
        {name}
      </div>
    );
  }
});
const NavigationSubCategoryList = React.createClass({
  renderSubCategories() {
    const { selectedCategoryId, items } = this.props;
    // This is the key to filtering based on selectedCategoryId
    return items.filter(item => {
      // Checking all the categories in the item's categories array
      // against the selectedCategoryId passed in from props
      return item.categories.some(category => {
        return category.id === selectedCategoryId;
      });
    })
    // After filtering what you need, map through
    // the new, shorter array and render each item
    .map(item => {
      const { title, link, id } = item;
      return (
        <NavigationSubCategoryLink
          key={id}
          name={title}
          link={link} />
      );
    });
  },

  render() {
    return (
      <div className="subCategoryContainer">
        {this.renderSubCategories()}
      </div>
    );
  }
});

ReactDOM.render(<NavContainer />, document.getElementById('app'));

此处过滤的两个关键部分是数组上的.filter().some()方法。

return items.filter(item => {
  return item.categories.some(category => {
    return category.id === selectedCategoryId;
  });
})

这说的是:遍历所有items。对于每个item,遍历其categories并检查其id中的任何一个是否与selectedCategoryId相同。如果其中一个是,则.some()语句将返回true,导致item中的.filter()返回true,导致其在最终返回,过滤,.filter()返回的数组。

您还会注意到我在List组件上创建了用于映射列表项的命名方法。这样,函数只在组件安装时才会声明一次,并且每次组件重新渲染时都不会重新声明。我认为它也读得更好,并为代码添加了更多的语义。

编辑:我注意到你正在使用Babel,所以我ES6了一下。 &lt; 3 ES6。

答案 1 :(得分:4)

显然,有很多方法可以达到你想要的效果。

但这里有一个例子,我将如何个人布局这样的简单UI。我删除了API调用,以便在下面的CodePen中提供可行的示例

class Nav extends React.Component {

  constructor () {
    super();

    this.state = {
      categories: [
        { title: 'First Category', id: 0 },
        { title: 'Second Category', id: 1 },
        { title: 'Third Category', id: 2 }
      ],
      items: [
        { title: 'Item 1', id: 0, category: { id: 0 } },
        { title: 'Item 2', id: 1, category: { id: 0 } },
        { title: 'Item 3', id: 2, category: { id: 0 } },
        { title: 'Item 4', id: 3, category: { id: 1 } },
        { title: 'Item 5', id: 4, category: { id: 1 } },
        { title: 'Item 6', id: 5, category: { id: 2 } },
        { title: 'Item 7', id: 6, category: { id: 2 } }
      ],
      selectedCategoryId: null
    };

    this.onSelectCategory = this.onSelectCategory.bind(this);
  }

  onSelectCategory(id) {
    this.setState({
      selectedCategoryId: id
    });
  }

  render() {
    const { categories, items, selectedCategoryId } = this.state;
    const deafultCategory = _.first(categories);
    const selectedCategory = _.find(categories, i => i.id === selectedCategoryId) || deafultCategory;    
    return (
      <div>
        <CategoryFilter categories={categories} onSelectCategory={this.onSelectCategory} />
        <ItemList items={items} selectedCategory={selectedCategory} />
      </div>
    );
  }
}

var CategoryFilter = ({ categories, onSelectCategory}) => {
  const links = categories.map(i => (
    <div key={i.id}>
      <a href="#" onClick={() => onSelectCategory(i.id)}>
        { i.title }
      </a>
    </div>
  ));
  return (
    <div>
      { links }
    </div>
  )
};

var ItemList = ({items, selectedCategory}) => {
  const currentItems = items
    .filter(i => i.category.id === selectedCategory.id)
    .map(i => (
      <div key={i.id}>
        { i.title }
      </div>
    ));
  return (
    <div>
      { currentItems } 
    </div>
  );
};


ReactDOM.render(<Nav />, document.getElementById("app"));

http://codepen.io/chadedrupt/pen/pbNNVO

希望它很有说服力。

请注意。使用了很多ES6的东西,我觉得它真的值得学习,因为它让一切都变得更加愉快。同时混合了一些Underscore / Lodash。