I need to be able to display the component on the home path "/" while I have "/signIn" modal up. However, after my user is signed in, I would like to replace the "/" component with the "/internal" component. I know how to use to render components exclusively, but I need a solution where I am rendering both inclusively and exclusively. My signIn modal must appear above the "/" component, and after the user is signed in I need the "/" component to be switched with the "/internal" component. I am only being able to do one or the other, is there a way to use both inclusive and exclusive routing in React-Router 4? Thank you.
What I tried:
<Route path="/" component={ExternalContainer} />
<Route path="/signIn" component={SignInModal} />
<Switch>
<Route path="/" component={ExternalContainer} />
<Route path="/internal" component={InternalContainer} />
</Switch>
What I have now but does not work:
<Route path="/" component={ExternalContainer} />
<Route path="/signIn" component={SignInModal} />
<Route path="/internal" component={InternalContainer} />
I think you are overcomplicating your routing structure. In theory, if you want to use a modal for your SignInModal component, then it does not need its own Route. You can just toggle its display within your ExternalContainer component. Then upon filling out the form and clicking the submit button in your SignInModal, just redirect to the InternalContainer.
See a very basic integration like this: https://codesandbox.io/s/old-snowflake-djbc3
Working code:
index.js
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter, Route } from "react-router-dom";
import ExternalContainer from "./ExternalContainer";
import InternalContainer from "./InternalContainer";
import "./styles.css";
function App() {
return (
<BrowserRouter>
<Route path="/" component={ExternalContainer} exact />
<Route path="/internal" component={InternalContainer} />
</BrowserRouter>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
ExternalContainer.js
import React from "react";
import SignInModal from "./SignInModal";
class ExternalContainer extends React.Component {
state = {
modalOpen: false
};
toggleModal = () => {
this.setState({
modalOpen: !this.state.modalOpen
});
};
render() {
return (
<div>
<h4>Welcome to the App</h4>
<button onClick={this.toggleModal}>Sign In</button>
<SignInModal
open={this.state.modalOpen}
toggleModal={this.toggleModal}
/>
</div>
);
}
}
export default ExternalContainer;
SignInModal.js
import React from "react";
import Modal from "react-modal";
import { withRouter } from "react-router-dom";
class SignInModal extends React.Component {
state = {
username: "",
password: ""
};
handleOnChange = e => {
this.setState({
[e.target.name]: e.target.value
});
};
handleOnSubmit = e => {
e.preventDefault();
const { username, password } = this.state;
if (username && password) {
this.props.history.push("/internal");
}
};
render() {
return (
<Modal
isOpen={this.props.open}
onRequestClose={this.props.toggleModal}
style={{
overlay: {
background: "rgba(0, 0, 0, 0.3)"
},
content: {
background: "white",
top: "45%",
left: "50%",
right: "auto",
bottom: "auto",
transform: "translate(-50%,-50%)",
width: "200px",
textAlign: "center"
}
}}
ariaHideApp={false}
>
<form onSubmit={this.handleOnSubmit}>
<div>
<label>Name</label>{" "}
<input name="username" onChange={this.handleOnChange} />
</div>
<div>
<label>Password</label>{" "}
<input name="password" onChange={this.handleOnChange} />
</div>
<button type="submit">Sign In</button>
</form>
</Modal>
);
}
}
export default withRouter(SignInModal);
InternalContainer.js
import React from "react";
const InternalContainer = () => {
return (
<div>
<h4>You have reached the internal container</h4>
</div>
);
};
export default InternalContainer;
Related
I am trying to implement this feature in my React JS app where if I login successfully, the UI would display the Logout button in the navbar. But if I click on the Logout, I would be logged out from the main UI and redirected to the Login Page where the navbar won't have any Logout Button. Currently, when I log in to the main UI successfully, then I have to refresh the browser tab to show the logout button. It does not appear automatically once I log in successfully.
This is my header Component:
import React, { Component } from "react";
import { BrowserRouter as Router, Link } from "react-router-dom";
import AuthenticationService from "../Login/AuthenticationService";
class HeaderComponent extends Component {
render() {
const isUserLoggedIn = sessionStorage.getItem("authenticatedUser");
console.log(isUserLoggedIn);
return (
<nav className="navbar navbar-expand-lg ">
<Router>
<div className="container-fluid">
<a className="navbar-brand" href="#">
<span className="header-title">TODO APP</span>
</a>
<ul className="navbar-nav d-flex flex-row ms-auto me-3">
{isUserLoggedIn && (
<Link
to="/login"
onClick={() => {
window.location.href = "/login";
}}
>
{" "}
<button
className="btn btn-success logout-button"
onClick={AuthenticationService.logout}
>
Logout
</button>
</Link>
)}
</ul>
</div>
</Router>
{" "}
</nav>
);
}
}
export default HeaderComponent;
The isUserLoggedIn is a boolean value that is retrieved from the session storage. If isUserLoggedIn is true then the Logout button would be displayed on the Navbar.
const isUserLoggedIn = sessionStorage.getItem("authenticatedUser");
The AuthenticationService.js file is:
import React, { Component } from "react";
class AuthenticationService extends Component {
registerSuccessfulLogin(username, password) {
console.log("Login Successful");
sessionStorage.setItem("authenticatedUser", username);
console.log(sessionStorage.getItem("authenticatedUser"));
}
isUserLoggedIn() {
let user = sessionStorage.getItem("authenticatedUser");
if (user === null) {
return false;
} else {
return true;
}
// return true;
}
logout() {
sessionStorage.removeItem("authenticatedUser");
}
getLoggedInUserName() {
let user = sessionStorage.getItem("authenticatedUser");
if (user === null) return "";
return user;
}
}
export default new AuthenticationService();
The App.js component is:
import "./App.css";
import DropDownMenu from "./components/DropdownMenu/DropDownMenu";
import HeaderComponent from "./components/Header/HeaderComponent";
import LoginComponent from "./components/Login/LoginComponent";
import {
BrowserRouter as Router,
Routes,
Route,
useParams,
Link,
} from "react-router-dom";
import "./styles.css";
import "./loginStyle.css";
import "./Header.css";
import ErrorComponent from "./components/search-options/ErrorComponent";
import AuthenticatedRoute from "./components/DropdownMenu/AuthenticatedRoute";
function App() {
return (
<div className="App">
<HeaderComponent />
<Router>
<Routes>
<Route path="/" element={<LoginComponent />}></Route>
{/* <Route path="/login" element={<Navigate to="/LoginComponent" />} /> */}
<Route path="/login" default element={<LoginComponent />}></Route>
<Route
path="/pst"
element={
<AuthenticatedRoute>
<DropDownMenu />
</AuthenticatedRoute>
}
></Route>
<Route path="*" element={<ErrorComponent />}></Route>
</Routes>
</Router>
</div>
);
}
export default App;
I am not sure why do I have to refresh the Tab to show the Logout button on to the NavBar.
EDIT
I have added the Login component for further reference:
import axios from "axios";
import { data } from "jquery";
import React, { Component } from "react";
import { useNavigate } from "react-router";
import App from "../../App";
import HeaderComponent from "../Header/HeaderComponent";
import GetUserIp from "../tracking/GetUserIp";
import TrackLoginActivity from "../tracking/TrackLoginActivity";
import AuthenticationService from "./AuthenticationService";
class LoginComponent extends Component {
constructor(props) {
super(props);
this.state = {
username: "",
password: "",
ipAddress: "",
hasLoginFailed: false,
isUserLoggedIn: false,
};
this.handleCredentialChange = this.handleCredentialChange.bind(this);
this.submitLogin = this.submitLogin.bind(this);
}
handleCredentialChange(event) {
console.log(event.target.name);
this.setState({ [event.target.name]: event.target.value });
}
submitLogin(event) {
event.preventDefault();
var session_url =
"login check api url";
GetUserIp.retrieveIpAddress().then((response) => {
this.setState({ ipAddress: response.data.ip });
});
console.log(this.state.ipAddress);
axios
.post(
session_url,
{},
{
auth: {
username: this.state.username,
password: this.state.password,
},
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
}
)
.then((response) => {
console.log("Authenticated");
console.log(this.props);
AuthenticationService.registerSuccessfulLogin(
this.state.username,
this.state.password
);
this.props.navigate(`/pst`);
//track user login activity into the DB Entity, smart_tool_login_logs
TrackLoginActivity.trackSuccessfulLogin(
this.state.username,
this.state.ipAddress
);
})
.catch((error) => {
console.log("Error on autheication");
this.setState({ hasLoginFailed: true });
});
}
render() {
return (
<div className="login-app">
<form action="" className="login-form" onSubmit={this.submitLogin}>
<div className="container">
<input
type="text"
placeholder="username"
name="username"
className="username-value"
maxLength={30}
autoComplete="on"
value={this.state.username}
onChange={this.handleCredentialChange}
/>
<input
type="password"
placeholder="password"
name="password"
className="password-value"
maxLength={30}
autoComplete="on"
value={this.state.password}
onChange={this.handleCredentialChange}
/>
{this.state.hasLoginFailed && (
<div className="login-fail-message">
Invalid username/password
</div>
)}
<div className="login-btn"></div>
<button className="btn btn-success login-btn" type="submit">
Login
</button>
</div>
</form>
</div>
);
}
}
function WithNavigate(props) {
let navigate = useNavigate();
return <LoginComponent {...props} navigate={navigate} />;
}
export default WithNavigate;
Your header component is a stateless component, it has neither props nor internal state, so it is only rendered once with the initial value of isUserLoggedIn.
If you want the header component to react to the login status, it needs to be notified in some way that the value has changed. The easiest way to do so is to store this information in the state of your App component, then passing it down as a prop to the HeaderComponent.
// App.js
import "./App.css";
import DropDownMenu from "./components/DropdownMenu/DropDownMenu";
import HeaderComponent from "./components/Header/HeaderComponent";
import LoginComponent from "./components/Login/LoginComponent";
import {
BrowserRouter as Router,
Routes,
Route,
useParams,
Link,
} from "react-router-dom";
import "./styles.css";
import "./loginStyle.css";
import "./Header.css";
import ErrorComponent from "./components/search-options/ErrorComponent";
import AuthenticatedRoute from "./components/DropdownMenu/AuthenticatedRoute";
import AuthenticationService from "../Login/AuthenticationService";
function App() {
const [isUserLoggedIn, setIsUserLoggedIn] = useState(AuthenticationService.isUserLoggedIn()) // use the session storage to get the initial state's value
function onLoginSuccessful() {
setIsUserLoggedIn(true); // update the state to notify React the value has changed
}
return (
<div className="App">
<HeaderComponent isUserLoggedIn={isUserLoggedIn} /> {/* pass down the state value as a prop */}
<Router>
<Routes>
<Route path="/" element={<LoginComponent onLoginSuccessful={onLoginSuccessful}/>}></Route>
{/* <Route path="/login" element={<Navigate to="/LoginComponent" />} /> */}
<Route path="/login" default element={<LoginComponent onLoginSuccessful={onLoginSuccessful} />}></Route>
<Route
path="/pst"
element={
<AuthenticatedRoute>
<DropDownMenu />
</AuthenticatedRoute>
}
></Route>
<Route path="*" element={<ErrorComponent />}></Route>
</Routes>
</Router>
</div>
);
}
export default App;
// HeaderComponent.js
import React, { Component } from "react";
import { BrowserRouter as Router, Link } from "react-router-dom";
import AuthenticationService from "../Login/AuthenticationService";
class HeaderComponent extends Component {
render() {
const isUserLoggedIn = this.props.isUserLoggedIn; // retrieve the value from the props
console.log(isUserLoggedIn);
return (
<nav className="navbar navbar-expand-lg ">
<Router>
<div className="container-fluid">
<a className="navbar-brand" href="#">
<span className="header-title">TODO APP</span>
</a>
<ul className="navbar-nav d-flex flex-row ms-auto me-3">
{isUserLoggedIn && (
<Link
to="/login"
onClick={() => {
window.location.href = "/login";
}}
>
{" "}
<button
className="btn btn-success logout-button"
onClick={AuthenticationService.logout}
>
Logout
</button>
</Link>
)}
</ul>
</div>
</Router>
{" "}
</nav>
);
}
}
export default HeaderComponent;
Note that you also have to pass a callback to LoginComponent to be called once the user successfully logs in. Using state updates, React gets notified that the value of isUserLoggedIn has changed and will re-render the components that depend on this value, including HeaderComponent.
EDIT: I updated the implementation of LoginComponent with the callback I mentionned
// LoginComponent.js
import axios from "axios";
import { data } from "jquery";
import React, { Component } from "react";
import { useNavigate } from "react-router";
import App from "../../App";
import HeaderComponent from "../Header/HeaderComponent";
import GetUserIp from "../tracking/GetUserIp";
import TrackLoginActivity from "../tracking/TrackLoginActivity";
import AuthenticationService from "./AuthenticationService";
class LoginComponent extends Component {
constructor(props) {
super(props);
this.state = {
username: "",
password: "",
ipAddress: "",
hasLoginFailed: false,
isUserLoggedIn: false,
};
this.handleCredentialChange = this.handleCredentialChange.bind(this);
this.submitLogin = this.submitLogin.bind(this);
}
handleCredentialChange(event) {
console.log(event.target.name);
this.setState({ [event.target.name]: event.target.value });
}
submitLogin(event) {
event.preventDefault();
var session_url =
"login check api url";
GetUserIp.retrieveIpAddress().then((response) => {
this.setState({ ipAddress: response.data.ip });
});
console.log(this.state.ipAddress);
axios
.post(
session_url,
{},
{
auth: {
username: this.state.username,
password: this.state.password,
},
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
}
)
.then((response) => {
console.log("Authenticated");
console.log(this.props);
AuthenticationService.registerSuccessfulLogin(
this.state.username,
this.state.password
);
// add this line to your `LoginComponent` so that
// onLoginSuccessul is called when user is logged in
this.props.onLoginSuccessful();
this.props.navigate(`/pst`);
//track user login activity into the DB Entity, smart_tool_login_logs
TrackLoginActivity.trackSuccessfulLogin(
this.state.username,
this.state.ipAddress
);
})
.catch((error) => {
console.log("Error on autheication");
this.setState({ hasLoginFailed: true });
});
}
render() {
return (
<div className="login-app">
<form action="" className="login-form" onSubmit={this.submitLogin}>
<div className="container">
<input
type="text"
placeholder="username"
name="username"
className="username-value"
maxLength={30}
autoComplete="on"
value={this.state.username}
onChange={this.handleCredentialChange}
/>
<input
type="password"
placeholder="password"
name="password"
className="password-value"
maxLength={30}
autoComplete="on"
value={this.state.password}
onChange={this.handleCredentialChange}
/>
{this.state.hasLoginFailed && (
<div className="login-fail-message">
Invalid username/password
</div>
)}
<div className="login-btn"></div>
<button className="btn btn-success login-btn" type="submit">
Login
</button>
</div>
</form>
</div>
);
}
}
function WithNavigate(props) {
let navigate = useNavigate();
return <LoginComponent {...props} navigate={navigate} />;
}
export default WithNavigate;
I'm implementing authentication to my React project and I'm trying to have the Login form in my Home component. This is how my Home component looks with the Login component attached to it.
import Login from './Login'
import * as React from 'react';
import Box from "#material-ui/core/Box";
const Home = () => {
return (
<div>
<Box sx={{
width: 600,
height: 150,
backgroundColor: 'lightgrey',
borderRadius: 16
}}>
{<h1>Welcome to our Podcast App</h1>}
{<h2>Feel free to share your favorites or have fun listening to the ones available.</h2>}
</Box><br/>
< Login />
</div>
);
};
export default Home;
The problem is, I want to redirect my users to the podcasts page and, history.push is not working. This is how the Login component looks.
import React from "react";
import { connect } from "react-redux";
import { loginUser } from "../actions/index";
import Button from "#material-ui/core/Button";
import { Box } from "#material-ui/core";
class Login extends React.Component {
state = {
email: "",
password: "",
error: false
};
handleChange = (event) => {
this.setState({
[event.target.name]: event.target.value
});
};
handleSubmit = (event) => {
event.preventDefault();
const { email, password } = this.state;
this.props
.dispatchLoginUser({ email, password })
.then(() => this.props.history.push('/podcasts'))
.catch(() => this.setState({ error: true }));
};
render() {
return (
<Box sx={{ p: 2, border: '1px solid grey' }}>
<form onSubmit={this.handleSubmit} >
<h3>Log In</h3>
<p>{this.state.error && "Invalid email or password"}</p>
<fieldset>
<label htmlFor='email'>
Email:
</label>
<input
type='text'
name='email'
id='email'
onChange={this.handleChange}
value={this.state.email}
/>
</fieldset>
<fieldset>
<label htmlFor='password'>
Password:
</label>
<input
type='password'
name='password'
id='password'
onChange={this.handleChange}
value={this.state.password}
/>
</fieldset><br/>
<Button variant="contained" size="small" color="inherit" type='submit'>Log In</Button>
</form>
</Box>
);
}
}
const mapDispatchToProps = (dispatch) => {
return {
dispatchLoginUser: (credentials) => dispatch(loginUser(credentials))
};
};
export default connect(null, mapDispatchToProps)(Login);
If I try to log in from the Login page, it will work. But it doesn't work from the Home page. I also tried adding history to my project like another post suggested but it still doesn't work. Here's the code:
//history.js
import { createBrowserHistory } from 'history';
export default createBrowserHistory();
//index.js
import { createBrowserHistory } from 'history';
const history = createBrowserHistory();
ReactDOM.render(
<Provider store={ store }>
<Router history={history} >
<App />
</Router>
</Provider>
//The routes in App.js
<Route exact path='/' component={Home}/>
<Route exact path='/signup' component={Signup} />
<Route exact path='/login' component={Login} />
Is it possible to make history.push work from the Home component? Any suggestion will be welcomed. Thank you all.
In react-router-dom version 6
useHistory() is replaced by useNavigate()
import {useNavigate} from 'react-router-dom';
const navigate = useNavigate();
navigate('/home')
Is it possible to make history.push work from the Home component?
Yes, absolutely it is. You just need to destructure it from the props object in the Home component. For this the Home component needs to actually have a defined/declared props object.
const Home = (props) => {
// access props.history
return (
...
<Login history={props.history} />
...
);
};
or
const Home = ({ history }) => {
// access history
return (
...
<Login history={history} />
...
);
};
Home is a function component, so it can also use the useHistory hook and pass it along.
const Home = () => {
const history = useHistory();
return (
...
<Login history={history} />
...
);
};
If you want to add framer-motion to a functional component, then add <motion.div ...> to the render method. Easy. But that's not possible in the case of non-functional components. Say I want to add a page-transition effect between the Login component and the anotherPage functional component, how is that done?
Here is some code for reference:
import React from "react"
import ReactDOM from "react-dom"
import { Route, Switch, BrowserRouter} from "react-router-dom";
import { AnimatePresence} from "framer-motion";
const AnotherPage = () => {
return <h1>another page</h1>
};
class Login extends Component {
constructor() {
...
}
handleLogin = async (event) => {
...
}
handleInputChange = (event) => {
...
}
render() {
return (
<form onSubmit={this.handleLogin}>
<input type="text" name="email"
value={this.state.email}
onChange={this.handleInputChange}/>
<input type="text" name="password"
value={this.state.password}
onChange={this.handleInputChange}/>
<button className="btn" onClick={this.handleLogin}>login</button>
</form>
);
}
};
const Routers = () => {
return (
<BrowserRouter>
<AnimatePresence>
<Switch>
<Route exact path="/login" component={Login} />
<Route exact path="/anotherPage" component={AnotherPage} />
</Switch>
</AnimatePresence>
</BrowserRouter>
);
};
ReactDOM.render(
<React.StrictMode>
<Routers/>
</React.StrictMode>,
document.getElementById("root")
)
It's not an issue of functional & non-functional components. You need to place your AnimatePresence component after Switch. If you place it before the Switch, then your AnimatePresence doesn't aware of the route change.
A workaround could be placing your AnimatePresence at the beginning of each component.
const AnotherPage = () => {
return (
<AnimatePresence>
<h1>another page</h1>
</AnimatePresence>
);
};
export default AnotherPage;
export default class Login extends Component {
constructor() {
// ...
}
handleLogin = async (event) => {
// ...
}
handleInputChange = (event) => {
// ...
}
render(){
return (
<AnimatePresence>
<h1>login page</h1>
</AnimatePresence>
);
}
}
I'm making a react application and whenever I search for something(eg cat) on the homepage, the url changes to search/cat and the forward, backward button work normally & help me switch between the homepage and the cat search page ...but when i search for something again (eg rat) after(homepage->cat) so the url changes to search/rat? and now when i press the back button the url changes to search/rat and i'm on the same page then if i press back button again the url becomes search/cat but the page still has the results of the rat search and if i press back again ,the homepage appears..why is this happening?I think it's because of the ? that appears at the end of the url..Please help
after searching cat
after searching for rat
after pressing the back button
after pressing the back button
after pressing the back button
This is the code of the search bar
import React, { Component } from "react";
import "./styles/searchBar.scss";
import "font-awesome/css/font-awesome.min.css";
import { withRouter } from "react-router-dom";
import SearchForm from "./SearchForm";
import { connect } from "react-redux";
import { fetchRandomPhotos } from "../redux/actions/randomPhotoAction";
class SearchBar extends Component {
state = {
searchQuery: "",
};
componentDidMount() {
this.props.fetchRandomPhotos();
}
handleChange = (event) => {
this.setState({ searchQuery: event.target.value });
};
handleSubmit = (event) => {
//event.preventDefault();
this.props.history.push(`/search/${this.state.searchQuery}`);
};
handleProfile = () => {
this.props.history.push(`/public/${this.props.photo.user.username}`);
};
render() {
const { photo } = this.props;
return !photo ? (
<div className="search-bar-container">
<div className="search-bar-area">
<div className="about-foto-fab">
<h1>Foto-Fab</h1>
<p>The internet's source of freely-usable images.</p>
<p>Powered by creator everywhere</p>
</div>
<SearchForm
onSubmit={this.handleSubmit}
onChange={this.handleChange}
/>
</div>
</div>
) : (
<div
className="search-bar-container"
style={{ backgroundImage: `url("${photo.urls.full}")` }}
>
<div className="black-layer"></div>
<div className="search-bar-area">
<div className="about-foto-fab">
<h1>Foto-Fab</h1>
<p>The internet's source of freely-usable images.</p>
<p>Powered by creator everywhere</p>
</div>
<SearchForm
onSubmit={this.handleSubmit}
onChange={this.handleChange}
/>
</div>
<div className="picture-info">
<div className="photographer">
<p onClick={this.handleProfile}>
<strong>Photo</strong> by {""}
<strong>{photo.user.name}</strong>
</p>
</div>
</div>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
photo: state.randomPhotoState.photo,
};
};
export default connect(mapStateToProps, { fetchRandomPhotos })(
withRouter(SearchBar)
);
This is the App.js
import React from "react";
import Navbar from "./components/Navbar";
import { BrowserRouter, Switch, Route, Redirect } from "react-router-dom";
import Home from "./pages/Home";
import LoginPage from "./pages/LoginPage";
import ProfilePage from "./pages/ProfilePage";
import SearchPage from "./pages/SearchPage";
import PublicUserProfilePage from "./pages/publicUserProfilePage";
import MobileNavigation from "./components/MobileNavigation";
import AboutPage from "./pages/AboutPage";
function App() {
return (
<BrowserRouter>
<Navbar />
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/login" component={LoginPage} />
<Route exact path="/profile" component={ProfilePage} />
<Route exact path="/search/:searchQuery" component={SearchPage} />
<Route exact path="/about" component={AboutPage} />
<Route
exact
path="/public/:username"
component={PublicUserProfilePage}
/>
<Redirect to="/" />
</Switch>
<MobileNavigation />
</BrowserRouter>
);
}
export default App;
search form component
import React, { Component } from "react";
export class SearchForm extends Component {
render() {
const { onSubmit, onChange } = this.props;
return (
<form className="search-form" onSubmit={onSubmit}>
<input
type="text"
placeholder="Search free high-resolution photos"
onChange={onChange}
/>
<button type="submit">
<i className="fa fa-search"></i>
</button>
</form>
);
}
}
export default SearchForm;
import React, { Component } from "react";
import "./styles/searchBar.scss";
import "font-awesome/css/font-awesome.min.css";
import { withRouter } from "react-router-dom";
import SearchForm from "./SearchForm";
import { connect } from "react-redux";
import { fetchRandomPhotos } from "../redux/actions/randomPhotoAction";
class SearchBar extends Component {
state = {
searchQuery: "",
};
componentDidMount() {
this.props.fetchRandomPhotos();
}
handleChange = (event) => {
this.setState({ searchQuery: event.target.value });
};
handleSubmit = (event) => {
event.preventDefault();
if (this.state.searchQuery) {
this.props.history.push(`/search/${this.state.searchQuery}`);
}
};
handleProfile = () => {
this.props.history.push(`/public/${this.props.photo.user.username}`);
};
render() {
const { photo } = this.props;
return !photo ? (
<div className="search-bar-container">
<div className="search-bar-area">
<div className="about-foto-fab">
<h1>Foto-Fab</h1>
<p>The internet's source of freely-usable images.</p>
<p>Powered by creator everywhere</p>
</div>
<SearchForm
onSubmit={this.handleSubmit}
onChange={this.handleChange}
/>
</div>
</div>
) : (
<div
className="search-bar-container"
style={{ backgroundImage: `url("${photo.urls.full}")` }}
>
<div className="black-layer"></div>
<div className="search-bar-area">
<div className="about-foto-fab">
<h1>Foto-Fab</h1>
<p>The internet's source of freely-usable images.</p>
<p>Powered by creator everywhere</p>
</div>
<SearchForm
onSubmit={this.handleSubmit}
onChange={this.handleChange}
/>
</div>
<div className="picture-info">
<div className="photographer">
<p onClick={this.handleProfile}>
<strong>Photo</strong> by {""}
<strong>{photo.user.name}</strong>
</p>
</div>
</div>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
photo: state.randomPhotoState.photo,
};
};
export default connect(mapStateToProps, { fetchRandomPhotos })(
withRouter(SearchBar)
);
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;