React - Route with same path, but different parameters change data on update - reactjs

I am creating a page where you can see peoples profiles and all their items, which has pathname="/users/{their id}" and a menu where it can take you to your profile. But my problem is that when you go to a persons profile page, and then to another one, the pathname changes but the data does not get rendered it only changes the pathname and the data remains the same. In order to render the data, you would have to refresh the page and then it shows the new users data. How would I get it so you wouldn't have to refresh the page, so like they click on the user they want to go to, the pathname changes and renders the new data without the page refresh? Also, something happens when you refresh the page when on a user profile, it is supposed to return the users' email address, which it does when you first visit the page, but when you refresh the page it returns an error saying it can't find the email.
Here is the code for the menu part (link to my profile):
import { Meteor } from "meteor/meteor"
import React from "react";
import { withRouter, Link } from "react-router-dom";
import { SubjectRoutes } from "./subjectRoutes/subjectRoutes";
import AddNote from "./AddNote";
class Menu extends React.Component{
render(){
return(
<div>
<div className="scroll"></div>
<div className="menu">
<h1>Menu</h1>
<p><Link to="/">Home</Link></p>
<Link to="/searchNotes">Notes</Link>
<p><Link to="/addNote">Add a Note</Link></p>
<p><Link to={`/users/${Meteor.userId()}`} >My Profile</Link></p>
</div>
</div>
)
}
}
export default withRouter(Menu)
userProfile.js:
import { Meteor } from "meteor/meteor";
import React from "react";
import { withRouter } from "react-router-dom";
import { Tracker } from "meteor/tracker";
import Menu from "./Menu";
import RenderNotesByUserId from "./renderNotesByUserId"
class userProfile extends React.Component{
constructor(props){
super(props);
this.state = {
email: ""
};
}
logoutUser(e){
e.preventDefault()
Accounts.logout(() => {
this.props.history.push("/login");
});
}
componentWillMount() {
Meteor.subscribe('user');
Meteor.subscribe('users');
this.tracker = Tracker.autorun(() => {
const user = Meteor.users.findOne(this.props.match.params.userId)
this.setState({email: user.emails[0].address})
});
}
render(){
return(
<div>
<Menu />
<button onClick={this.logoutUser.bind(this)}>Logout</button>
<h1>{this.state.email}</h1>
<RenderNotesByUserId filter={this.props.match.params.userId}/>
</div>
)
}
}
export default withRouter(userProfile);
Sorry to make this question so long it's just a really weird problem that I am having which I can't seem to find any answers to online.

ComponentWillMount() only runs one time, before your component is rendered. You need to also use ComponentWillReceiveProps() in order to update your state when your props change.
Check out React Component Lifecycle

you can use useLocation in this situation.
let location = useLocation();
useEffect(() => {
dispatch(fetchDetail(id))
dispatch(fetchSuggestions(category))
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [location]);

Related

How to navigate from one component to another by clicking a button using React router

I want to use react-router-dom to navigate my website.
Every component will render <button> whenever onClick will active the switch to the next component.
Main component Login.js will contain all the 4 components inside it.
I need to know what are the ways to do it.
(The first time user must not skip the login process below anyhow)
import React, { Component } from 'react';
import {
BrowserRouter as Router,
Redirect,
Route,
Switch,
} from 'react-router-dom';
import MobileNum from './MobileNum.jsx';
import IdNumber from './IdNum.jsx';
import userEmail from './userEmail';
import CreatePass from './createPassword .jsx';
class Login extends Component {
render() {
return (
<div>
<button onClick={}></button>
</div>
);
}
}
To redirect the user you want to call push on the history object. To get the history object in a class component you can use the withRouter higher-order component, e.g.
class Login extends Component {
render() {
const { history } = this.props;
return (
<div>
<button onClick={() => history.push('foo')}>Login</button>
</div>
);
}
}
export const LoginWithRouter = withRouter(Login);
Source: https://dev.to/kozakrisz/react-router---how-to-pass-history-object-to-a-component-3l0j

Reactjs - how to pass props to Route?

I’m learning React Navigation using React-Router-Dom. I have created a simple app to illustrate the problem:
Inside App.js I have a Route, that points to the url “/” and loads the functional Component DataSource.js.
Inside DataSource.js I have a state with the variable name:”John”. There is also a buttonwith the onclick pointing to a class method that’s supposed to load a stateless component named ShowData.js using Route.
ShowData.js receives props.name.
What I want to do is: when the button in DataSource.js is clicked, the url changes to “/showdata”, the ShowData.js is loaded and displays the props.name received by DataSource.js, and DataSource.js goes away.
App.js
import './App.css';
import {Route} from 'react-router-dom'
import DataSource from './containers/DataSource'
function App() {
return (
<div className="App">
<Route path='/' component={DataSource}/>
</div>
);
}
export default App;
DataSource.js
import React, { Component } from 'react';
import ShowData from '../components/ShowData'
import {Route} from 'react-router-dom'
class DataSource extends Component{
state={
name:' John',
}
showDataHandler = ()=>{
<Route path='/showdata' render={()=><ShowData name={this.state.name}/>}/>
}
render(){
return(
<div>
<button onClick={this.showDataHandler}>Go!</button>
</div>
)
}
}
export default DataSource;
ShowData.js
import React from 'react';
const showData = props =>{
return (
<div>
<p>{props.name}</p>
</div>
)
}
export default showData;
I have tried the following, but, even though the url does change to '/showdata', the DataSource component is the only thing being rendered to the screen:
DataSource.js
showDataHandler = ()=>{
this.props.history.push('/showdata')
}
render(){
return(
<div>
<button onClick={this.showDataHandler}>Go!</button>
<Route path='/showdata' render={()=>{<ShowData name={this.state.name}/>}}/>
</div>
)
}
I also tried the following but nothing changes when the button is clicked:
DataSource.js
showDataHandler = ()=>{
<Route path='/showdata' render={()=>{<ShowData name={this.state.name}/>}}/>
}
render(){
return(
<div>
<button onClick={this.showDataHandler}>Go!</button>
</div>
)
}
How can I use a nested Route inside DataSource.js to pass a prop to another component?
Thanks.
EDIT: As user Sadequs Haque so kindly pointed out, it is possible to retrieve the props when you pass that prop through the url, like '/showdata/John', but that's not what I'd like to do: I'd like that the url was just '/showdata/'.
He also points out that it is possible to render either DataSource or ShowData conditionally, but that will not change the url from '/' to '/showdata'.
There were multiple issues to solve and this solution worked as you wanted.
App.js should have all the routes. I used Route params to pass the props to ShowData. So, /showdata/value would pass value as params to ShowData and render ShowData. And then wrapped the Routes with BrowserRouter. And then used exact route to point / to DataSource because otherwise DataSource would still get rendered as /showdata/:name has /
DataSource.js will simply Link the button to the appropriate Route. You would populate DataSourceValue with the appropriate value.
ShowData.js would read and display value from the router prop. I figured out the object structure of the router params from a console.log() of the props object. It ended up being props.match.params
App.js
import { BrowserRouter as Router, Route } from "react-router-dom";
import DataSource from "./DataSource";
import ShowData from "./ShowData";
function App() {
return (
<div className="App">
<Router>
<Route exact path="/" component={DataSource} />
<Route path="/showdata/:name" component={ShowData} />
</Router>
</div>
);
}
export default App;
DataSource.js
import React, { Component } from "react";
import ShowData from "./ShowData";
class DataSource extends Component {
state = {
name: " John",
clicked: false
};
render() {
if (!this.state.clicked)
return (
<button
onClick={() => {
this.setState({ name: "John", clicked: true });
console.log(this.state.clicked);
}}
>
Go!
</button>
);
else {
return <ShowData name={this.state.name} />;
}
}
}
export default DataSource;
ShowData.js
import React from "react";
const ShowData = (props) => {
console.log(props);
return (
<div>
<p>{props.name}</p>
</div>
);
};
export default ShowData;
Here is my scripts on CodeSandbox. https://codesandbox.io/s/zen-hodgkin-yfjs6?fontsize=14&hidenavigation=1&theme=dark
I figured it out. At least, one way of doing it, anyway.
First, I added a route to the ShowData component inside App.js, so that ShowData could get access to the router props. I also included exact to DataSource route, so it wouldn't be displayed when ShowData is rendered.
App.js
import './App.css';
import {Route} from 'react-router-dom'
import DataSource from './containers/DataSource'
import ShowData from './components/ShowData'
function App() {
return (
<div className="App">
<Route exact path='/' component={DataSource}/>
{/* 1. add Route to ShowData */}
<Route path='/showdata' component={ShowData}/>
</div>
);
}
export default App;
Inside DataSource, I modified the showDataHandler method to push the url I wanted, AND added a query param to it.
DataSource.js
import React, { Component } from 'react';
class DataSource extends Component{
state={
name:' John',
}
showDataHandler = ()=>{
this.props.history.push({
pathname:'/showdata',
query:this.state.name
})
}
render(){
return(
<div>
<button onClick={this.showDataHandler}>Go!</button>
</div>
)
}
}
export default DataSource;
And, finally, I modified ShowData to be a Class, so I could use state and have access to ComponentDidMount (I guess is also possible to use hooks here, if you don't want to change it to a Class).
Inside ComponentDidMount, I get the query param and update the state.
ShowData.js
import React, { Component } from 'react';
class ShowData extends Component{
state={
name:null
}
componentDidMount(){
this.setState({name:this.props.location.query})
}
render(){
return (
<div>
<p>{this.state.name}</p>
</div>
)
}
}
export default ShowData;
Now, when I click the button, the url changes to '/showdata' (and only '/showdata') and the prop name is displayed.
Hope this helps someone. Thanks.

ReactJS - JSON objects in arrays

I am having a little problem and can't seem to understand how to fix it. So I am trying to create a pokemon app using pokeapi. The first problem is that I can't get my desired objects to display. For example I want to display {pokemon.abilities[0].ability}, but it always shows Cannot read property '0' of undefined but just {pokemon.name} or {pokemon.weight} seems to work. So the problem appears when there is more than 1 object in an array.
import React, {Component} from 'react';
export default class PokemonDetail extends Component{
constructor(props){
super(props);
this.state = {
pokemon: [],
};
}
componentWillMount(){
const { match: { params } } = this.props;
fetch(`https://pokeapi.co/api/v2/pokemon/${params.id}/`)
.then(res=>res.json())
.then(pokemon=>{
this.setState({
pokemon
});
});
}
render(){
console.log(this.state.pokemon.abilities[0]);
const { match: { params } } = this.props;
const {pokemon} = this.state;
return (
<div>
{pokemon.abilities[0].ability}
<img src={`https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/${params.id}.png`} />
</div>
);
}
}
And also some time ago I added the router to my app, so I could pass id to other components, but the thing is I want to display pokemonlist and pokemondetail in a single page, and when you click pokemon in list it fetches the info from pokeapi and display it in pokemondetail component. Hope it makes sense.
import React, { Component } from 'react';
import { BrowserRouter as Router, Route} from 'react-router-dom';
import './styles/App.css';
import PokemonList from './PokemonList';
import PokemonDetail from './PokemonDetail';
export default class App extends Component{
render(){
return <div className="App">
<Router>
<div>
<Route exact path="/" component={PokemonList}/>
<Route path="/details/:id" render={(props) => (<PokemonDetail {...props} />)}/>
</div>
</Router>
</div>;
}
}
In case componentWillMount(), An asynchronous call to fetch data will not return before the render happens. This means the component will render with empty data at least once.
To handle this we need to set initial state which you have done in constructor but it's not correct. you need to provide default values for the abilities which is an empty array.
So change it to
this.state = {
pokemon: {
abilities: []
}
}
And then inside render method before rendering you need to verify that it's not empty
render() {
return (
(this.state.pokemon.abilities[0]) ?
<div>
{console.log(this.state.pokemon.abilities[0].ability)}
<img src={`https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/1.png`} />
</div> :
null
);
}
It is common in React that you always need to safe-check for existence of data before rendering, especially when dealing with API data. At the time your component is rendered, the state is still empty. Thus this.state.pokemon.abilities is undefined, which leads to the error. this.state.pokemon.name and this.state.pokemon.weight manage to escape same fate because you expect them to be string and number, and don't dig in further. If you log them along with abilities, they will be both undefined at first.
I believe you think the component will wait for data coming from componentWillMount before being rendered, but sadly that's not the case. The component will not wait for the API response, so what you should do is avoid accessing this.state.pokemon before the data is ready
render(){
const { match: { params } } = this.props;
const {pokemon} = this.state;
return (
<div>
{!!pokemon.abilities && pokemon.abilities[0].ability}
<img src={`https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/${params.id}.png`} />
</div>
);
}

Can't get React Router's 4 Link working

Here is the code of component where problem occurs:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Redirect, Link, withRouter } from 'react-router-dom'
import * as actions from 'actions';
class DashBoard extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
this.props.dispatch(actions.deleteItems());
this.props.dispatch(actions.fetchItems());
}
render() {
let { items } = this.props;
let key = 0;
let renderItems = () => {
if (!items) {
return
}
return items.map((item) => {
let { action, author } = item.logs[item.logs.length - 1];
return (
<div className="dashboard-item">
<h3>{item.name}</h3>
<div className="info-container">
<span>Amount: {item.number}</span>
<span>{item.state}</span>
</div>
<span className="created">{`${action} by ${author}`}</span>
<span className="last-log">{`Last log: ${item.logs[0].action} by ${item.logs[0].author}`}</span>
<div className="buttons">
<Link to='/'>Edit</Link>
<Link to={`/items/${item.id}/edit`}>Delete</Link>
</div>
</div>
);
})
}
if (this.props.auth.token) {
return (
<div className="dashboard-container">
{renderItems()}
</div>
);
} else {
this.props.dispatch(actions.setError('You must log in.'))
return <Redirect to='/' />
}
}
}
export default withRouter(connect(
(state) => {
return state;
}
)(DashBoard));
I got Redirect working, but clicking a link just changes url in browser, and actually I still see my dashboard component. If I enter localhost:3000/items/random id/edit i get right result. Creating and clicking a link does nothing. Nothing changes visually except url bar. withRouter hack seems to not work for me. However entering url directly works. How can I fix this?
EDIT : Route definition
import React, { Component } from 'react';
import { Route, Link } from 'react-router-dom';
import { connect } from 'react-redux';
import Home from 'Home';
import Dashboard from 'Dashboard';
import EditItemForm from 'EditItemForm';
import NewItemForm from 'NewItemForm';
export class Main extends Component {
constructor(props) {
super(props);
}
render() {
let { auth, error } = this.props;
let renderError = () => {
if (error) {
return (
<div className="error">
<p>{error}</p>
</div>
)
} else {
return (<div></div>)
}
}
return (
<div>
{renderError()}
<Route exact={true} path="/" component={Home} />
<Route path="/dashboard" component={Dashboard} />
<Route path="/items/:id/edit" component={EditItemForm} />
<Route path="/items/new" component={NewItemForm}/>
</div>
);
}
}
export default connect(
(state) => {
return state;
}
)(Main);
EDIT #2: Figured out that clicking a Link even doesnt change pathname in route > location > pathname if look into React DevTools
It seems to me that there is nothing wrong with your code relative to the use of React Router that you shown here.
I would suggest that you simplify your application and you post the main Router component, it should be fine because you see your dashboard, but it can help.
Try to do a fake hello word route and do the transition from your dashboard to there..
Usually the most likely thing you could have here is a nested component that is blocking the update of the route. Usually putting withRouter in every connected component should work. Alternatively you could try to add withRouter in every component that is nested with the route.
I think there should be a elegant why to solve this, but maybe this can help you know where this problem comes from.
Then try to check if your third party libraries support RR4.
Good luck!
I think that you may need to use a switch above all routes in a component because even I had a similar issue before. however that works in react router v3. Hope this helps u :) cheers
React Router 4
React Router v4 Unofficial Migration Guide
So I just rolled back to v3. Thank you guys for trying to help!

React doesn't change to LoggedInView when Meteor user logs in

I am developing a React + Meteor application and I'm having trouble with the user login functionality.
I have a header navbar that displays a different component based on whether or not the user is logged in.
Like this:
export default class Header extends Component {
constructor(props) {
super(props)
this.state = {
user: Meteor.user()
}
}
render() {
return (
<header className="main-header">
<nav className="navbar navbar-static-top">
<div className="navbar-custom-menu">
{this.state.user() !== null ? <LoggedInNavigation /> : <LoggedOutNavigation />}
</div>
</nav>
</header>
)
}
}
Now this works but it doesn't change upon a user being logged in. I have to refresh the page in order to change the views (which obviously is not ideal).
Here is my login code:
Meteor.loginWithPassword(this.state.email, this.state.password, (error) => {
if (error)
this.setState({ meteorError: "Error: " + error.reason })
else {
this.setState({ meteorError: "" })
// Handle successful login
}
})
The problem is these two blocks of code sit in different components.
The first block is in imports/ui/components/main-layout/Header and the second block is in imports/ui/components/authentication/Login.
As I said, the problem is that the user can log in but the view doesn't change according to the authentication state. What's the best practice to solving this?
EDIT:
Here is the hierarchy of components:
1 - LoggedOutNav
MainLayout -> Header -> LoggedOutNav
2 - Login Code
MainLayout -> Routes -> (Route path="/login" component={Login}) -> LoginForm
The problem here is that the constructor of your class will only run once and never again as long as the component is mounted. So even though Meteor.user() will change, your state won't. The component will rerender when a) the props change or b) your state changes e.g. when you call setState. We can leverage a) through meteors createContainer HOC (react-meteor-data) to wrap your Header class and set a reactive data context for it. When the data changes, the props for Header will change and the component rerenders. In code that would be something like:
import { Meteor } from 'meteor/meteor';
import { createContainer } from 'meteor/react-meteor-data';
import React, { Component, PropTypes } from 'react';
class HeaderComponent extends Component {
render() {
const { user } = this.props;
return (
<header className="main-header">
<nav className="navbar navbar-static-top">
<div className="navbar-custom-menu">
{user ? <LoggedInNavigation /> : <LoggedOutNavigation />}
</div>
</nav>
</header>
)
}
}
export const Header = createContainer(() => {
// assuming you have a user publication of that name...
Meteor.subscribe('users/personalData');
return {
user: Meteor.user(),
};
}, HeaderComponent);

Resources