2016-06-01 4 views
9

Sono abbastanza nuovo per React JS e ho questa semplice interfaccia utente che ho bisogno di compilare. Fondamentalmente ho una lista di categorie e se clicco su una categoria, una lista di elementi verrà mostrata sotto quella categoria. Nasconderà la lista degli oggetti se clicco su un'altra categoria.Come visualizzare un elenco diverso di elementi quando si fa clic su una categoria

Sono state fornite due API, una contenente il JSON delle categorie e un'altra contenente gli elementi.

Sono riuscito a recuperare i dati dalle API e sputarli sul DOM. Tuttavia trovo difficile mettere insieme il componente per visualizzare solo gli elementi giusti quando si fa clic sulla categoria.

Sto usando Babel per traspondere la mia sintassi JSX e usa gli assi per recuperare i dati. Al momento la mia pagina sputa solo tutti gli oggetti e tutte le categorie. Capire lo stato è difficile per me.

Qualche consiglio per un principiante Reactjs più snello? Grazie!

Le mie due API possono essere trovate nel mio codice poiché non ho abbastanza punti Rep per postare link.

mio 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")); 

Ecco uno screenshot di quello che ho sulla mia pagina web finora. Tutto semplicemente scarica sullo schermo. I collegamenti in blu sono le categorie.

Screenshot of current web page

+1

Hai mai capito? – montrealist

+0

Proverò a scrivere una risposta specifica per la tua domanda in seguito, ma controlla questo jsbin che mostra qualcosa di simile a ciò che stai cercando di fare. Prendere nota di 'items.filter (...)' a metà strada. Questa è la chiave per filtrare attraverso una serie di risultati e selezionarne solo specifici. Avrai bisogno di scrivere una funzione che controlli l'ID di ogni elemento e lo paragoni a 'SelectedCategoryId' e seleziona solo quelli che corrispondono. http://jsbin.com/yotucu/1/embed?html,js,output – jaybee

+0

Grazie @dannyid; Qualche idea sulla migliore pratica React-way per organizzare il codice in base a un gruppo esistente di categorie e elementi (relazione molti-a-molti) provenienti dal server? – montrealist

risposta

7

credo di avere una versione funzionante per voi. Ho modificato alcuni nomi di sintassi e variabile/prop per maggiore chiarezza e aggiunto commenti che spiegano i cambiamenti.

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

Le due parti fondamentali di filtraggio qui i .filter() e .some() metodi su array.

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

Ciò sta dicendo è: Scorrere tutti i items. Per ogni item, scorrere il suo categories e verificare se uno qualsiasi dei loro id s è lo stesso del selectedCategoryId. Se uno di questi è, l'istruzione .some() restituirà true causando che item nel .filter() restituisca true, causandone il ritorno nell'array finale filtrato restituito da .filter().

Noterete anche che ho creato metodi denominati sui componenti List per la mappatura degli elementi dell'elenco. Questo è il motivo per cui le funzioni vengono dichiarate una sola volta quando il componente si monta e non viene visualizzato nuovamente ogni volta che il componente viene nuovamente sottoposto a rendering. Penso che sia anche un po 'più carino e aggiunge più semantica al codice.

Modifica: ho notato che stavi usando Babel, quindi l'ho fatto un po '. < 3 ES6.

3

Chiaramente ci sono molti modi per ottenere quello che vuoi.

Ma ecco un esempio di Come strutturerei personalmente un'interfaccia utente semplice come quella.Ho rimosso il chiamate API per fornire un campione realizzabile in CodePen sotto

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

Eventualmente è abbastanza esplicativo.

Nota. Ho usato un sacco di cose per ES6 perché penso che valga davvero la pena imparare come rende tutto molto più piacevole. Anche mescolato un po 'di Underscore/Lodash in pure.