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>
);
}
}
Related
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} />
...
);
};
The code below is a react component called Start. Its function is to take in a players name via a form with onSubmit. It stores the players name in a hook called "player". Then it passes the player name prop to another component called GameBoard. Once the submit button is pressed the browser navigates to the GameBoard component via react-router-dom. The GameBoard Component is then supposed to display the players name that passed to it in the Start component.
The issue I'm having is that the player name state is not being passed into the GameBoard component. When onSubmit is initiated the page changes to the GameBoard but the player name doesn't get passed. Any ideas?
import React, { useState, useEffect } from "react";
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
//styles
import { Wrapper, Content } from "../styles/start.styles";
//component
import GameBoard from "../components/gameBoard.component.jsx";
const Start = (props) => {
const [player, setPlayer] = useState("");
let handleChange = (event) => {
event.preventDefault();
setPlayer(event.target.value);
};
let handleSubmit = () => {
if (player === "") {
alert("Please enter a players name");
} else {
window.history.replaceState(null, "GameBoard", "/gameboard");
}
};
useEffect(() => {
console.log(player);
});
return (
<Router>
<Switch>
<Route exact path="/">
<Wrapper>
<Content>
<h1>Trivia Words</h1>
<h2>Start Menu</h2>
<form onSubmit={handleSubmit}>
<label>Enter Player Name:</label>
<input type="text" onChange={handleChange}></input>
<input type="submit" value="Start"></input>
</form>
</Content>
</Wrapper>
</Route>
<Route exact path="/gameboard">
<GameBoard playerName={player} />
</Route>
</Switch>
</Router>
);
};
export default Start;
GameBoard Component below
import React, { useState } from "react";
//styles
import { Wrapper, Content } from "../styles/gameBoard.styles";
const GameBoard = (props) => {
const [playerName, setPlayerName] = useState(props.playerName);
const [letters, setLetters] = useState([]);
const [triviaQA, setTriviaQA] = useState([]);
const [gameOver, setGameOver] = useState(false);
return (
<Wrapper>
<Content>Player Name: {playerName}</Content>
</Wrapper>
);
};
export default GameBoard;
creating a derived state from props is in general bad practice. you should consume your props directly, and if you need to update its state at Child Component you should pass a setState prop as well:
import React, { useState } from "react";
//styles
import { Wrapper, Content } from "../styles/gameBoard.styles";
const GameBoard = (props) => {
const [letters, setLetters] = useState([]);
const [triviaQA, setTriviaQA] = useState([]);
const [gameOver, setGameOver] = useState(false);
return (
<Wrapper>
<Content>Player Name: {props.playerName}</Content>
</Wrapper>
);
};
export default GameBoard;
also, you seem not using the proper navigation from react-router-dom at your handleSubmit.
you could create a Form Player component and import useHistory and push to the desired route to be able to use useHistory or wrap your component with BrowserRouter:
import React, { useState, useEffect } from "react";
import { BrowserRouter as Router, Switch, Route, useHistory } from "react-router-dom";
//styles
import { Wrapper, Content } from "../styles/start.styles";
//component
import GameBoard from "../components/gameBoard.component.jsx";
const Start = (props) => {
const [player, setPlayer] = useState("");
let handleChange = (event) => {
event.preventDefault();
setPlayer(event.target.value);
};
useEffect(() => {
console.log(player);
});
return (
<Router>
<Switch>
<Route exact path="/">
<Wrapper>
<Content>
<h1>Trivia Words</h1>
<h2>Start Menu</h2>
<PlayerNameForm player={player} handleChange={handleChange} />
</Content>
</Wrapper>
</Route>
<Route exact path="/gameboard">
<GameBoard playerName={player} />
</Route>
</Switch>
</Router>
);
};
export default Start;
const PlayerNameForm = ({player, handleChange}) => {
const history = useHistory();
let handleSubmit = () => {
if (player === "") {
alert("Please enter a players name");
} else {
history("/gameboard");
}
};
return (
<form onSubmit={handleSubmit}>
<label>Enter Player Name:</label>
<input type="text" onChange={handleChange}></input>
<input type="submit" value="Start"></input>
</form>
)
}
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;
I am trying to create a shared global state for all components that an app needs, and instead of relying on props drilling or redux, I am trying to achieve that with the React Context.
Why does my user context not survive when I switch between routes? The application bellow illustrates the issue.
Do I need to use any other hook in conjunction with useContext?
//index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { AuthenticationProvider } from "./AuthenticationProvider";
const Index = () => {
return (
<AuthenticationProvider>
<App />
</AuthenticationProvider>
);
}
ReactDOM.render(<Index />, document.getElementById('root'));
//App.js
import React, { useState, useContext } from 'react';
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import './App.css';
import { AuthenticationContext } from './AuthenticationProvider';
function AddUser() {
const [formUser, setFormUser] = useState("");
const [user, setUser] = useContext(AuthenticationContext);
const handleSubmit = async (event) => {
event.preventDefault();
setUser(formUser);
}
return (
<React.Fragment>
Form user: {formUser}.
<form id="form1" onSubmit={handleSubmit}>
<input type="text" id="user" onChange={event => setFormUser(event.target.value)} />
<input type="submit" value="Save" />
</form>
<br/>
Current user: {user}
<br/>
Back to home
</React.Fragment>
);
}
function Home() {
const [user, setUser] = useContext(AuthenticationContext);
return (
<React.Fragment>
<div className="App">
Hello {user}.
<br/>
Add user
</div>
</React.Fragment>
);
}
function App() {
return (
<Router>
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/add" component={AddUser} />
</Switch>
</Router>
);
}
export default App;
//AuthenticationProvider.js
import React, { useState, createContext } from "react";
const DEFAULT_STATE = "";
export const AuthenticationContext = createContext(DEFAULT_STATE);
export const AuthenticationProvider = ({ children }) => {
const [user, setUser] = useState(DEFAULT_STATE);
return (
<AuthenticationContext.Provider value={[user, setUser]} >
{children}
</AuthenticationContext.Provider>
);
}
The problem is that you used a regular <a> link to navigate through the app and every time you go from Home to addUser the app refreshes. To navigate through the app without refreshing the page use the Link component from react-router-dom
in Home and AddUser change the a links to the Link component
import { Link } from "react-router-dom";
function Home() {
const { user, setUser } = useContext(AuthenticationContext);
return (
<React.Fragment>
<div className="App">
Hello {user}.
<br />
<Link to="/add">Add user</Link> <-- Changed a to Link
</div>
</React.Fragment>
);
}
function AddUser() {
const [formUser, setFormUser] = useState("");
const [user, setUser] = useContext(AuthenticationContext);
const handleSubmit = async (event) => {
event.preventDefault();
setUser(formUser);
}
return (
<React.Fragment>
Form user: {formUser}.
<form id="form1" onSubmit={handleSubmit}>
<input type="text" id="user" onChange={event => setFormUser(event.target.value)} />
<input type="submit" value="Save" />
</form>
<br />
Current user: {user}
<br />
<Link to="/">Back to home</Link> <-- Changed a to Link
</React.Fragment>
);
}
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!