Reputation: 23593
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>
)
}
}
Upvotes: 5
Views: 173
Reputation: 281726
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.
Upvotes: 2
Reputation: 23593
The component parameter can accept a function. If you pass props then with props.match.params.id
you can find the ID from the url. As we're in a function we're able to find the show from our state and pass it to the Show component.
<BrowserRouter>
<Switch>
<Route
exact
path="/"
render={() => {
return <Table shows={this.state.shows} />;
}}
/>
<Route path="/show/:id" component={(props)=>{
const foundShow = this.state.shows.find((show)=>{
const urlId = props.match.params.id;
return show.eppisodeId === parseInt(urlId);
});
// {...props} is optional, it will pass the other properties from React Router
return <Show show={foundShow} {...props} />
}} />
<Route component={FourOhFour} />
</Switch>
</BrowserRouter>;
There is however some weird behaviour. If I console.log(this.props);
in the Show component then I can see its being rendered twice. The first time this.props.show
is empty and the second time it has the data that I passed. This is a real pain as I have to check for the presence of each variable before I render it, which makes me thing I must done something wrong...
UPDATE: The following solves the above problem but I don't know if this is a good practice:
class Show extends Component {
render(props) {
if (this.props.foundShow) {
const {name, network, time} = this.props.foundShow;
return (
<div>
<h2>
{name} on {network} at {time}
</h2>
</div>
)
} else {
return (
<h2>Loading....</h2>
)
}
}
}
Upvotes: 0