$cache[$key] = empty($arr) ? NULL : $arr; return $cache[$key]; } // 门户 获取需要在频道显示的栏目主题数据 function portal_channel_thread($fid) { global $forumlist; if (empty($fid)) return NULL; $orderby = array('tid' => 1); $page = 1; // 遍历所有在频道显示内容的栏目 $category_forumlist = channel_category($fid); $arrlist = array(); $forum_tids = array(); $tidlist = array(); if ($category_forumlist) { foreach ($category_forumlist as &$_forum) { // 频道显示数据 $arrlist['list'][$_forum['fid']] = array( 'fid' => $_forum['fid'], 'name' => $_forum['name'], 'rank' => $_forum['rank'], 'type' => $_forum['type'], 'url' => $_forum['url'], 'channel_new' => $_forum['channel_new'], ); $forum_thread = thread_tid__find(array('fid' => $_forum['fid']), $orderby, $page, $_forum['channel_new'], 'tid', array('tid')); // 最新信息按栏目分组 foreach ($forum_thread as $key => $_thread) { $forum_tids[$key] = $_thread; } unset($forum_thread); } $tidlist += $forum_tids; } unset($category_forumlist); // 获取属性对应的tid集合 list($flaglist, $flagtids) = flag_thread_by_fid($fid); empty($flagtids) || $tidlist += $flagtids; unset($flagtids); // 频道置顶 $stickylist = sticky_list_thread($fid); empty($stickylist) || $tidlist += $stickylist; // 在这之前合并所有二维数组 tid值为键/array('tid值' => tid值) $tidarr = arrlist_values($tidlist, 'tid'); // 在这之前使用$tidarr = array_merge($tidarr, $arr)前合并所有一维数组 tid/array(1,2,3) if (empty($tidarr)) { $arrlist['list'] = isset($arrlist['list']) ? array_multisort_key($arrlist['list'], 'rank', FALSE, 'fid') : array(); return $arrlist; } $tidarr = array_unique($tidarr); $pagesize = count($tidarr); // 遍历获取的所有tid主题 $threadlist = well_thread_find_asc($tidarr, $pagesize); // 遍历时为升序,翻转为降序 $threadlist = array_reverse($threadlist); foreach ($threadlist as &$_thread) { // 各栏目最新内容 isset($forum_tids[$_thread['tid']]) AND $arrlist['list'][$_thread['fid']]['news'][$_thread['tid']] = $_thread; // 全站置顶内容 isset($stickylist[$_thread['tid']]) AND $arrlist['sticky'][$_thread['tid']] = $_thread; // 首页属性主题 if (!empty($flaglist)) { foreach ($flaglist as $key => $val) { if (isset($val['tids']) && in_array($_thread['tid'], $val['tids'])) { $arrlist['flaglist'][$key][array_search($_thread['tid'], $val['tids'])] = $_thread; ksort($arrlist['flaglist'][$key]); $arrlist['flag'][$_thread['tid']] = $_thread; } } } } unset($threadlist); if (isset($arrlist['sticky'])) { $i = 0; foreach ($arrlist['sticky'] as &$val) { ++$i; $val['i'] = $i; } } if (isset($arrlist['flag'])) { $i = 0; foreach ($arrlist['flag'] as &$val) { ++$i; $val['i'] = $i; } } if (isset($arrlist['flaglist'])) { foreach ($arrlist['flaglist'] as &$val) { $i = 0; foreach ($val as &$v) { ++$i; $v['i'] = $i; } } } isset($arrlist['list']) AND $arrlist['list'] = array_multisort_key($arrlist['list'], 'rank', FALSE, 'fid'); return $arrlist; } ?>javascript - How to display different list of items when clicking on a category - Stack Overflow
最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How to display different list of items when clicking on a category - Stack Overflow

programmeradmin0浏览0评论

I am quite new to React JS and I have this simple UI I need to build. 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.

I have managed to fetch the data from the APIs and spit them out on the 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. 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? Thanks!

My two APIs can be found in my code since I don't have enough rep points to post links.

My 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('.0/categories'),
        axios.get('.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 am quite new to React JS and I have this simple UI I need to build. 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.

I have managed to fetch the data from the APIs and spit them out on the 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. 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? Thanks!

My two APIs can be found in my code since I don't have enough rep points to post links.

My 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

Share Improve this question edited Jun 23, 2016 at 1:37 Rob 15.2k30 gold badges48 silver badges73 bronze badges asked Jun 1, 2016 at 10:34 geraldjtorresgeraldjtorres 1211 gold badge1 silver badge8 bronze badges 4
  • 1 Did you ever figure it out? – montrealist Commented Jun 17, 2016 at 0:41
  • I will try to write up an answer specific to your question later but check out this jsbin which demonstrates something similar to what you are trying to do. Take note of the items.filter(...) about midway through. This is the key to filtering through a set of results and only picking out specific ones. You'll need to write a function that checks the id of each item and compares it to the selectedCategoryId and only picks out the ones that match. jsbin.com/yotucu/1/embed?html,js,output – jaybee Commented Jun 17, 2016 at 5:24
  • Thank you @dannyid; any thoughts on best practice React-way to organize code based on an existing set of categories and items (many-to-many relationship) that are coming from the server? – montrealist Commented Jun 17, 2016 at 12:35
  • @dalbaeb, check out my answer below. Hopefully that helps answer the organization question. – jaybee Commented Jun 19, 2016 at 5:47
Add a comment  | 

2 Answers 2

Reset to default 8 +50

I believe I have a working version for you. I changed some syntax and variable/prop names for clarity and added comments explaining changes.

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.

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

What this is saying is: Iterate through all the items. For each item, iterate through its categories and check if any of their ids is the same as the 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.

You'll also notice I made named methods on the List components for mapping through the list items. 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. <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. I removed the API calls to provide a workable sample in CodePen below

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

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. Also mixed a bit of Underscore/Lodash in as well.

发布评论

评论列表(0)

  1. 暂无评论