React-Router How to push to next page after checks - reactjs

In my code I have a few checks after a user has entered some data, then I want to load the next route if everything is correct, what is the best way to do so?
This is my current Route page:
<Router history = {browserHistory}>
<Route exact path="/" component={() => <MainMenu userData={this.state.userData}/>}/>
<Route exact path="/login" component = {Login} />
<Route exact path="/pastMeetingsPlay/:meetingCode" component={(props) => <PastMeetingsPlay user={this.state.userData.UserID} {...props}/>} />
<Route exact path="/meetingMode/:meetingCode" component={(props) => <MeetingMode user={this.state.userData.UserID} {...props}/>} />
</Router>
the user submits a form then there inputs are checked and if all the required checks pass then it should load meetingMode page
EDIT:
import React, { Component } from 'react';
import './App.css';
import MeetingMode from'./MeetingMode';
import NavbarMenu from './Navbar';
import Popup from "reactjs-popup";
import axios from 'axios';
import {withRouter, history, Redirect, Route} from "react-router";
class MeetingModeLoad extends Component{
constructor(props)
{
super(props);
this.state ={
meeting:{},
value:0
};
this.handleSubmit = this.handleSubmit.bind(this);
this.handleChange = this.handleChange.bind(this);
}
async handleSubmit(event)
{
event.preventDefault();
let meetingLoadCode = this.state.value
try{
let getter = await axios.get(`https://smartnote1.azurewebsites.net/api/meetings/${meetingLoadCode}`)
let meetingLocal = getter.data
this.setState({meeting:meetingLocal})
if(meetingLocal.Status == 2)
{
console.log("please join meeting that is planned or under going")
}
else
{
console.log("/meetingMode/" + this.state.meeting.MeetingID);
this.props.history.push("/meetingMode/" + this.state.meeting.MeetingID)
}
}
catch(error)
{
console.error(error)
}
}
handleChange(event)
{
this.state.value = event.target.value
console.log(this.state.value)
}
render()
{
return(
<div>
<Popup
trigger={<button className="meetingModeButton" onClick={() => this.handleClick}>Meeting Mode</button>}
modal
closeOnDocumentClick>
<div className="newNote">
<header style={{background: "#F7941D" }}> Meeting Mode</header>
<form onSubmit={this.handleSubmit}>
<label> Enter Meeting Code :
<input type="text" name="type" className="inputBox" onChange={this.handleChange}/>
</label>
<input type="submit" value="Submit" />
</form>
</div>
{console.log(this.state.meeting)}
</Popup>
</div>
)
}
}
export default withRouter (MeetingModeLoad)

Looks like you forgot to wrap your component into withRouter. It is mandatory to access the history prop
Place this in the component from which you try to push:
import { withRouter } from 'react-router'
...
export default withRouter(YourComponent);
And push by using this in your component:
this.props.history.push("/meetingMode/" + meetingCode);

Related

TypeError: this.props is not a function (react router / switch)

I've got a switch in App.js to render different body components. "Landing" is the landing page body component. It's got a text field to enter a zip code, and when you click the submit button, it renders the "Events" page body component that displays some stuff.
When the Events component loads, I need it to be able to access the zip code that the user entered on the Landing page, so I lifted "zip" to App.js, which is the parent of Landing and Events.
I'm using Route and Switch so I can render the different body components. It's not getting that far though:
TypeError: this.props.onZipChange is not a function
No clue why it doesn't recognize onZipChange as a function in App.js. I won't bother showing the Events.js file because it's not even being rendered before I get the TypeError. The second I try to type into the input box in Landing.js, it triggers the input box's onChange attr, which calls this.handleChangeZip, which tries to call App.js' onZipChange function through this.props, which it's not recognizing.
Any thoughts?
App.js:
import React, { PropTypes, Component } from "react";
import "./styles/bootstrap/css/bootstrap.min.css";
import "./styles/App.css";
import "./index.css";
import Header from "./routes/Header";
import Body from "./routes/Body";
import { Switch, Route, NavLink } from "react-router-dom";
import Landing from "./routes/Landing";
import Events from "./routes/Events";
import Help from "./routes/Help";
class App extends Component {
constructor(props) {
super(props);
this.state = { zip: "" };
this.handleZipChange = this.handleZipChange.bind(this);
}
handleZipChange = newZip => {
this.setState({ zip: newZip });
};
render() {
const currZip = this.state.zip;
return (
<div className="App">
<Header zip={currZip} />
<Switch>
<Route
exact
path="/"
render={props => <Landing {...props} zip={currZip} />}
onZipChange={this.handleZipChange}
/>
<Route
exact
path="/Events"
render={props => <Events {...props} zip={currZip} />}
onZipChange={this.handleZipChange}
/>
<Route exact path="/Help" component={Help}></Route>
</Switch>
</div>
);
}
}
export default App;
Landing.js:
import { Redirect } from "react-router-dom";
import React from "react";
import "../styles/App.css";
class Landing extends React.Component {
constructor(props) {
super(props);
this.state = { value: "", toEvents: false };
this.handleSubmit = this.handleSubmit.bind(this);
this.handleChangeZip = this.handleChangeZip.bind(this);
}
handleChangeZip(e) {
this.props.onZipChange(e.target.value);
}
handleSubmit(event) {
this.setState(() => ({
toEvents: true
}));
event.preventDefault();
}
render() {
if (this.state.toEvents === true) {
return <Redirect to="/Events" />;
}
return (
<div>
<div className="main-body">
<div className="main-question" id="thisfontonly">
What city are you looking for?
</div>
<div className="textbar-and-button">
<input
onChange={this.handleChangeZip}
value={this.props.zip}
type="text"
name="city"
id="citylabel"
style={{ fontSize: "24pt" }}
className="rcorners"
/>
<div className="buttons">
<input
onClick={this.handleSubmit}
type="submit"
name="submit"
value="Go!"
id="submit"
className="button"
/>
</div>
</div>
</div>
</div>
);
}
}
export default Landing;

problem with router and privaterouter / history

Hello I have a problem redirecting to a page doing a verification on a privaterouter
Unhandled Rejection (TypeError): Cannot read property 'push' of
undefined
on this line:
this.props.history.push ("/ home");
my component:
import React, { Component } from 'react';
import api from '../services/api';
import { withRouter } from 'react-router';
class LoginForm extends Component {
constructor(props){
super(props);
this.state = {
login:'',
password:'',
};
this.onSubmit = this.onSubmit.bind(this);
this.onChange = this.onChange.bind(this);
}
async onSubmit(e){
e.preventDefault();
const {login, password } = this.state;
const response = await api.post('/login', { login,password });
const user = response.data.user.login;
const {jwt} = response.data;
localStorage.setItem('token', jwt);
localStorage.setItem('user', user);
this.props.history.push("/home");
}
onChange(e){
this.setState({[e.target.name]: e.target.value});
}
render() {
const { errors, login, password, isLoading } = this.state;
return (
<form onSubmit={this.onSubmit}>
<label htmlFor="login">Login</label>
<input type="text" name="login" id="login" value={login} onChange={(e) => this.onChange(e)} placeholder="Informe seu login" />
<label htmlFor="password">Senha</label>
<input type="password" name="password" id="password" value={password} onChange={(e) => this.onChange(e)} placeholder="Informe sua senha"/>
<button className="btnEnt" type="submit">Entrar</button>
</form>
)
}
}
export default withRouter (LoginForm);
my router:
import React from 'react';
import { BrowserRouter, Switch, Route } from 'react-router-dom';
import Login from './pages/login/index';
import DashBoard from './pages/dashboard/index';
import PrivateRoute from './auth';
export default function Routes(){
return(
<BrowserRouter>
<div>
<Switch>
<Route path="/" exact component = {Login}/>
<PrivateRoute path="/home" component = {DashBoard}/>
</Switch>
</div>
</BrowserRouter>
);
}
my private route or auth router:
import React from 'react';
import { Route, Redirect} from 'react-router-dom';
const isAuth = () => {
console.log('a');
if(localStorage.getItem('token') !== null) {
console.log('true')
return true;
}
return false;
};
const PrivateRoute = ({component: Component, ...rest}) => {
return (
<Route
{...rest}
render={props =>
isAuth() ? (
<Component {...props} />
): (
<Redirect
to={{
pathname: '/',
state: {message: 'Usuário não autorizado'}
}}
/>
)}
/>
);
}
export default PrivateRoute;
I basically have my router and I also check if the user is allowed to enter this page, but I'm having trouble making it work.
Well, I read your code and here is my answer
You just need import withRouter from react-router-dom and not from react-router ;)
import { withRouter } from "react-router-dom";
And use it like
export default withRouter(LoginForm);

Component loses data from states on reload

I am building a basic form setup, but need to grab what the user's data was before the change so they can edit from that data. On the first page load, everything works as it should, but as soon as I refresh the page (F5) I get the error: Unhandled Rejection (TypeError): Cannot read property 'userFirstName' of null. The userId is not being grabbed the second time I refresh the page. If I leave the page and go to another page and come back, it loads again as it should. How do I get the page to always re-grab the data on page refresh?
I am using React.js, Firebase, and React-router
Settings.js:
import React, { Component } from 'react';
import fire from '../../config/Fire.js';
export default class Settings extends Component {
constructor(props) {
super(props)
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.update = this.update.bind(this);
this.userDatabase = fire.database().ref().child('users');
this.state = {
userId: this.props.user.uid,
userFirstName: '',
};
}
componentWillMount(){
fire.database().ref('/users/' + this.state.userId).once('value').then(function(snapshot) {
var first_name = (snapshot.val().userFirstName);
this.setState({
userFirstName: first_name,
})
}.bind(this));
}
handleChange(e) {
this.setState({ [e.target.name]: e.target.value });
}
handleSubmit(e) {
e.preventDefault();
}
update(e){
e.preventDefault();
//update here
fire.auth().createUserWithEmailAndPassword(this.state.email, this.state.password).then((u)=>{ this.props.history.push('/about') }).catch((error) => {
alert(error);
});
}
render() {
return (
<div className="m-container">
<h1>Hello, {this.state.userFirstName}</h1>
<hr/>
<p>Here are your settings details:</p>
<label for="first-name">First Name: </label>
<br/>
<input
value={this.state.userFirstName}
onChange={this.handleChange}
type="text"
name="first-name"
id="first-name"
placeholder={this.state.userFirstName}
/>
<br/>
<br/>
<button
type="submit"
className="m-btn"
onClick={this.signup}>Submit</button>
</div>
);
}
}
Index.js (Routing for passing in userId as props to pages):
import React, { Component } from 'react';
import { Route, Switch, Redirect } from 'react-router-dom';
import Home from './main/Home';
import About from './main/About';
import LoginContainer from '../components/LoginContainer';
import RegisterContainer from '../components/RegisterContainer';
import Resolved from './main/Resolved';
import Settings from './main/Settings';
export default class Routes extends Component {
render() {
return (
<Switch>
<Route path="/" exact component={Home} />
<Route path="/about" exact component={About} />
<Route path="/register" render={()=> <RegisterContainer user={this.props.user} />} />
<Route path="/login" render={()=> <LoginContainer user={this.props.user} />} />
<Route path="/resolved" exact render={()=>(
this.props.user ? (<Resolved user={this.props.user} />) :
(alert("You must log in to visit this page."), (<Redirect to="/login"/>))
)} />
<Route path="/account/settings" exact render={()=>(
this.props.user ? (<Settings user={this.props.user} />) :
(alert("You must log in to visit this page."), (<Redirect to="/login"/>))
)} />
</Switch>
);
}
};
you should call your firebase connection in componentDidMount, but not in the constructor. You can read the details when to use React Lifecycles. Even though it is out of date (not react 16 article), it is a a great resource. https://engineering.musefind.com/react-lifecycle-methods-how-and-when-to-use-them-2111a1b692b1
class App extends React.Component {
constructor() {}
componentDidMount() {
// firebase call
// do setState
}
render() {
return <div></div>
}
}
Solved following: https://javebratt.com/firebase-user-undefined/
Made Asynchronous call because the uid was null for a few moment before fetching data, and error was triggered. Just had to wait for data to load before error went off. Also am grabbing the uid a new way via fire.auth().currentUser.uid.
Code I changed in Settings.js:
componentDidMount(){
// Asynchronous call fixes uid being null on reload
fire.auth().onAuthStateChanged( user => {
if (user) {
fire.database().ref('/users/' + fire.auth().currentUser.uid).once('value').then(function(snapshot) {
this.setState({
userFirstName: (snapshot.val().userFirstName),
userLastName: (snapshot.val().userLastName),
userEmail: (snapshot.val().userEmail),
userPhone: (snapshot.val().userPhone),
userAddress: (snapshot.val().userAddress),
userZip: (snapshot.val().userZip),
userProfilePicUrl: (snapshot.val().userProfilePicUrl)
})
}.bind(this));
}
});

Is it possible to realize communication between independent components in ReactJS?

I have two components. These components are located on different routes. 'CreateItem' component gives me possibility to create new items. I store new items to array. Array will include new created items. I want send this modified array to component 'Main' where I will iterate those items and display them as list.
Here is my code:
1) index.js file:
import React, { Component } from 'react';
import { render } from 'react-dom';
import { BrowserRouter, Route } from 'react-router-dom'
import {Main} from "./components/Main"
import {CreateItem} from "./components/CreateItem"
import {CurrentItem} from "./components/CurrentItem"
render(
<BrowserRouter>
<div>
<Route exact path="/" component={Main}/>
<Route path="/create_item" component={CreateItem}/>
<Route path="/item" component={CurrentItem}/>
</div>
</BrowserRouter>,
document.getElementById('app')
);
2) Main.js
import React from 'react';
import { withRouter } from 'react-router-dom';
import { Route, browserHistory } from 'react-router-dom';
export class Main extends React.Component {
render(){
const ToCreateItemPageButton = () => (
<Route render={({ history}) => (
<button type='button' onClick={() => { history.push('/create_item') }}>Move to create item page!</button>
)}
/>
)
return (
<div>
<h1>Main Page</h1>
<ToCreateItemPageButton/>
</div>
);
}
}
3) CreateItem.js
import React from 'react';
import { Route, browserHistory } from 'react-router-dom';
export class CreateItem extends React.Component {
constructor(props) {
super(props);
this.state = {
mainArray: [],
item: {},
item_id: 0,
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({item: {item_id: this.state.item_id,
name:event.target.value}});
}
handleSubmit(event) {
if (this.state.item.name.length > 0) {
this.state.mainArray.push(this.state.item);
this.state.item_id = this.state.item_id + 1;
let data = JSON.stringify(this.state.mainArray);
localStorage.setItem('mainObject', data);
this.setState(
{mainArray : this.state.mainArray,
item_id : this.state.item_id,}
);
event.preventDefault();
}
}
render(){
const ToMainPageButton = () => (
<Route render={({ history}) => (
<button type='button' onClick={() => { history.push('/') }}>Move to main page!</button>
)}
/>
)
return (
<div>
<h1>Create new item</h1>
<ToMainPageButton/>
<form onSubmit={this.handleSubmit}>
<label>
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
</div>
);
}
}
So all I want is to have possibility to transfer my mainArray from 'CreateItem' component to 'Main' component.
You could redirect and send data like that:
this.props.history.push({
pathname: '/target-path',
data: [/*your data*/]
});
and receive it on the target component so:
const { data } = this.props.location;
Short answer - Yes it's possible using container component like in fiddle example.
So the idea is to keep you array of items in a container state and pass it to "iterated" component as well as a callback for handling incoming item.
// container component
class Container extends React.Component {
constructor(props){
super(props);
this.state = {
array: ['Hello', 'Stack', 'Overflow']
}
this.handleOnAdd = this.handleOnAdd.bind(this)
}
handleOnAdd(item){
this.setState({
array: [...this.state.array, item]
})
}
render() {
return (
<div>
// pass shared props to "display" component
<ChildOneDisplay items={this.state.array} />
// pass a callback to CreateItem component
<ChildTwoAdd onAdd={this.handleOnAdd} />
</div>
);
}
}
// display component
class ChildTwoAdd extends React.Component{
constructor(props){
...
this.handleAdd = this.handleAdd.bind(this)
}
handleAdd(){
this.props.onAdd(this.state.item);
...
}
render(){
return(
<div>
<input
name="item"
type="text"
onChange={this.handleChange}
value={this.state.item}
/>
<button onClick={this.handleAdd}>Add Me</button>
</div>
)
}
}
So all you need is to wrap your two routes with a container component and pass props to both of them as i did in this example.
// So your container should look like the following one
render(){
return (
<div>
<Route exact path="/" render={() => <Main items={this.state.array}}/>
<Route path="/create_item" render={() => <CreateItem onAdd={this.handleAdd}/>}/>
</div>
)
}
// And render it as the following
<BrowserRouter>
<Container />
<Route path="/item" component={CurrentItem}/>
</BrowserRouter>
Moreover i suggest looking at redux - this is the library for managing your app state.
Thanks!

React router changing landing view to search results view

EDIT: Added component to view
I am trying to render the correct page view based on a submit button from a search. Currently I have a search bar at the top of the view and a default landing page in the middle. When the user searches I want to change the default landing page to the profile page they are searching for.
I am assuming I will have to remove the component from Main and replace it with {this.props.children}. Then in I will have to add maybe a around the submit button? The problem with this so far is that Profile then doesn't get the necessary props it needs from SearchBar.
My view ideally will show at the top and in the main container. When the user searches will change to containing the correct user information searched for which is passed to from -> ->
Below are my current Routs and Main components
import React, { Component } from 'react';
import { Router, Route, Redirect, IndexRoute, Link, hashHistory } from 'react-router';
import Main from '../components/Main';
import Profile from '../components/Profile';
import Landing from '../components/Landing';
class Routes extends Component {
render() {
return (
<Router history={ hashHistory }>
<Route path="/" component={Main}>
<Route path="Profile" component={Profile}></Route>
<Route path="Landing" component={Landing}></Route>
<IndexRoute component={Landing}></IndexRoute>
</Route>
</Router>
)
}
}
export default Routes;
Main
import React, { Component } from 'react';
import Routes from '../utils/Routes';
import Footer from './Footer';
import Profile from './Profile';
import SearchBar from './SearchBar';
import Landing from './Landing';
class Main extends Component {
constructor(props) {
super(props);
this.state = {
profileName: ''
}
}
handleProfileChange(profileName) {
this.setState( { profileName });
//replace <Profile /> with {this.props.children} maybe
}
render() {
return (
<div className="container-fluid">
<div className="row">
<SearchBar history={this.props.history} handleProfileChange={this.handleProfileChange.bind(this)} />
</div>
<div className="row">
<Profile name={this.state.profileName} />
</div>
<div className="row">
<Footer />
</div>
</div>
)
}
}
export default Main;
SearchBar
import React, { Component, PropTypes } from 'react';
import Profile from './Profile';
import TopNav from './TopNav';
import sass from '../scss/application.scss';
import { Router, Route, Redirect, IndexRoute, Link, hashHistory } from 'react-router';
class SearchBar extends Component {
constructor(props){
super(props)
this.state = {
name: ''
}
}
handleChange(e) {
this.setState({
name: e.target.value
});
}
handleSubmit(e) {
e.preventDefault();
console.log("searching for NAME " + this.state.name);
let profileName = this.state.name;
profileName = profileName.toLowerCase().trim();
//Cap the first letter in the name and add the rest of the name
profileName = profileName.charAt(0).toUpperCase() + profileName.substr(1);
console.log("NEW NAME " + profileName);
this.props.handleProfileChange(profileName);
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit.bind(this)}>
<input type="text" placeholder="Enter Name"
name="name"
value={this.state.name}
onChange={this.handleChange.bind(this)} />
<button className="btn btn-success" type="submit">Search</button>
</form>
</div>
)
}
}
SearchBar.propTypes = {
handleProfileChange: React.PropTypes.func.isRequired,
}
export default SearchBar;
Here's a basic sketch, if I understand you correctly. This would be the search bar and the submit.
class SearchProfiles extends Component {
static contextTypes = {
router: PropTypes.object,
};
state = { search: '' };
onSubmit = e => {
e.preventDefault();
// maybe do your search here, or inject it directly.
this.context.router.push(`/profiles/${this.state.search}`);
};
onChange = e => this.setState({ search: e.target.value });
render() {
return (
<form onSubmit={this.onSubmit}>
<input onChange={this.onChange} value={this.state.search} />
<button type="submit">Search Profiles</button>
</form>
)
}
}
You would have to add a route for the search, though.
<Route path="/profiles/:search" component={Profile} />

Resources