Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use URL on a component page to identify and pass data in state from parent with React Router?

Im using React Router. In my state I have a number of TV shows identifiable by an ID. On the shows page I need to load the data for the specific show, matching the show ID to the end of the URL.

My state looks something like this but with more shows:

shows: [
  {
    id: 1318913
    name: "Countdown"
    time: "14:10"
  },
  {
    id: 1318914
    name: "The News"
    time: "15:00"
  }
]

In App.js:

<BrowserRouter>
  <Switch>
    <Route
      exact
      path="/"
      render={() => {
        return <Table shows={this.state.shows} />;
      }}
    />
    <Route
      path="/show/:showID"
      render={() => {
        return <Show />;
      }}
    />
    <Route component={FourOhFour} />
  </Switch>
</BrowserRouter>;

React Router makes the :showID part of the URL available on the shows page . So I could pass my entire show state to the Show component and then find the correct show from the url. However I imagine this is inefficient?

I was wondering if I should look up the show via the ID in App.js and only pass the correct show to the Show component. Is this better practice / more efficient?

Here is my Table component:

class Table extends Component {
  render(props) {
    return (
      <table className="table table-striped table-bordered table-hover">
        <tbody>
        {
          this.props.shows.map((item)=>{
            return <TableRow
              name={item.name}
              time={item.time}
              key={item.eppisodeId}
              eppisodeId={item.eppisodeId}
              img={item.img}
            />
          })
        }
        </tbody>
      </table>
    )
  }
}

Here is my TableRow component:

class TableRow extends Component {
  render(props) {
    const image = this.props.img !== null ?
      <img src={this.props.img} alt={this.props.name} />
      : null;
    const showUrl = '/show/' + this.props.eppisodeId;
    return (
      <tr className="tableRow">
        <td className="tableRow--imgTd">{image}</td>
        <td><Link to={showUrl}>{this.props.name}</Link></td>
        <td className="tableRow--timeTd">{this.props.time}</td>
      </tr>
    )
  }
}
like image 530
Evanss Avatar asked Oct 29 '22 00:10

Evanss


1 Answers

As I see in your question, you are using Link to navigate. Considering you want to also send the data with the Link, you can pass an object to it instead of the string path as mentioned in the React-router docs, So you can pass the show information with the state object in TableRow component like

const {name, time, episodeId, img} = this.props;
<Link to={{pathname:showUrl, state: {show: {name, time, episodeId, img }}}}>{this.props.name}</Link>

Now you can retrieve this information in Show component as

this.props.location.state && this.props.location.state.name

The other thing that you need to take care of is, that you need to pass the props to the render function

 <Switch>
       <Route
      exact
      path="/"
      render={(props) => {
        return <Table shows={this.state.shows} {...props}/>;
      }}
    />
    <Route
      path="/show/:showID"
      render={(props) => {
        return <Show {...props}/>;
      }}
    />
    <Route component={FourOhFour} />
  </Switch>

Now the above solution will work only if you navigate from within the App, but if you change the URL it won't work, if you want to make it more robust, you would need to pass the data to the show component and then optimise in the lifecycle functions

You would . do it like

<Switch>
       <Route
      exact
      path="/"
      render={(props) => {
        return <Table shows={this.state.shows} {...props}/>;
      }}
    />
    <Route
      path="/show/:showID"
      render={(props) => {
        return <Show shows={this.state.shows} {...props}/>;
      }}
    />
    <Route component={FourOhFour} />
</Switch>

And then in the Show component

class Show extends React.Component {
    componentDidMount() {
        const {shows, match} = this.props;
        const {params} = match;
        // get the data from shows which matches params.id here using `find` or `filter` or whatever you feel appropriate

    }
    componentWillReceiveProps(nextProps) {
        const {match} = this.props;
        const {shows: nextShows, match: nextMatch} = nextProps;
        const {params} = match;
        const {params: nextParams} = nextMatch;
        if( nextParams.showId !== params.showId) {
            // get the data from nextProps.showId and nextShows here 
        }

    }
}

However this is where libraries like redux and reselect are useful. with Redux your data shows will be in one common store and you can access it in any component and reselect gives you an option to create a selector that is memoized to get the appropriate show data from shows, so that the same calculation is not repeated again. Please explore about these and check if you find them a good fit for your project.

like image 195
Shubham Khatri Avatar answered Nov 15 '22 20:11

Shubham Khatri