简体   繁体   中英

Creating a dynamic list of Links in react-router

I am learning react-router and I need help in the following problem:

I would like to create a list of clickable links of the strings using react-router so that each link will navigate though a route defined in route.js.

var React = require('react');
var Router = require('react-router');
var createReactClass = require('create-react-class');
var Link = Router.Link;  
var request = require('superagent'); //Superagent is an AJAX API used in Node.

var classComp = createReactClass({

request.post('/getdata')
        .end(function (err, res) {
            if (err || !res.ok) {
                console.log('Oh no! err');
            } else {
                var response = JSON.parse(res.text);
                var i;
                var pathArr = [];
                var nameArr = [];

                for (i = 0; i < response.length; i++) {
                    pathArr[i] = response[i].videourl;
                    nameArr[i] = response[i].name;
                }
var displayItem = "<div className=\"container\">";

                for (var m in nameArr) {
                  displayItem += "<div class='text-center'><Link to='play'><h3>" + nameArr[m].toString() + "</h3></Link></div>";
                }

                displayItem += "</div>";
                document.getElementById("vdlist").innerHTML = displayItem;
}

render: function () {
    return (
        <div className="container center-block vlsection1">
            {this.display()}
            <h1 className="text-center">Videos</h1>
            <div id="vdlist">

            </div>
        </div>
    );
}
});

A more idiomatic way of doing this:

render: function () {
  return (
    <div className="container center-block vlsection1">
      <h1 className="text-center">Videos</h1>
      <div id="vdlist">
        {nameArr.map(name => (
          <div key={name} className="container">
            <div class="text-center"><Link to="play"><h3>{name.toString()}</h3></Link></div>
          </div>
        ))}
      </div>
    </div>
  );
}

FYI you can't use JSX-only elements (like <Link/> ) in .innerHTML .

EDIT[Explanation]: React is built to make it easier to manage dynamic web pages, and essentially replaces the need to use things like .innerHTML . If you're using React and find yourself trying to use .innerHTML it is most likely you're going in the wrong direction.

Any capitalised element you can be sure is not an original HTML element but is instead a React component. If that's the case then the appropriate place to use it is within a JSX block (or possibly directly as a React component, if you prefer). So <Link/> , <Router/> and any other tag beginning with a capitable letter.

What I've done is added a block of JavaScript to your .render JSX that maps over the nameArr array and generates the code you were trying to generate from your .display method. As you can see, you can insert arbitrary JavaScript into JSX by putting it between {} braces.

EDIT[to answer updated question]: Your nameArr variable is only available in the .end() function for the request you're making so of course you don't have access to it in the render function. React provides a feature exactly for this case! React state! You can read up more about State and Lifecycle .

In this case all you need to do is initialise some state for your class and then update it when you receive new data. The way you initialise state using the pre-ES6 style you're using is by adding a getInitialState key to your createReactClass argument:

getInitialState: function() {
  return { nameArr: [] };
},

In this case I've initialised nameArr to be an empty array. Then in your render function you can refer to this.state.nameArr :

render: function () {
  return (
    <div className="container center-block vlsection1">
      <h1 className="text-center">Videos</h1>
      <div id="vdlist">
        {this.state.nameArr.map(name => (
          <div key={name} className="container">
            <div class="text-center"><Link to="play"><h3>{name.toString()}</h3></Link></div>
          </div>
        ))}
      </div>
    </div>
  );
}

Once you do this if we don't update it anywhere, it will always be empty. To update it, we call this.setState({ ... }) . This is how it would work with your request call:

request.post('/getdata')
  .end(function (err, res) {
    var response;
    var i;
    var nameArr = [];

    if (err || !res.ok) {
      console.log('Oh no! err');
      // NOTE: You should probably set an error in state here:
      // this.setState({ error: '...' });
    } else {
      var response = JSON.parse(res.text);

      // NOTE: .map is an easier way to extract the name here
      nameArr = response.map(function (p) { return p.name });

      // This is where you update your React state.
      this.setState({ nameArr: nameArr });
    }
  });

Once you've done this, your React class should update quickly and efficiently. This is the power of React and hopefully you can see that this is much easier than attempting to build HTML all over again. Instead you can just rely on one render method.

As already mentioned in the comment by Rajit, you try to access a dom element in the render function. This is never a good idea and shouldn't ever be done.

It is also not the correct way in React to access the dom directly since you will loose some powerful tools of react, like the virtual dom.

A better way would be to use JSX. And by JSX, I mean real JSX. There is a difference between a string that "looks" like JSX and actual JSX. What you try to do is to create a string with JSX inside. Then you add this string into the dom - using the innerHTML property. However, your JSX won't really be handled as JSX. It will just handle it like HTML - and there is no <Link /> element in HTML. That's why you can't see the items.

Instead do it like this:

import React, { Component } from 'react';
import { Link } from 'react-router-dom'; // You need to use react-router-dom here

class VideoList extends Component {
  constructor(props) {
    super(props);
    this.nameArr = ['name1', 'name2'];
  }

  getVideos() {
    // This creates a JSX element for every name in the list.
    return this.nameArr.map(name => <div className="text-center"><Link to="play"><h3>{name}</h3></Link></div>);
  }

  render() {
    return (
      <div className="container center-block vlsection1">
        <h1 className="text-center">Videos</h1>
        {this.getVideos()}
    </div>
    );
  }
}

You could try an Anchor and then Link component as:

<Anchor>
    <Link to={`crew/${crew._id}`}> {`${crew.name}`}</Link>
</Anchor>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM