2016-06-01 11 views
9

Ich bin ziemlich neu in React JS und ich habe diese einfache Benutzeroberfläche, die ich erstellen muss. Ich habe grundsätzlich eine Liste von Kategorien und wenn ich auf eine Kategorie klicke, wird eine Liste von Elementen unter dieser Kategorie angezeigt. Es wird die Liste der Elemente ausblenden, wenn ich auf eine andere Kategorie klicke.So zeigen Sie verschiedene Liste von Elementen an, wenn Sie auf eine Kategorie klicken

Ich wurde zwei APIs zur Verfügung gestellt, eine mit dem JSON der Kategorien und eine andere mit den Elementen.

Ich habe es geschafft, die Daten von den APIs zu holen und sie auf dem DOM auszuspucken. Allerdings finde ich es schwierig, die Komponente zusammenzusetzen, um nur die richtigen Elemente anzuzeigen, wenn auf die Kategorie geklickt wurde.

Ich benutze Babel, um meine JSX-Syntax zu transpilieren und verwendet Axios, um die Daten zu holen. Im Moment spuckt meine Seite nur alle Gegenstände und alle Kategorien aus. Den Zustand zu verstehen ist schwierig für mich.

Irgendwelche Tipps für einen Neuling Reactjs schlanker? Vielen Dank!

Meine zwei APIs können in meinem Code gefunden werden, da ich nicht genügend Rep-Punkte zum Posten von Links habe.

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

Hier ist ein Screenshot von dem, was ich auf meiner Webseite haben so weit. Alles wird nur auf dem Bildschirm angezeigt. Die Links in Blau sind die Kategorien.

Screenshot of current web page

+1

Haben Sie es jemals herausgefunden? – montrealist

+0

Ich werde versuchen, später eine spezifische Antwort auf Ihre Frage zu schreiben, aber schauen Sie sich dieses jsbin an, das etwas zeigt, das dem ähnlich ist, was Sie versuchen zu tun. Notieren Sie den 'items.filter (...)' ungefähr in der Mitte durch. Dies ist der Schlüssel zum Filtern einer Reihe von Ergebnissen und nur zum Auswählen bestimmter Ergebnisse. Sie müssen eine Funktion schreiben, die die 'ID' jedes Elements überprüft und diese mit der 'selectedCategoryId' vergleicht und nur diejenigen auswählt, die übereinstimmen. http://jsbin.com/yotucu/1/embed?html,js,output – jaybee

+0

Danke @dannyid; irgendwelche Gedanken zu Best Practice React-Weg, um Code basierend auf einer vorhandenen Reihe von Kategorien und Elementen (Viele-zu-viele-Beziehung) zu organisieren, die vom Server kommen? – montrealist

Antwort

7

Ich glaube, ich für Sie eine funktionierende Version. Ich habe einige Syntax- und Variablen-/Prop-Namen geändert, um Klarheit zu schaffen, und Kommentare hinzugefügt, die Änderungen erklären.

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

Die beiden Schlüsselteile für die Filterung sind hier die .filter() und .some() Methoden auf Arrays.

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

Was dies sagt, ist: Iterieren durch alle items. Für jede item, iterieren Sie durch categories und überprüfen Sie, ob einer ihrer id s ist der gleiche wie der selectedCategoryId. Wenn einer von ihnen lautet, gibt die .some()-Anweisung true zurück, was dazu führt, dass item in true zurückgibt, was dazu führt, dass es im endgültigen, gefilterten Array zurückgegeben wird, das .filter() zurückgibt.

Sie werden auch feststellen, dass ich benannte Methoden auf den List Komponenten für die Zuordnung durch die Listenelemente gemacht. Dies bedeutet, dass die Funktionen nur einmal deklariert werden, wenn die Komponente geladen wird, und nicht jedes Mal neu deklariert wird, wenn die Komponente neu gerendert wird. Ich denke, es liest sich auch ein bisschen schöner und fügt dem Code mehr Semantik hinzu.

Edit: Ich bemerkte, dass du Babel benutzt hast, also habe ich es ein bisschen verbessert. < 3 ES6.

3

Natürlich gibt es viele Möglichkeiten zu erreichen, was Sie wollen.

Aber hier ist ein Beispiel dafür, wie ich persönlich eine einfache Benutzeroberfläche so gestalten würde.Ich entfernte die API eine brauchbare Probe in CodePen unter

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

bieten nennt Hoffentlich ist es ziemlich erklärend.

Hinweis. Habe viel ES6-Zeug benutzt, weil ich denke, dass es wirklich lohnenswert ist, da es alles so viel angenehmer macht. Auch etwas Underscore/Lodash gemischt.

Verwandte Themen