I'm working on my first React app and having some troubles with routing, just looking for some guidance. I see that this is a specific problem for a lot of people, but having trouble following along with other answers.
From what I can tell of other answers, people are assigning keys to specific routes, and checking the key in componentWillReceiveProps(nextProps). I kinda get that, although not sure where to go from there in terms of re-rendering/mounting.
I'm simply trying to transition between the URLs /catalog/genre/:genre and /catalog/genres. Sorry in advance for the messy code, just trying to get it working!
App.js contains main routes, more specifically for this problem:
<Route path="/catalog/genres" component={Genres}/>
Genres.js:
import React, { Component } from 'react';
import {
Link,
BrowserRouter as Router,
Route,
} from 'react-router-dom';
class Genre extends Component {
constructor(props) {
super(props);
console.log(props);
this.state = {
data: [],
};
}
componentDidMount() {
fetch('http://localhost:3000/catalog/genre/58eacca74a0d2c105c68fbe9')
.then(response => response.json())
.then(json => {
this.setState({
data: json.genre_books
});
});
}
componentWillReceiveProps(newProps) {
console.log(newProps.params);
}
render() {
return (
<div>
<div>
Genre:
<label>
<ul>
{
this.state.data.map((piece) =>
<Link key={piece._id} to={`${piece.url}`}>
<li>
{piece.title}
</li>
</Link>
)
}
</ul>
</label>
</div>
</div>
);
}
}
class AllGenres extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
}
}
componentDidMount() {
fetch('http://localhost:3000/catalog/genres')
.then(response => response.json())
.then(json => {
this.setState({
data: json.genres_list
});
});
}
render() {
return (
<div>
All Genres:
<label>
<ul>
{
this.state.data.map((piece) =>
<Link key={piece._id} to={`${piece.url}`}>
<li>
{piece.name}
</li>
</Link>
)
}
</ul>
</label>
</div>
);
}
}
class Genres extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
}
}
componentDidMount() {
fetch('http://localhost:3000/catalog/genres')
.then(response => response.json())
.then(json => {
this.setState({
data: json.genres_list
});
});
}
render() {
return (
<div>
<Router>
<div>
<Route path="/catalog/genre/:genre" render={() => (
<Genre testKey='1'/>
)}/>
<Route exact path="/catalog/genres" render={() => (
<AllGenres testKey='2' />
)}/>
</div>
</Router>
</div>
);
}
}
export default Genres;
For people coming back to this problem, although componentWillReceiveProps(nextProps) is useful for other situations, I found a way around this by following this
guide.
Now I'm able to render based on the URL parameter genreId.
Here's the code:
App.js Nav component
class Nav extends Component {
constructor(props){
super(props);
this.state = {
tap: 0,
}
}
render() {
return (
<div >
<Router>
<div style={styles.mainContent}>
<Drawer open="TRUE" title="My App">
<MenuItem primaryText="Home" containerElement={<Link to="/catalog/home" />} />
<MenuItem primaryText="All books" containerElement={<Link to="/catalog/books" />} />
<MenuItem primaryText="All authors" containerElement={<Link to="/catalog/authors" />} />
<MenuItem primaryText="All genres" containerElement={<Link to="/catalog/genres" />} />
<MenuItem primaryText="All book-instances" containerElement={<Link to="/catalog/bookinstances" />} />
<hr></hr>
<MenuItem primaryText="Create new author" containerElement={<Link to="/catalog/author/create" />} />
<MenuItem primaryText="Create new genre" containerElement={<Link to="/catalog/genre/create" />} />
<MenuItem primaryText="Create new book" containerElement={<Link to="/catalog/book/create" />} />
<MenuItem primaryText="Create new book instance (copy)" containerElement={<Link to="/catalog/bookinstance/create" />} />
<hr></hr>
<MenuItem primaryText="About" containerElement={<Link to="/about" />} />
<MenuItem primaryText="Topics" containerElement={<Link to="/topics" />} />
</Drawer>
<Route path="/catalog/home" component={Home}/>
<Route path="/catalog/books" component={Books}/>
<Route path="/catalog/authors" component={Authors}/>
<Route path="/catalog/genres" component={Genres}/>
<Route path="/catalog/bookInstances" component={BookInstances}/>
<Route path="/about" component={About}/>
<Route path="/topics" component={Topics}/>
</div>
</Router>
</div>
);
}
}
Genres.js
import React, { Component } from 'react';
import {
BrowserRouter as Router,
Route,
Link,
} from 'react-router-dom';
class GenreList extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
}
}
componentDidMount() {
fetch('http://localhost:3000/catalog/genres')
.then(response => response.json())
.then(json => {
this.setState({
data: json.genres_list
});
});
}
render() {
return (
<div>
All Genres:
<label>
<ul>
{
this.state.data.map((piece) =>
<Link key={piece._id} to={`${piece.url}`}>
<li>
{piece.name}
</li>
</Link>
)
}
</ul>
</label>
</div>
);
}
}
const Genre = ({match}) => (
<div>
<h3>{match.params.genreId}</h3>
</div>
)
const Genres = ({ match }) => {
return (
<div>
<Route path={`${match.url}/:genreId`} component={Genre}/>
<Route exact path={match.url} render={() => (
<div>
<GenreList url={match.url}/>
</div>
)}/>
</div>
);
}
export default Genres;
genre.js
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var GenreSchema = Schema({
name: { type: String, required: true, min: 3, max: 100 },
}, {
toObject: {
virtuals: true
},
toJSON: {
virtuals: true
} //reference to the associated book
});
// Virtual for bookinstance's URL
GenreSchema
.virtual('url')
.get(function () {
return '/catalog/genres/' + this._id;
});
//Export model
module.exports = mongoose.model('Genre', GenreSchema);
Related
I am trying to create a filter feature in my Reactjs app (which also includes some Redux). I am a newbie so I'm still trying to get the hang of ReactJs.I want to be able to search through my list of matters, return either the original list, or the resulted search. My matter list is inside my MattersContainer.js.
In my app, I have my SearchBar.js as child component of App.js.
class App extends React.Component {
state = {
filteredMatter: [],
}
componentDidMount() {
this.props.fetchMatters();
}
onSearchSubmit = (searchTerm) => {
const filterResults = this.props.matters.filter(matter => matter.case_title.toLowerCase()
=== searchTerm.toLowerCase())
this.setState({filteredMatter: filterResults})
}
render() {
return (
<React.Fragment>
<Router>
<NavBar />
<SearchBar onSubmit={this.onSearchSubmit}/>
<Sidebar />
<Switch>
<div className="matters-container">
<Route exact path='/' component={Home} />
<Route exact path="/matters">
<MattersContainer matters={this.state.filteredMatter.length > 0 ? this.state.filteredMatter : this.props.matters}/>
</Route>
<Route exact path="/matters/new" render={(routerProps) => <MatterForm {...routerProps} />} />
<Route exact path="/matters/:id" render={(routerProps) => {
const matterId = parseInt(routerProps.match.params.id)
// debugger
const matterObj = this.props.matters.find(matterArrObj => matterArrObj.id === matterId)
if (matterObj) {
return (
<Matter key={matterObj.id}
matters={matterObj}
{...routerProps}
/>
)
}
}
}/>
<Route path='/tasks' component={TasksContainer} />
</div>
</Switch>
</Router>
</React.Fragment>
) } }
const mapStateToProps = state => {
return ({
matters: state.matterReducer.matters
})
}
export default connect(mapStateToProps, {fetchMatters})(App)`
> My SearchBar.js looks like this:
import React, { Component } from 'react'
export default class SearchBar extends Component {
constructor() {
super()
this.state = {
searchTerm: ''
}
}
onInputChange = (event) => {
this.setState({
searchTerm: event.target.value
})
}
onFormSubmit = (event) => {
debugger
event.preventDefault();
this.props.onSubmit(this.state.searchTerm)
}
render() {
return (
<div>
<form onSubmit={this.onFormSubmit}>
<input
type="text"
value={this.state.searchTerm}
onChange={this.onInputChange}
/>
<button type="Submit">Search for a matter</button>
</form>
</div>
)
}
}
This is my MattersContainer:
class MattersContainer extends Component {
render() {
return (
<>
<div className="matter" >
<h3>Your Matters</h3>
<Paper style={{ overflow:'hidden',margin: '5px', display: 'flex',justifyContent: 'space-between' }}>
<Table>
<TableHead>
<TableRow>
<TableCell>Matter</TableCell>
<TableCell>Client</TableCell>
</TableRow>
</TableHead>
<TableBody>
{this.props.mattersArr.map(matter => (
<TableRow key={matter.id} >
<TableCell>
<Link to={`/matters/${matter.id}`}>{matter.case_title} </Link>
</TableCell>
<TableCell>
{matter.client}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Paper>
</div>
</>
)
}
}
const mSTP = state => {
return {mattersArr: state.matterReducer.matters};
}
const mDTP = dispatch => ({
deleteMatter: id => dispatch({type: "DELETE_MATTER", id})
});
export default connect(mSTP, mDTP)(MattersContainer);
If you don't use Redux to dispatch state, you must be initing state searchTemp in App.js
this.state = {
searchTemp: null,
}
After that you create functions set state searchTemp:
const setSearchTemp =(searchKey)=>{
this.setState({ searchTemp: searchKey });
}
And send functions setSearchTemp to component SearchBar to get keyword filter
I have a mind boggling issue where all three of these <RecordAdmin> component instances seem to be using the state from whichever component is loaded first on page load.
I have no clue how it's happening or why, and weirdly, it was working before.
<Switch>
<Route path="/admin/books">
<RecordAdmin singular="book" plural="books" table={BookTable} form={BookForm} />
</Route>
<Route path="/admin/authors">
<RecordAdmin singular="author" plural="authors" table={AuthorTable} form={AuthorForm} />
</Route>
<Route path="/admin/branches">
<RecordAdmin singular="branch" plural="branches" table={BranchTable} form={BranchForm} />
</Route>
</Switch>
Using console.log, it seems as though all 3 of these components will have the same this.state.records object. Shouldn't each component instance have its own state?
Here is the source for the <RecordAdmin> component:
import React from "react";
import Axios from "axios";
import {
Switch,
Route,
NavLink,
Redirect
} from "react-router-dom";
class NewRecordForm extends React.Component {
constructor(props) {
super(props);
this.state = {
redirect: false,
};
}
handleSubmit = (event, formFields, multipart = false) => {
event.preventDefault();
let formData = null;
let config = null;
if (multipart) {
formData = new FormData();
for (let [key, value] of Object.entries(formFields)) {
formData.append(key, value)
}
config = {
headers: {
'Content-Type': 'multipart/form-data'
}
}
} else {
formData = formFields;
}
Axios.post(`${process.env.REACT_APP_API_URL}/${this.props.plural}`, formData, config)
.then(response => {
this.setState({redirect: true})
}).catch(error => {
console.log(error)
})
}
render() {
if (this.state.redirect) {
this.props.redirectCallback();
}
const Form = this.props.form
return (
<div>
{this.state.redirect ? <Redirect to={`/admin/${this.props.plural}`} /> : null}
<Form handleSubmit={this.handleSubmit} />
</div>
)
}
}
function errorMessage(props) {
return (
<div class="alert alert-danger" role="alert">
{props.msg}
</div>
)
}
export default class RecordAdmin extends React.Component {
constructor(props) {
super(props)
this.state = {
records: []
}
}
componentDidMount() {
this.loadRecords();
}
loadRecords = () => {
Axios.get(process.env.REACT_APP_API_URL + '/' + this.props.plural)
.then(response => {
this.setState({records: response.data})
}).catch(error => {
console.log(error)
})
}
deleteRecord = (event, recordId) => {
event.preventDefault();
Axios.delete(process.env.REACT_APP_API_URL + '/' + this.props.plural + '/' + recordId).then(response => {
this.loadRecords();
})
}
render() {
// this allows us to pass props to children that are loaded via {this.props.children}
// more on that here: https://medium.com/better-programming/passing-data-to-props-children-in-react-5399baea0356
const TableComponent = this.props.table
return (
<div className="admin-body">
{this.state.errorMessage ? <errorMessage msg={this.state.errorMessage} /> : null}
<Switch>
<Route exact path={`/admin/${this.props.plural}`}>
<div className="admin-menu">
<NavLink className="btn btn-primary" to={`/admin/${this.props.plural}/new`}>New {this.props.singular.charAt(0).toUpperCase() + this.props.singular.slice(1)}</NavLink>
</div>
<TableComponent records={this.state.records} deleteRecord={this.deleteRecord} />
</Route>
<Route exact path={`/admin/${this.props.plural}/new`}>
<NewRecordForm plural={this.props.plural} form={this.props.form} redirectCallback={this.loadRecords}/>
</Route>
</Switch>
</div>
);
}
}
EDIT:
When I throw in a console.log I see that the first <RecordAdmin> that is loaded on page load, is having its records output to the console no matter which <RecordAdmin> instance is currently selected.
render() {
// this allows us to pass props to children that are loaded via {this.props.children}
// more on that here: https://medium.com/better-programming/passing-data-to-props-children-in-react-5399baea0356
const TableComponent = this.props.table
console.log(this.records) // No matter which <RecordAdmin> is currently being displayed, the records will be the records from whichever <RecordComponent was first loaded on page load.
return (
<div className="admin-body">
{this.state.errorMessage ? <errorMessage msg={this.state.errorMessage} /> : null}
<Switch>
<Route exact path={`/admin/${this.props.plural}`}>
<div className="admin-menu">
<NavLink className="btn btn-primary" to={`/admin/${this.props.plural}/new`}>New {this.props.singular.charAt(0).toUpperCase() + this.props.singular.slice(1)}</NavLink>
</div>
{console.log(this.state.records)}
<TableComponent records={this.state.records} deleteRecord={this.deleteRecord} />
</Route>
<Route exact path={`/admin/${this.props.plural}/new`}>
<NewRecordForm plural={this.props.plural} form={this.props.form} redirectCallback={this.loadRecords}/>
</Route>
</Switch>
</div>
);
}
No matter which <RecordAdmin> instance is being displayed, using console.log shows that state is being shared between all 3 <RecordAdmin> instances.
You can use different key for each instance of RecordAdmin and maybe pass exact={true} just to be sure.
When clicking on the back button , the url gets changed, but the component that should be rendered for this url, is not rendered. Rerendering seems to be blocked. I am using connected-react-router. Before implementing connected-react-router, the back button worked fine. I tried a suggested solution , wrapping my connect function with the redux store with withRouter, but still see no result.
index.js
const store = configureStore();
ReactDOM.render(
<Provider store={store}>
<ConnectedRouter history={history}>
<App />
</ConnectedRouter>
</Provider>,
document.getElementById('root'),
);
App.js
class App extends Component {
componentDidMount() {
const { dispatch } = this.props;
dispatch(getInitialBookData());
}
render() {
return (
<div>
<Navigation />
</div>
);
}
}
export default connect()(App);
Navigation.js
const Navigation = () => {
return (
<Fragment>
<Navbar color="light" light expand="md">
<Collapse className="navbar-collapse">
<Nav className="ml-sm-auto navbar-nav">
<NavItem className="p-2">
<NavLink activeClassName="active" to="/">Dashboard</NavLink>
</NavItem>
<NavItem className="p-2">
<NavLink activeClassName="active" to="/addbook">Add Book</NavLink>
</NavItem>
</Nav>
</Collapse>
</Navbar>
<Container className="content mt-8">
<Switch>
<Route exact path="/" component={Dashboard} />
<Route exact path="/editbook/:id" component={CreateEditBookContainer} />
<Route exact path="/addbook" component={CreateEditBookContainer} />
<Route component={Page404} />
</Switch>
</Container>
</Fragment>
);
};
Dashboard.js
const Dashboard = ({ loading, error, success }) => {
return (
<Fragment>
{ error.status === true
? <ErrorAlert message={error.message} />
: null }
{ success.status === true
? <SuccessAlert message={success.message} />
: null }
<Row className="mt-3">
<h2 className="mb-5">Welcome to Dashboard</h2>
{ loading === true
? null
: <BookListContainer /> }
</Row>
</Fragment>
);
};
const mapStateToProps = ({fetching, errors, success}) => {
return {
loading: fetching.status,
error: errors,
success: success,
};
}
Dashboard.propTypes = {
loading: PropTypes.bool.isRequired,
error: PropTypes.object.isRequired,
success: PropTypes.object.isRequired,
};
export default withRouter(connect(mapStateToProps)(Dashboard));
BookListContainer.js
class BookListContainer extends Component{
state = {
navigateToEditBook: false,
};
idBookToEdit = null;
goToEditComponent = (id) => {
this.idBookToEdit = id;
this.setState({ navigateToEditBook: true });
}
render() {
let idBookToEdit = this.idBookToEdit;
if (this.state.navigateToEditBook === true) {
return <Redirect push to={{
pathname:`/editBook/${idBookToEdit}`,
state: { referrer: '/', bookId: idBookToEdit }
}} />
}
const { books } = this.props;
return(
<Row>
{ books.map((book) => {
return (
<Book
key={book._id}
book={book}
handleEditBook={this.goToEditComponent}
/>
)
}) }
</Row>
)};
};
const mapStateToProps = (props) => {
return {
books: props.books.books,
location: props.router.location,
};
}
BookListContainer.propTypes = {
books: PropTypes.array.isRequired,
};
export default withRouter((connect(mapStateToProps)(BookListContainer)));
CreateEditBookContainer.js
class CreateEditBookContainer extends Component {
render() {
const bookId = this.props.location.state
? this.props.location.state.bookId
:null
const { books, error, success } = this.props;
let book = null;
if(bookId){
book = books.filter(book => book._id === bookId)
}
return(
<Col sm="12" md="6" className="m-auto pt-5">
<CreateEditBookForm
{ ...book ? book = { ...book[0] } : null }
/>
{ error.status === true
? <ErrorAlert message={error.message} />
: null }
{ success.status === true
? <SuccessAlert message={success.message} />
: null }
</Col>
)}
}
const mapStateToProps = ({ books, errors, success }) => {
return {
books: books.books,
error: errors,
success: success,
}
}
CreateEditBookContainer.propTypes = {
books: PropTypes.array.isRequired,
error: PropTypes.object.isRequired,
success: PropTypes.object.isRequired,
};
export default withRouter(connect(mapStateToProps)(CreateEditBookContainer));
When clicking on EditBook Button, CreateEditBookContainer.js gets correctly rendered, but when clicking on the back button, this components remains, while the Dashboard component should actually get rendered since it corresponds to the path '/', that is correctly set in the url. When clicking on the Route links in specified in Navigation, all components are rendered correctly. It ONLY fails on the back button. Thank you for any suggestions.
I am working through a practice app to get acquainted with React-Router
In my App component I have a Route which acts as a way to afford for 404's
class App extends Component {
render() {
return (
<Router>
<div>
<Navbar />
<Switch>
<Route path="/" exact component={Home} />
<Route path="/players" component={Players} />
<Route path="/teams" component={Teams} />
{/* params should be last Route as it would match players and teams */}
<Route path="/:teamId" exact component={TeamPage} />
<Route
render={({ location }) => (
<h1 className="text-center">
Sorry, we couldn't find {location.pathname.substr(1)} <br />{" "}
404: page not found.
</h1>
)}
/>
</Switch>
</div>
</Router>
);
}
}
That works fine.
However I have another component TeamPage which essentially has the same mechanism.
import React, { Component } from "react";
import { Redirect, Link } from "react-router-dom";
import { getTeamNames, getTeamsArticles } from "../api";
import TeamLogo from "./TeamLogo";
import Team from "./Team";
import slug from "slug";
export default class TeamPage extends Component {
state = {
loading : true,
teamNames: {},
articles : []
};
componentDidMount() {
Promise.all([
getTeamNames(),
getTeamsArticles(this.props.match.params.teamId)
]).then(([teamNames, articles]) => {
this.setState(() => ({
teamNames,
articles,
loading: false
}));
});
}
render() {
const { loading, teamNames, articles } = this.state;
const { match } = this.props;
const { teamId } = match.params;
if (loading === false && teamNames.includes(teamId) === false) {
return (
<Redirect
to={{
pathname: "/",
location: { location: window.location.pathname.substr(1) }
}}
/>
);
}
return (
<div>
<Team id={teamId}>
{team =>
team === null ? (
<h1>LOADING</h1>
) : (
<div className="panel">
<TeamLogo id={teamId} />
<h1 className="medium-header">{team.name}</h1>
<h4 style={{ margin: 5 }}>
<Link
style = {{ cursor: "pointer" }}
to = {{ pathname: "/players", search: `?teamId=${teamId}` }}
>
View Roster
</Link>
</h4>
<h4>Championships</h4>
<ul className="championships">
{team.championships.map(ship => (
<li key={ship}>{ship}</li>
))}
</ul>
<ul className="info-list row" style={{ width: "100%" }}>
<li>
Established
<div>{team.established}</div>
</li>
<li>
Manager
<div>{team.manager}</div>
</li>
<li>
Coach
<div>{team.coach}</div>
</li>
<li>
Record
<div>
{team.wins}-{team.losses}
</div>
</li>
</ul>
<h2 className="header">Articles</h2>
<ul className="articles">
{articles.map(article => (
<li key={article.id}>
<Link to={`${match.url}/articles/${slug(article.title)}`}>
<h4 className="article-title">{article.title}</h4>
<div className="article-date">
{article.date.toLocaleDateString()}
</div>
</Link>
</li>
))}
</ul>
</div>
)
}
</Team>
</div>
);
}
}
But in this instance I only can accomplish the redirect, with no message presented in the UI.
I am trying to pass props to home or "/" so if someone did that from the TeamPage. The Route in my App component would respond with that message, like it does normally.
Excerpt from TeamPage:
const { loading, teamNames, articles } = this.state;
const { match } = this.props;
const { teamId } = match.params;
if (loading === false && teamNames.includes(teamId) === false) {
return (
<Redirect
to={{
pathname: "/",
location: { location: window.location.pathname.substr(1) }
}}
/>
);
}
Can this be done?
Thanks in advance!
Update Oct 11th 2:56PM
So as per Hannad's great insight I updated my App component and the TeamPage component and created a catch all ErrorPage route. What I need to do now is update the Teams.js file as when I try to go to http://localhost:3000/teams/foo I get the following error:
import React, { Component } from "react";
import { Redirect, Route, Link } from "react-router-dom";
import Sidebar from "./Sidebar";
import { getTeamNames } from "../api";
import TeamLogo from "./TeamLogo";
import Team from "./Team";
export default class Teams extends Component {
state = {
teamNames: [],
loading : true
};
componentDidMount() {
getTeamNames().then(teamNames => {
this.setState(() => ({
loading: false,
teamNames
}));
});
}
render() {
const { loading, teamNames } = this.state;
const { location, match } = this.props;
return (
<div className="container two-column">
<Sidebar
loading = {loading}
title = "Teams"
list = {teamNames}
{...this.props}
/>
{loading === false &&
(location.pathname === "/teams" || location.pathname === "/teams/") ? (
<div className="sidebar-instruction">Select a Team</div>
) : null}
<Route
path = {`${match.url}/:teamId`}
render = {({ match }) => (
<div className="panel">
<Team id={match.params.teamId}>
{team =>
team === null ? (
<h1>Loading</h1>
) : (
<div style={{ width: "100%" }}>
<TeamLogo id={team.id} className="center" />
<h1 className="medium-header">{team.name}</h1>
<ul className="info-list row">
<li>
Established
<div>{team.established}</div>
</li>
<li>
Manager
<div>{team.manager}</div>
</li>
<li>
Coach
<div>{team.coach}</div>
</li>
</ul>
<Link
className = "center btn-main"
to = {`/${match.params.teamId}`}
>
{team.name} Team Page
</Link>
</div>
)
}
</Team>
</div>
)}
/>
</div>
);
}
}
there is an other way to do 404 redirects. which i would suggest. as it has more control..
define your error component which takes simple params.
class App extends Component {
render() {
return (
<Router>
<div>
<Navbar />
<Switch>
<Route path="/" exact component={Home} />
<Route path="/players" component={Players} />
<Route path="/teams" component={Teams} />
{/* params should be last Route as it would match players and teams */}
<Route path="/:teamId" exact component={TeamPage} />
<Route path="/error/:errortype" exact component={ErrorPage} />
</Switch>
</div>
</Router>
);
}
}
// your redirection logic
const { loading, teamNames, articles } = this.state;
const { match } = this.props;
const { teamId } = match.params;
if (loading === false && teamNames.includes(teamId) === false) {
return (
<Redirect
to={'/error/404'}
/>
);
}
// ErrorPage Component
const ErorPage = ({match: { params:{ errortype } }})=>(
// have your logic for different templates for different types of errors.
// ex.
<div>
{
errortype === 404 ?
<div>you got a 404</div> :
<div>sone generic message</div>
// this logic can change with respect to your app.
}
</div>
)
I want to build a membership-based web app using React. The users would sign-up and pay before they can access exclusive content. If they have already registered or signed up they can just login and access the content.
How would I go about making such a web app with React? What would I need to implement?
You can use react-router, found in npm packages : https://www.npmjs.com/package/react-router
See this example to private routes https://reacttraining.com/react-router/web/example/auth-workflow
import React from "react";
import {
BrowserRouter as Router,
Route,
Link,
Redirect,
withRouter
} from "react-router-dom";
////////////////////////////////////////////////////////////
// 1. Click the public page
// 2. Click the protected page
// 3. Log in
// 4. Click the back button, note the URL each time
const AuthExample = () => (
<Router>
<div>
<AuthButton />
<ul>
<li>
<Link to="/public">Public Page</Link>
</li>
<li>
<Link to="/protected">Protected Page</Link>
</li>
</ul>
<Route path="/public" component={Public} />
<Route path="/login" component={Login} />
<PrivateRoute path="/protected" component={Protected} />
</div>
</Router>
);
const fakeAuth = {
isAuthenticated: false,
authenticate(cb) {
this.isAuthenticated = true;
setTimeout(cb, 100); // fake async
},
signout(cb) {
this.isAuthenticated = false;
setTimeout(cb, 100);
}
};
const AuthButton = withRouter(
({ history }) =>
fakeAuth.isAuthenticated ? (
<p>
Welcome!{" "}
<button
onClick={() => {
fakeAuth.signout(() => history.push("/"));
}}
>
Sign out
</button>
</p>
) : (
<p>You are not logged in.</p>
)
);
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route
{...rest}
render={props =>
fakeAuth.isAuthenticated ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: "/login",
state: { from: props.location }
}}
/>
)
}
/>
);
const Public = () => <h3>Public</h3>;
const Protected = () => <h3>Protected</h3>;
class Login extends React.Component {
state = {
redirectToReferrer: false
};
login = () => {
fakeAuth.authenticate(() => {
this.setState({ redirectToReferrer: true });
});
};
render() {
const { from } = this.props.location.state || { from: { pathname: "/" } };
const { redirectToReferrer } = this.state;
if (redirectToReferrer) {
return <Redirect to={from} />;
}
return (
<div>
<p>You must log in to view the page at {from.pathname}</p>
<button onClick={this.login}>Log in</button>
</div>
);
}
}
export default AuthExample;