简体   繁体   English

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

[英]How to display different list of items when clicking on a category

I am quite new to React JS and I have this simple UI I need to build. 我是React JS的新手,我需要构建这个简单的UI。 I basically have a list of categories and if I click on a category, a list of items will display under that category. 我基本上有一个类别列表,如果我点击一个类别,项目列表将显示在该类别下。 It will hide the list of items if I click on another category. 如果我点击另一个类别,它将隐藏项目列表。

I was provided two APIs, one containing the JSON of categories and another containing the items. 我提供了两个API,一个包含类别的JSON,另一个包含项目。

I have managed to fetch the data from the APIs and spit them out on the DOM. 我已经设法从API中获取数据并将其吐出DOM。 However I am finding it hard to piece the component together to only display the right items when it's category has been clicked. 但是我发现很难将组件拼凑在一起,只在单击类别时才显示正确的项目。

I am using Babel to transpile my JSX syntax and uses axios to fetch the Data. 我正在使用Babel来转换我的JSX语法并使用axios来获取数据。 At the moment my page only spits out all the items and all the categories. 目前我的页面只会吐出所有项目和所有类别。 Understanding state is difficult for me. 理解国家对我来说很难。

Any advice for a newbie Reactjs leaner? 对新手Reactjs更精简的任何建议? Thanks! 谢谢!

My two APIs can be found in my code since I don't have enough rep points to post links. 我的两个API可以在我的代码中找到,因为我没有足够的重复点来发布链接。

My JSX: 我的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"));

Here is a screenshot of what I have on my webpage so far. 这是我到目前为止在我的网页上的截图。 Everything just dumps on the screen. 一切都只是在屏幕上转储。 The links in blue are the categories. 蓝色链接是类别。

Screenshot of current web page 当前网页的屏幕截图

I believe I have a working version for you. 我相信我有适合你的版本。 I changed some syntax and variable/prop names for clarity and added comments explaining changes. 为了清晰起见,我更改了一些语法和变量/ prop名称,并添加了解释更改的注释

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

The two key parts for filtering here are the .filter() and .some() methods on arrays. 这里过滤的两个关键部分是数组上的.filter().some()方法。

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

What this is saying is: Iterate through all the items . 这是说:迭代所有items For each item , iterate through its categories and check if any of their id s is the same as the selectedCategoryId . 对于每个item ,迭代其categories并检查它们的任何id是否与selectedCategoryId相同。 If one of them is, the .some() statement will return true causing that item in the .filter() to return true , causing it to get returned in the final, filtered, array that .filter() returns. 如果他们中的一个就是, .some()语句将返回true导致该item.filter()返回true ,使其最终,过滤,阵列,在获得返回.filter()返回。

You'll also notice I made named methods on the List components for mapping through the list items. 您还会注意到我在List组件上创建了用于映射列表项的命名方法。 This is so the functions only get declared once when the component mounts and don't get redeclared every time the component re-renders. 这样,函数只在组件安装时才会声明一次,并且每次组件重新渲染时都不会重新声明。 I think it also reads a bit nicer and adds more semantics to the code. 我认为它也读得更好,并为代码添加了更多的语义。

Edit: I noticed you were using Babel so I ES6'd it up a bit. 编辑:我注意到你正在使用Babel,所以我ES6了一下。 <3 ES6. <3 ES6。

Clearly there are many ways to achieve what you want. 很明显,有很多方法可以达到你想要的效果。

But here is an example of How I would personally layout a simple UI like that. 但这里有一个例子,我将如何个人布局这样的简单UI。 I removed the API calls to provide a workable sample in CodePen below 我删除了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 http://codepen.io/chadedrupt/pen/pbNNVO

Hopefully it is pretty explanatory. 希望它很有说服力。

Note. 注意。 Used a lot of ES6 stuff as I think its really worth learning as it makes everything so much more pleasant. 使用了很多ES6的东西,我觉得它真的值得学习,因为它让一切都变得更加愉快。 Also mixed a bit of Underscore/Lodash in as well. 同时混合了一些Underscore / Lodash。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 单击图标后如何显示我的列表项? - How can I display my list items after clicking an icon? 单击一个页面上的按钮时如何显示不同的组件? - How to display different components when clicking a button on one page? 如何在没有项目的情况下隐藏类别? - How to hide a category when there are no items in it? 如何通过许多不同的类别列表对数据项列表进行分类,其中每个列表包含几个不同的类别值? - How does one categorize a list of data items via many different category lists where each list contains several distinct category values? 单击列表项时如何显示其他数据? - How can I display additional data when clicking on list item? 单击单选按钮时如何显示下拉列表? - How to display dropdown list when clicking radio button? 在 Phaser 3 中,如何启用单击数组中的单个项目并在屏幕的不同部分显示每个项目? - How do you enable clicking on individual items in an array and display each item in a different part of the screen in Phaser 3? 如何通过单击不同的按钮来显示不同的元素 - How to display different elements by clicking in different buttons 单击父列表时如何显示隐藏的列表项 - How to display hidden list items when a parent list is clicked 单击侧边按钮时显示不同的图表 - Display different chart when clicking on side buttons
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM