Dynamically create React Routes Using Data From Component State - reactjs

I am trying to dynamically create routes using react-router-dom. The route will depend on the id of a state I have in one of my components. How can I achieve this?
App.js:
function App() {
return (
<div className="App">
<Router>
<Route path="/" component={Categories}></Route>
<Route path="/:cat" component={Recipes}></Route>
</Router>
</div>
);
}
Categories.js which I want to get the id (:cat) from - the state categories has an id value:
class Categories extends React.Component {
state = {
categories: []
}
componentDidMount() {
fetch('http://localhost:8000/api/categories/')
.then(response => response.json())
.then(data => {
this.setState({categories: data});
});
}
render() {
return (
);
}
}
I have seen others use useParams but I can't do that since Categories is a class.
Thank you for the help.

Use
this.props.match.params
i.e:
import { BrowserRouter, Route, Router, Switch } from "react-router-dom";
import React from "react";
function App() {
return (
<div className="App">
<p>R</p>
<BrowserRouter>
<Switch>
<Route path="/categories/:id" component={Recipes}></Route>
<Route path="/" component={Categories}></Route>
</Switch>
</BrowserRouter>
</div>
);
}
class Categories extends React.Component {
state = {
categories: []
};
componentDidMount() {
fetch('http://localhost:8000/api/categories/')
.then(response => response.json())
.then(data => {
this.setState({categories: data});
});
}
render() {
return (
<div>
{this.state.categories.map((c) => (
<div>CAT: {c.name}</div>
))}
</div>
);
}
}
class Recipes extends React.Component {
state = {};
componentDidMount() {
console.log('Params:',this.props.match.params)
}
render() {
return <div></div>;
}
}
export default App;

Related

React Router - Path with :id is not working correctly for component wrapped by HOC

Hi I have been developing this application using react and react-router-dom The Page component is wrapped by a HOC that imports a contentservice to access a rest api.
My navigation is in the App component. The relevant part is the
<Link to="/page/123">About Page</Link>
and
<Link to="/page/456">Contact Page</Link>
When these links are clicked the page doesn't redraw as i expected. First time i go to 'About Page' it's all good. Then when i click to go to 'Contact Page' nothing changes. Then i click on the 'About Page' again and the 'Contact Page' shows.
In all the cases above the browser address bar shows the right path and if i refresh the page i go to the right page.
Here is my navigation page:
import React, { Component } from "react";
import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";
import { connect } from "react-redux";
import Home from "./Home";
import Admin from "./Admin";
import Members from "./Members";
import Login from "./Login";
import Page from "./Page";
import PrivateRoute from "./PrivateRoute";
import "./App.css";
class App extends Component {
render() {
return (
<Router>
<div>
<ul>
<li>
<Link to="/">Home Page</Link>
</li>
<li>
<Link to="/page/123">About Page</Link>
</li>
<li>
<Link to="/page/456">Contact Page</Link>
</li>
<li>
<Link to="/members">Members</Link>
</li>
<li>
<Link to="/admin">Admin</Link>
</li>
</ul>
</div>
<Switch>
<Route path="/login" component={Login} />
<Route path="/page/:id" component={Page} />
<Route exact path="/" component={Home} />
<PrivateRoute path="/members">
<Members />
</PrivateRoute>
<PrivateRoute path="/admin">
<Admin />
</PrivateRoute>
</Switch>
</Router>
);
}
}
const mapStateToProps = (state) => {
return {
isLoggedIn: state.isLoggedIn,
};
};
export default connect(mapStateToProps, null)(App);
This is my page component:
import React, { Component } from "react";
import WithBackend from "./WithBackend";
class Page extends Component {
constructor(props) {
super(props);
this.resource = "/pages/";
this.state = { model: null };
}
render() {
if (this.state.model != null) {
return (
<div className="container">
<div className="row">
<div className="col-md">
<h1>{this.state.model.title}</h1>
<h2 dangerouslySetInnerHTML={{ __html: this.state.model.body }} />
</div>
</div>
</div>
);
} else {
return (
<div>
<h2>Page id: {this.props.match.params.id}</h2>
</div>
);
}
}
componentDidMount() {
this.props
.getEntity(this.resource, this.props.match.params.id)
.then((model) => this.setState({ model }));
}
componentDidUpdate(nextProps) {
if (nextProps.match.params.id !== this.props.match.params.id) {
this.props
.getEntity(this.resource, nextProps.match.params.id)
.then((data) => {
this.setState({ model: data });
});
}
}
}
export default WithBackend(Page);
This is the Withbackend HOC:
import React from "react";
import ContentService from "./ContentService";
const WithBackend = (WrappedComponent) => {
class HOC extends React.Component {
constructor() {
super();
this.contentService = new ContentService();
this.getEntity = this.getEntity.bind(this);
this.getEntities = this.getEntities.bind(this);
}
getEntity(resource, id) {
return this.contentService
.getEntity(resource, id)
.then((response) => response.json())
.catch((e) => {
console.log(e);
});
}
getEntities(resource) {
return this.contentService
.getEntities(resource)
.then((response) => response.json())
.catch((e) => {
console.log(e);
});
}
render() {
return (
<WrappedComponent
getEntity={this.getEntity}
getEntities={this.getEntities}
{...this.props}
/>
);
}
}
return HOC;
};
export default WithBackend;
And the content service:
class ContentService {
baseUrl = "http://localhost:8080";
getEntity(resource, id) {
const path = this.baseUrl + resource + id;
const fetchPromise = fetch(path, {
method: "GET",
});
return Promise.resolve(fetchPromise);
}
getEntities(resource) {
const fetchPromise = fetch(this.baseUrl + resource, {
method: "GET",
});
return Promise.resolve(fetchPromise);
}
}
export default ContentService;
Has anyone got any ideas why this is happening? I am not sure if it has anything to do with the Page component being wrapped by HOC but just thought it is worth mentioning.
Thank you.
Issue
The componentDidUpdate lifecycle method receives the previous props, state, and snapshot values, not the next ones.
componentDidUpdate
componentDidUpdate(prevProps, prevState, snapshot)
By sending the "previous" props' match param id you were a "cycle" behind.
Solution
Use the current id value from props.
componentDidUpdate(prevProps) {
if (prevProps.match.params.id !== this.props.match.params.id) {
this.props
.getEntity(this.resource, this.props.match.params.id)
.then((data) => {
this.setState({ model: data });
});
}
}

facing issue in passing state(loaded through api) from App Component through React Router

I am facing issue in passing state from App Component through React Router. In the App component's ComponentwillMount function, the state is loaded through an API, which is passed to Login Component by specifying it in the render function of the Route Component.
But, the Login Component is loaded prior to App setState. I need to pass this state to all other Components. Please help !
import React, { Component } from 'react';
class App extends Component {
constructor(props) {
super(props);
this.state = {
language: 'en',
labels: null,
};
}
componentDidMount() {
let language = getLanguage(); //from url
this.setState({ language }, async () => {
await this.getLabels();
});
}
getLabels = () => {
//Hit Api to fetch labels on basis of language set
this.setState({ labels: data });
};
render() {
return (
<div className='App'>
<Router>
<Switch>
<Route
exact
path='/'
render={(props) => (
<Login labels={this.state.labels} {...props} />
)}
/>
</Switch>
</Router>
</div>
);
}
}
export default App;
import React, { Component } from 'react';
export default class Login extends Component {
render() {
console.log(this.props.labels);
}
}
this.props.labels is undefined in Login Component.
Can you try showing a loder untill your api call was successfull.
import React, { Component } from 'react';
class App extends Component {
constructor(props) {
super(props);
this.state = {
language: 'en',
labels: null,
fetchingLabels:true
};
}
componentDidMount() {
let language = getLanguage(); //from url
this.setState({ language }, async () => {
await this.getLabels();
});
}
getLabels = () => {
//Hit Api to fetch labels on basis of language set
this.setState({ labels: data, fetchingLabels:false });
};
render() {
if(this.state.fetchingLabels){
return 'I am loading' // you can add your loader here
}
return (
<div className='App'>
<Router>
<Switch>
<Route
exact
path='/'
render={(props) => (
<Login labels={this.state.labels} {...props} />
)}
/>
</Switch>
</Router>
</div>
);
}
}
export default App;
import React, { Component } from 'react';
export default class Login extends Component {
render() {
console.log(this.props.labels);
}
}

React Router sharing data between components

I have a dynamic route set up in my react application, when a user clicks the image it navigates to a new route with url /details/:id:
<div className='App'>
<Switch>
<Route path='/' exact component={Home} />
<Route exact path='/details/:id' component={ItemDetails} />
</Switch>
</div>
It comes from my functional component:
const headerImages = (props) => {
const imageResults = props.trending.slice(0, 5).map(r => ( // Grab firt 5 array objects
<Link key={r.id} to={`/details/${r.id}`}>
<div key={r.id}>
<img key={r.id} src={`https://image.tmdb.org/t/p/w1280${r.backdrop_path}`} alt={r.title} className='header-image' />
<h1 className='now-playing'>Latest</h1>
<h1 className='header-title'>{r.title}</h1>
<h4 className='header-details'>{r.overview}</h4>
</div>
</Link>
))
return <div className='header-images'>
<Carousel infinite autoPlay={4500}>{imageResults}</Carousel>
</div>
}
export default headerImages
ItemDetails is a class based component that has an API call, How do I get r.id value from my functional component into my Api call?
class ItemDetails extends Component {
constructor (props) {
super(props)
this.state = { selectedItem: null }
}
fetchItemDetails = () => {
axios.get('https://api.themoviedb.org/3/movie/${this.props.r.id}?api_key=40d60badd3d50dea05d2a0e053cc96c3&language=en-US')
.then((res) => {
console.log(res.data.results)
})
}
componentDidMount(){
this.fetchItemDetails()
}
render () {
return <h1>test</h1>
}
}
Currently the API call returns undefined, but as you can see, I'm trying to pass a dynamic id into the call.
Updated solution:
class ItemDetails extends Component {
constructor (props) {
super(props)
this.fetchItemDetails = this.fetchItemDetails.bind(this)
}
fetchItemDetails = (itemId = this.props.match.params.id) => {
axios.get('https://api.themoviedb.org/3/movie/${itemId}?api_key=40d60badd3d50dea05d2a0e053cc96c3&language=en-US')
.then((res) => {
console.log(res.data.results)
})
}
componentDidMount(){
this.fetchItemDetails()
}
render () {
return <h1>test</h1>
}
}
export default ItemDetails
You can use:
const id = this.props.match.params.id
To get the id and get the data from that specific id.
This is because you haven't bound this to the function trying to access the props.
Add in the constructor:
this.fetchItemDetails = this.fetchItemDetails.bind(this);
and the url should use template literal ` instead of quote '
`https://api.themoviedb.org/3/movie/${itemId}?api_key=40d60badd3d50dea05d2a0e053cc96c3&language=en-US`
Try this:
import React, { Component } from 'react';
import axios from 'axios';
export default class ItemDtails extends Component {
fetchItemDetails = () => {
const itemId = this.props.match.params.id;
const ROOT_URL = 'https://api.themoviedb.org/3/movie';
const API_KEY = 'api_key=40d60badd3d50dea05d2a0e053cc96c3&language=en-US';
axios.get(`${ROOT_URL}/${itemId}?${API_KEY}`).then(res => {
console.log(res.data.results);
});
};
componentDidMount() {
this.fetchItemDetails()
}
render() {
return <div>Test...</div>;
}
}

Undefined prop value in child component

I'm attempting to read an array item in a child component via props. Logging the array in the child component works. But if I try to access a property of one of the array items by indexing it with the :id from match.params, it tells me that I can't access a property of 'undefined'.
Any guidance would be greatly appreciated.
tours.js
import React, { Component } from "react";
import { Route, Switch } from "react-router-dom";
// Page Imports
import Summary from "../pages/summary";
import Details from "../pages/details";
// Component Imports
import Homebutton from "../components/homebutton";
class Tours extends Component {
state = {
tours: []
};
componentDidMount() {
window.scrollTo(0, 0);
fetch("/tours")
.then(res => res.json())
.then(res => this.setState({ tours: res }));
}
render() {
const tours = this.state.tours;
return (
<section className="tours-page">
<div className="center-box">
<h2>Tours</h2>
</div>
<Switch>
<Route
exact
path={this.props.match.url}
render={props => <Summary {...props} tours={tours} />}
/>
<Route
path={this.props.match.url + "/:id"}
render={props => <Details {...props} tours={tours} />}
/>
</Switch>
<Homebutton />
</section>
);
}
}
export default Tours;
details.js
import React from "react";
const Details = ({
tours,
match: {
params: { id }
}
}) => (
<section className="details">
<h2>{tours[id]["name"]}</h2>
</section>
);
export default Details;
To be sure that tours[id] is not undefined you should check it first
<section className="details">
<h2>{tours[id] && tours[id]["name"]}</h2>
</section>
As componentDidMountalways gets called after first render, you must validate your props to avoid app crashes:
const Details = ({
tours,
match: {
params: { id }
}
}) => (
<section className="details">
<h2>{tours.length && tours[id]["name"]}</h2>
</section>
);

why after refresh page state is undefined?

//routes
const AppRoute = () => {
return (
<BrowserRouter>
<div className="container">
<Switch>
<Route path='/' component={BooksList} exact/>
<Route path='/create' component={BookCreate}/>
<Route path='/books/:id' component={BookShow}/>
</Switch>
</div>
</BrowserRouter>
);
};
export default AppRoute;
//store
const store = createStore(reducers, applyMiddleware(Promise));
ReactDOM.render(
<Provider store={store}>
<AppRoute/>
</Provider>,
document.getElementById("root")
);
I use react and redux.
I created a BookShow component to show data of one book. Data loads correctly but when I refresh the page I get this error:
Type Error: Cannot read property 'title' of undefined and hole state is undefined.
Why did this happen and how can I prevent it from happening?
this is my code
import React from 'react';
import {connect} from 'react-redux'
const BookShow = props => {
if(!props){
return <div>loading...</div>
}
return (
<div>
<h2 className="text-center">{props.book.title}</h2>
<p className="">{props.book.body}</p>
{console.log(props)}
</div>
);
};
const mapStateToProps = (state, props) => {
return {
book: state.books.find((book) => {
return book.id === props.match.params.id
})
}
};
export default connect(mapStateToProps)(BookShow);
I have not tested it though! Try it and let me know.
import React from 'react';
import {connect} from 'react-redux'
class BookShow extends React.Component{
constructor(props, context) {
super(props, context);
this.state = {
book: {}
}
}
componentWillMount(){
const { match: { params }, books } = this.props;
this.state.book = books.find((book) => {
return book.id === params.id
});
}
render(){
const { book } = this.props;
if(!props){
return <div>loading...</div>
}
return (
<div>
<h2 className="text-center">{book.title}</h2>
<p className="">{book.body}</p>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
books: state.books
}
};
export default connect(mapStateToProps)(BookShow);
BookShow is a stateless component, try to make it a class,
import React, { Component } from 'react';
export default class BookShow extends Component {
render() {
return (
<div>
your code...
</div>
);
}
}
import {withRouter} from 'react-router-dom';
export default withRouter(connect(mapStateToProps)(BookShow));
when you start from homePage and then navigate to some book you can use props.match.params.id but when refreshing page you can't. Try to use withRouter to see if it will fix your problem.

Resources