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));
}
});
Related
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.
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;
please, what is the best way in React how to achieve:
submit form (and..)
redirect to another page (and..)
have some props from the origin form here?
I have discovered two possibilities how to redirect:
Source article: https://tylermcginnis.com/react-router-programmatically-navigate/
1) with React Router: history.push()
2) with React Router: <Redirect />
1) With history.push(): Redirecting works but i have no idea how to add custom props to redirected page.
2) With <Redirect />: adding custom props works (in this way):
<Redirect to={{ pathname: '/products', state: { id: '123' } }} />
But redirecting does not work to me, I keep receiving errors after submission.
Source code:
import React from 'react';
import './App.css';
import { withRouter, Redirect } from 'react-router-dom'
class App extends React.Component {
state = {
toDashboard: false,
}
handleSubmit = () => {
this.setState(() => ({
toDashboard: true
}));
}
render() {
if (this.state.toDashboard === true) {
return <Redirect to={{
pathname: '/products', state: { id: '123' }
}} />
}
return (
<div>
<h1>Register</h1>
<form onSubmit={this.handleSubmit}>
<button type="submit">Submit</button>
</form>
</div>
);
}
}
export default withRouter(App);
Errors:
Warning: You tried to redirect to the same route you're currently on: /products"
Form submission canceled because the form is not connected
What is the best way how to achieve my target, please?
You need to cancel the default submit action.
so change you handleSubmit method to
handleSubmit = (e) => {
e.preventDefault();
this.setState({
toDashboard: true
});
}
What is finally working fine to me is code below here.
From App.js it is routed to Products.js, then i click on the button and it is redirected to NotFound.js and i can reach props "state: { id: 123 }" and i display it here.
Hope it will help to someone who is looking for some working submission patern.
App.js
import React from 'react';
import './App.css';
import { Route, Switch } from 'react-router-dom';
import Products from './Products';
import NotFound from './NotFound';
import Home from "./Home";
class App extends React.Component {
render() {
return (
<div>
<Switch>
<Route path="/products" component={Products} />
<Route path="/notfound" component={NotFound} />
<Route path="/" exact component={Home} />
</Switch>
</div>
);
}
}
export default App;
Products.js
import React, { Component } from "react";
class Products extends Component {
handleSubmit = (e) => {
e.preventDefault();
this.props.history.push({ pathname: '/notfound', state: { id: 123 } });
}
render() {
console.log(this.props);
return (
<div>
<h1>Products</h1>
<form onSubmit={this.handleSubmit}>
<button type="submit">Submit</button>
</form>
</div>
);
}
}
export default Products;
NotFound.js
import React from "react";
const NotFound = (props) => {
console.log(props);
return (
<div>
<h1>Not Found</h1>
<h2>{props.location.state.id}</h2>
</div>
);
};
export default NotFound;
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);
I have a child component:
import * as React from 'react';
import Select from 'react-select';
import { Link } from 'react-router-dom';
import { Button } from '../controls/Button/Button';
import { ISelectedItem } from '../../interfaces/ISelectedItem';
import * as service from "../../helpers/service";
export interface IProps{
onClickRender: (selectedItem: ISelectedItem) => void;
}
export interface IState {
customerData: ISelectedItem[];
selectedItem: ISelectedItem;
}
export class DropDownSearch extends React.Component<{}, IState>{
constructor(props: any) {
super(props);
this.state = ({
customerData: [],
selectedItem: { shortName: '', description: '' }
});
}
componentDidMount() {
service.fetchJson<ISelectedItem[]>("/api/customers")
.then((json) =>{
this.setState({
customerData: json
});
});
}
handleChange = (selectedItem: any) => {
this.setState({
selectedItem
});
}
render() {
const { selectedItem } = this.state;
const value = selectedItem && selectedItem;
return (
<div>
<Select
name="form-field-name"
value={this.state.selectedItem}
onChange={this.handleChange}
options={this.state.customerData}
labelKey="shortName"
/>
<Link to={{
path "/dashboard/" + this.state.selectedItem.shortName,
state: { detail : this.state.selectedItem }
}}>
<Button type="button" className="btn btn-primary" caption="Search" />
</Link>
</div>
);
}
}
I want to pass the this.state.selectedItem to the Dashboard component, which is part of the Route config in the parent component below:
import * as React from 'react';
import { Navbar } from './Navbar/Navbar';
import { ShortNameSelector } from './ShortNameSelector/ShortNameSelector';
import { Dashboard } from './Dashboard/Dashboard';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
export class App extends React.Component<{},{}>{
render(){
return(
<BrowserRouter>
<div className="container">
<Navbar />
<div className="col-lg-12">
<Switch>
<Route exact path="/" component={ShortNameSelector} />
<Route path="/dashboard" component={Dashboard} />
</Switch>
</div>
</div>
</BrowserRouter>
);
}
}
Problem is I'm using Routes to switch components on the button click in my child components. How do I pass the this.state.selectedItem object from child to the Dashboard component (shown in parent component) via Routes?
EDIT:
So I put the state attribute inside Link tag and referenced it in Dashboard component like this.props.location.state.detail and it works. But now I want to persist the data in that route/Dashboard component when I open that link in a new page. How do I go about it?
You can use like this
<Route path="/dashboard/:selectedItem" component={Dashboard} />
So you can dynamically update the selected item in the DOM URL and when you click it, you can use 'this.props.match.params.id' in the 'Dashboard' component to access that value.
Passing object between components via Router in React: I have copied fragment of codes from my project, might be useful to you.
I use NavLink, which supposed pass an object to my InfoComponent
<NavLink to={{
pathname: /menu/${props.data.code},
search: '',
state: { selectedMenu: props.data }
}} color="info" className="btn btn-info btn-success mx-4">Info</NavLink>
In my router, I then received the passed argument in Router as follows, added console log for more clarity
<Route path="/menu/:item" render={(props) => {
console.log("::::::::: " + JSON.stringify(props.location.state.selectedMenu));
return (<InfoComponent selectedMenu={props.location.state.selectedMenu} />);
}} />