I'm new to React and I've made a <Link>to go to next or previous item from dy datas(for example, if i am on user/2 view, previous link go to user/1 and next link go to user/3), the url is correctly changed but the component is not rendered at all and the datas are not reloaded at all.
I've read that it's due to the component not detecting that the children is not changing state so the parent component does not render.
I've tried to use withRouter but I've got a error : You should not use <Route> or withRouter() outside a <Router> and I'm not understanding what I'm doing so if someone has the solution and some explanation to it I would be grateful :)
App.js :
import React, { Component } from 'react';
import {
Route,
Switch,
withRouter,
} from 'react-router-dom';
import HomePage from './pages/home';
import SinglePage from './pages/single';
class App extends Component {
render() {
return (
<Switch>
<div>
<Route exact path="/" component={HomePage} />
<Route path="/:id" component={SinglePage} />
</div>
</Switch>
);
}
}
export default withRouter(App);
Single.js :
import React, { Component } from 'react';
import Details from '../components/details'
import Header from '../components/header'
import { ProgressBar } from 'react-materialize';
class SinglePage extends Component {
constructor(props) {
super(props);
this.state = {
data: { data: null },
}
}
componentDidMount() {
fetch(`http://localhost:1337/${this.props.match.params.id}`)
.then((res) => res.json())
.then((json) => {
this.setState({
data: json,
});
});
}
render() {
const { data } = this.state;
return (
<div>
<h2> SinglePage </h2>
{!data ? (
<ProgressBar />
) : (
<div>
<Header id={this.props.match.params.id} />
<Details item={data} />
</div>
)}
</div>
);
}
}
export default SinglePage;
Header.js :
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Link, withRouter } from 'react-router-dom';
class Header extends Component {
static propTypes = {
item: PropTypes.shape({
data: PropTypes.string.isRequired,
}).isRequired,
}
render() {
const prev = parseInt(this.props.id) - 1
const next = parseInt(this.props.id) + 1
return (
<div>
<Link to="/"> Retour </Link>
<Link to={`/${prev}`}> Précédent </Link>
<Link to={`/${next}`}> Suivant </Link>
</div>
)
}
}
export default Header;
the solution is pretty-simple. All you need to do is make use of componentWillReceiveProps and check if the param updated, if it did fetch the data again
componentDidMount() {
this.getData(this.props.match.params.id);
}
componentWillReceiveProps(nextProps) {
if(this.props.match.params.id !== nextProps.match.params.id) {
this.getData(nextProps.match.params.id);
}
}
getData = (param) => {
fetch(`http://localhost:1337/${params}`)
.then((res) => res.json())
.then((json) => {
this.setState({
data: json,
});
});
}
Related
i'm using "react with axios" to get data from fake API ( https://jsonplaceholder.typicode.com)
what i want from the code: when i press on a certain user, all user's information should appear to me , while this error "Failed to load resource: the server responded with a status of 404 ()" appear to me and i don't know what i do.
.........................
App.js
.........................
import React, { Component, Fragment } from 'react'
import { BrowserRouter, Routes, Route, NavLink} from 'react-router-dom'
import './App.css';
import Home from './component/Home/Home';
import About from './component/About/About';
import UsersPage from './component/UsersPage/UsersPage';
import UserPage from './component/UserPage/UserPage'
class App extends Component {
render() {
return (
<BrowserRouter>
<Fragment>
<NavLink to='/'> Home</NavLink>
<NavLink to='/users'> Users</NavLink>
<NavLink to='/About'> About</NavLink>
<Routes>
<Route path='/' exact element={<Home />} />
<Route path='/About' exact element={<About />} />
<Route path='/users' exact element={<UsersPage />} />
<Route path='/users/:id' exact element={<UserPage />} />
</Routes>
</Fragment>
</BrowserRouter>
);
}
}
export default App;
.........................
Users-axios.js
.........................
import axios from "axios";
export async function getUsers() {
const response = await
axios.get('https://jsonplaceholder.typicode.com/users');
console.log(response);
console.log(response.data);
return response;
}
export async function getUser(id) {
const respon = await
axios.get('https://jsonplaceholder.typicode.com/users/'+id);
return respon;
}
.........................
UsersPage.js
.........................
import React, { Component } from 'react'
import {getUsers} from '../../Api/Users-axios'
import {NavLink} from 'react-router-dom'
export class UsersPage extends Component {
state = {
users: []
}
componentDidMount = () => {
getUsers().then(respo => {
console.log(respo)
this.setState({
users: respo.data
})
})
.catch(error => {
alert('error mount');
})
}
render() {
return (
<div>
<ul>
{this.state.users.map(user =>
<li key={user.id}>
{user.name}{" "}
{user.id}
<NavLink to={"/users/" + user.id}>View</NavLink>
</li>
)}
</ul>
</div>
)
}
}
export default UsersPage;
.........................
UserPage.js
.........................
import React, { Component } from 'react'
import {getUser} from '../../Api/Users-axios'
import ViewUserComp from '../ViewUserComp/ViewUserComp'
import {useParams} from 'react-router'
export class UserPage extends Component {
state = {
user:{}
}
componentDidMount = () => {
console.log(this.props)
let {id} = useParams;
console.log('my id:' );
console.log(id);
getUser(id).then(response => {
this.setState({
user: response.data
});
})
.catch(error => {
alert('error');
});
}
render() {
return (
<div>
<h2>User Page</h2>
<ViewUserComp user={this.state.user} />
</div>
)
}
}
export default UserPage;
...............................
ViewUserComp.js
...............................
import React from 'react'
function ViewUserComp(props) {
return (
<div>
<p>id: {props.user.id}</p>
<p>Name: {props.user.name}</p>
<p>Email: {props.user.email}</p>
</div>
)
}
export default ViewUserComp;
The error is clear as in the following image
You have to wrap the class component inside of withRouter since hooks won't work with class based components.
......................... UserPage.js .........................
import React, { Component } from 'react'
import {getUser} from '../../Api/Users-axios'
import ViewUserComp from '../ViewUserComp/ViewUserComp'
import {withRouter} from 'react-router'
export class UserPage extends Component {
state = {
user:{}
}
componentDidMount = () => {
console.log(this.props)
const id = this.props.match.params.id; // try this one
console.log('my id:' );
console.log(id);
getUser(id).then(response => {
this.setState({
user: response.data
});
})
.catch(error => {
alert('error');
});
}
render() {
return (
<div>
<h2>User Page</h2>
<ViewUserComp user={this.state.user} />
</div>
)
}
}
export default withRouter(UserPage); // wrap it
In hook, Use useParams(). Example like below.
import {useParams} from 'react-router-dom';
let { id } = useParams();
I am trying to pass a function from one of my components to another inside the tag from react router.
I have three screens (LoginScreen, SelectionScreen, and CreateScreen) in my application. I have a function (submit) in the login screen that I want to pass to the create screen.
When I try to invoke the function that I have passed, I get: TypeError: this.props.history.location.data.submitFunction is not a function
I tried to use this answer from a previous post but it still says the same error. Let me know if you guys need anymore info. Thanks!
LoginScreen.js
import React from 'react';
import { Link } from "react-router-dom";
import { Button } from 'reactstrap';
export default class LoginScreen extends React.Component {
constructor(props){
super(props)
}
...
submit = () => { // this is the function I want to pass to CreateScreen
... do some stuff
}
render(){
return(
<div>
...
<Button color="secondary">
<Link to={{
pathname: "/Selection",
state: {
data: this.RandomData
},
data: {
submitFunction: this.submit
}
}}> Next Screen
</Link>
</Button>
</div>
)}
}
SelectionScreen.js
import React from 'react';
import { Link } from "react-router-dom";
import { Button } from 'reactstrap';
import { withRouter } from 'react-router';
class SelectionScreen extends React.Component {
constructor(props){
super(props)
this.state = {}
}
...
render(){
return(
<div>
...
<Button color="secondary">
<Link to={{
pathname: "/Create",
state: {
data: this.props.history.location.state.data
},
data: {
submitFunction: this.props.history.location.data.submitFunction
}
}}>Next Screen
</Link>
</Button>
</div>
)}
}
export default withRouter(SelectionScreen)
CreateScreen.js
import React from 'react';
import { Link } from "react-router-dom";
import { Button } from 'reactstrap';
import { withRouter } from 'react-router';
class CreateScreen extends React.Component {
constructor(props){
super(props)
this.state = {}
}
...
handleClick = (e) => {
e.preventDefault(e);
this.props.history.location.data.submitFunction(randomData);
}
render(){
return(
<div>
...
<Button color="secondary" onPress={this.handleClick}>
Submit
</Button>
</div>
)}
}
export default withRouter(CreateScreen)
App.js
import React from 'react';
import {
BrowserRouter as Router,
Switch,
Route,
} from "react-router-dom";
import LoginScreen from './LoginScreen.js'
import SelectionScreen from './SelectionScreen.js'
import CreateScreen from './CreateScreen.js'
export default function App() {
return (
<div>
<Router>
<Switch>
<Route exact path="/">
<LoginScreen />
</Route>
<Route exact path="/Selection">
<SelectionScreen />
</Route>
<Route exact path="/Create">
<CreateScreen />
</Route>
</Switch>
</Router>
</div>
)
}
You're missing the state in your example, that is why you're pointing to an undefined value and getting the error, for example your link look like this:
<Link to={{
pathname: "/Selection",
state: {
data: this.RandomData,
submitFunction: this.submit,
},
}}> Next Screen
</Link>
And then in any other component that has the react router props you need to call the submit function like this:
this.props.location.state.submitFunction
Or to call the data object
this.props.location.state.data
Keep in mind that your component should have some validation before calling that, in case you're accessing the page without using the Link component
handleClick = (e) => {
e.preventDefault(e);
if (this.props.location?.state?.submitFunction) {
this.props.location.state.submitFunction(randomData)
}
}
how can i pass the history to the dynamically created routes which are
wrapped with a another component
see the below snippet
import React, {Component} from 'react';
import {BrowserRouter as Router, Switch, Route } from 'react-router-dom';
import Loader from './Loader';
import RootComponent from './RootComponent';
import About from './About';
import Contact from './Contact';
class App extends Component{
state = {
loaderStatus: false,
dynamicRoutes: null
}
componentWillMount(){
this.setState({
loaderStatus: true
})
}
componentDidMount(){
let routes = () => {
let accessedRoutes = [{path:'about', component: () => <About />},{path:'contact', component: () => <Contact />}].map(o => {
return (
<Route
exact={true}
path={o.path}
component={o.component}
>
</Route>
)
})
return accessedRoutes
}
setTimeout(() => {
let output = this.createRoutes(routes)
this.setState({
dynamicRoutes: output,
loaderStatus: false
})
}, 4000)
}
createRoutes = (routes) => {
return (
<RootComponent>
<Switch>
{routes}
</Switch>
</RootComponent>
)
}
render(){
return(
<Fragment>
{
this.state.loaderStatus ?
<Loader />
:
<Router>
{this.state.dynamicRoutes}
</Router>
}
</Fragment>
)
}
}
export default App;
// About.js
import React from 'react';
const about = (props) => {
console.log('props in about', props) // giving empty object
return (
<div>About</div>
)
}
export default about
Change the structure of accessedRoutes to [{path:'about', component:About}].
now you can access to the router props
I'm trying to reuse a component in react to render a table view from mysql. I have my navigation generated by a query that lists all of the tables in the database.
import React, { Component } from 'react';
import { BrowserRouter as Router, NavLink } from "react-router-dom";
import './navigation.scss';
class Navigation extends Component {
state = {
items: []
}
Normalize = name => {
return name.split('_')
.map(part => part.charAt(0).toUpperCase() + part.slice(1))
.join(' ');
}
componentDidMount() {
fetch('http://localhost:3132/api/tables')
.then(data => {
return data.json();
})
.then(json => {
let tables = json.map((table, index) => {
var name = table.table_name;
return <li key={index}>
<NavLink activeClassName="active-nav" to={'/table/' + name} name={name}>{this.Normalize(name)}</NavLink>
</li>;
})
this.setState({ tables: tables });
});
}
render() {
return (
<div className="navigation">
<h2>Tables</h2>
<Router>
<ul>
<li><NavLink exact activeClassName="active-nav" to="/">Home</NavLink></li>
{this.state.tables}
</ul>
</Router>
</div>
)
}
}
export default Navigation;
The links work in that they update the url and the active class gets assigned. However, the 'view' component never changes after initial page load.
import React from 'react';
import { Switch, Route } from 'react-router-dom';
import Home from './components/home/home.component';
import Table from './components/table/table.component';
const Routes = () => {
return (
<div className="main">
<Switch>
<Route exact path="/" component={Home}></Route>
<Route path={"/table/:path"} component={(props) => (<Table {...props} />)}></Route>
</Switch>
</div>
)
}
export default Routes;
Even the static Home route doesn't update when it is active. This is what the Table component looks like:
import React, { Component } from 'react';
class Table extends Component {
state = {};
componentDidMount() {
this.setState({tableName: this.props.match.params.path});
}
render() {
return <h1>{this.state.tableName}</h1>
}
}
export default Table;
What do I have to do to get the 'Table' component to render/update?
Edit:
Tried a functional component as suggested in the comment as such and no luck
import React, { Component } from 'react';
const Table = props => {
return <h1>{props.match.params.path}</h1>
}
export default Table;
I have a static component called Item.js
Routes.js
export default () => (
<Provider store={store}>
<BrowserRouter>
<Switch>
<Route path="/posts" component={Posts} />
<Route path="/form" component={Postform} />
<Route exact path="/" component={App} />
<Route path="/items" component={Items} />
<Route path="/cart" component={Cart} />
<Route path="/page/:id" component={Page} />
</Switch>
</BrowserRouter>
</Provider>
);
In the above page component, I want to load item.js or any other page depending on whats passed to the URL params in as "id" in the page component.
import React, { Component } from 'react';
import Navbar from './Navbar';
import Item from './pages/Item';
class Page extends Component {
componentDidMount() {
const { id } = this.props.match.params;
console.log(id);
}
render() {
return (
<div>
<Navbar />
<div>Hello</div>
</div>
);
}
}
export default Page;
How do I achieve this? I don't know.
Are there any alternative ways of doing it?
Ok, I solved it by following a variation of Johnny Peter's answer.
Page.js
import React, { Component } from 'react'
import Navbar from './Navbar';
import components from './indexPage'
class Page extends Component {
render() {
const { id } = this.props.match.params;
const PageComponent = components.find(comp => comp.id === id).component;
return (
<div>
<Navbar />
<PageComponent/>
</div>
);
}
}
export default Page;
indexPage.js
import Item from './pages/Item'
import Meow from './pages/Meow'
const components = [{
id: 'item',
component: Item
},
{
id: 'meow',
component: Meow
}
]
export default components;
Something like this should work
import React from 'react';
import Navbar from './Navbar';
import Item from './pages/Item';
const components = [{
id: 'your/id/passed/in/param'
component: Item
}]
class Page extends React.Component {
state = {
Component: null,
}
componentDidMount() {
const { id } = this.props.match.params;
this.setState({ Component: components.find(comp => comp.id === id).component })
}
render() {
const { Component } = this.state;
return (
<div>
<Navbar />
<Component />
</div>
);
}
}
export default Page;
You can create an index file like so:
index.js:
import Item from './item';
const pages = {
pageId: Item,
};
export default pages;
And in page component:
import React, { Component } from 'react';
import Navbar from './Navbar';
import Item from './pages/Item';
import pages from './pages';
class Page extends Component {
componentDidMount() {
const { id } = this.props.match.params;
console.log(id);
}
render() {
const { id } = this.props.match.params;
if (pages[id]) {
return pages[id];
}
return (
<div>
<Navbar />
<div>Hello</div>
</div>
);
}
}
export default Page;
in order to dynamically load component based on id.