I ask you not to throw stones, because everyone once studied) The bottom line is this: I am writing a wishlist for myself and my friends, and I got stuck at a seemingly very stupid moment. The nested component is a header with navigation tabs and an exit button. While I am implementing the framework of the application, therefore, authorization occurs simply by loggedInUser /!loggedInUser. Actually, when you click on the exit button, an empty string is dispatched in loggedInUser and history.push ('/ login'). In the debugger, I see that the dispatch is happening and the location is pushed to the history, but for some reason, the rendering condition will not be fulfilled. I ask the help of knowledgeable people. I suppose that I messed up with routing somewhere, since I read that BrowserRouter for some reason is not used, but without it nothing works at all. In general, thanks in advance!
Entry:
const history = createBrowserHistory()
ReactDOM.render(
<Router history={history}>
<Provider store={store}>
<App />
</Provider>
</Router>,
document.getElementById('root')
);
Routing component:
interface AppProps {
loggedInUser: string;
}
class App extends React.Component<AppProps & RouteComponentProps> {
state = {
loggedInUser: ""
}
render() {
const history = this.props
return (
<>
<BrowserRouter>
<Segment>
<Switch>
<Route history={history} path='/login' render={() => (!this.props.loggedInUser ? <LoginPage /> : <Redirect to="/account" />)}/>
<Route history={history} path='/account' render={() => (this.props.loggedInUser ? <AccountPage/> : <Redirect to="/login" />)}/>
<Route history={history} path='/friendslist' render={() => (this.props.loggedInUser ? <FriendsList/> : <Redirect to="/login" />)}/>
<Redirect from='/' to='/login' />
</Switch>
</Segment>
</BrowserRouter>
</>
)
}
}
const mapStateToProps = (state: StateInterface): AppProps => ({
loggedInUser: state.loggedInUser
})
export default connect(mapStateToProps)(withRouter(App))
Nested component (it is rendered in each of the routed components):
interface HeaderProps extends HeaderPropsFromState{
activeItem: string;
}
interface HeaderPropsFromState{
loggedInUser: string;
}
class Header extends React.Component<HeaderProps & RouteComponentProps> {
state = {
activeItem: this.props.activeItem,
}
handleItemClick = (e: any, { name }: any) => this.setState({ activeItem: name })
render() {
const { activeItem } = this.state
const { match, history } = this.props
return (
<Menu pointing>
<Menu.Item
name='Friends'
active={activeItem === 'Friends'}
onClick={this.handleItemClick}
as={Link}
to="/friendslist"
/>
<Menu.Item
name='Account'
active={activeItem === 'Account'}
onClick={this.handleItemClick}
as={Link}
to="/account"
/>
{activeItem === 'Friends' &&
<Menu.Menu position='right'>
<Menu.Item>
<Input icon='users' iconPosition='left' placeholder='Search users...' />
{/* TODO: create function to filter by friend's name */}
</Menu.Item>
</Menu.Menu>
}
{activeItem === 'Account' &&
<Menu.Menu position='right'>
<Menu.Item>
<Button basic
color='black'
onClick={() => {
console.log(this.props.loggedInUser)
store.dispatch(setLoggedInUser({username: ""}))
history.push('/login')
}}
>Exit</Button>
{/* TODO: create function to exit from account */}
</Menu.Item>
</Menu.Menu>
}
</Menu>
)
}
}
const mapStateToProps = (state: StateInterface): HeaderPropsFromState => ({
loggedInUser: state.loggedInUser
})
export default connect(mapStateToProps)(withRouter(Header))
UPD: Component that includes the header component.
class FriendsList extends React.Component{
users = [...users objects]
render() {
const usersList = this.users.map((user: User, index: number) =>
<OneFriendWishes user={user} key={index}/>
)
return (
<>
<Header activeItem={"Friends"}/>
{usersList}
</>
)
}
}
export default FriendsList
Related
I have a function App in App.js as below
function App() {
return (
<AuthProvider>
<Layout>
<Routes>
{/* Basic routes access allowed by all */}
<Route exact path={RoutesNames.GLOBAL} element={<MainHomePage />} />
<Route exact path={RoutesNames.LOGIN} element={<Login />} />
<Route exact path={RoutesNames.REGISTER} element={<Register />} />
<Route exact path={RoutesNames.CATALOGUE} element={<Catalogue />} />
{/* Protected routes access allowed by user connected */}
<Route exact path={RoutesNames.REGISTRATION_SUCCESS} element={<ProtectedRoute />}>
<Route exact path={RoutesNames.REGISTRATION_SUCCESS} element={<RegistrationSuccess />} />
</Route>
{/* Errors routes */}
<Route path="*" element={<PageNotFound />} />
</Routes>
</Layout>
</AuthProvider>
)
}
The Layout contain the Header bar and the childrens after.
Now when I login from the login page, I'm redirected to Catalogue Page. And I have my AuthProvider who set the isAuth state to true as below :
import React, { Component, createContext } from 'react'
import VerificationUserAuth from '../../utils/VerificationUserAuth'
const AuthContext = createContext()
class AuthProvider extends Component {
constructor(props) {
super(props)
this.state = {
isAuth: false,
}
}
checkAuth = () => {
const user = new VerificationUserAuth().getUserConnected()
if (user) {
this.setState({ isAuth: true })
}
}
render() {
if (!this.state.isAuth) {
this.checkAuth()
}
return (
<AuthContext.Provider value={{ isAuth: this.state.isAuth }}>
{this.props.children}
</AuthContext.Provider>
)
}
}
const AuthConsumer = AuthContext.Consumer
export { AuthProvider, AuthConsumer }
Now the problem is I check in my header who is envelopped by my AuthConsumer if he's authenticated or not and change the menu consequently. But each time I login, i need to refresh the page for Render the AuthProvider who check the authentification.
Btw there is my Header Bar :
export default function HeaderBar() {
return (
<AuthConsumer>
{({ isAuth }) => (
<header className="mb-5">
{isAuth ? (
<p>Connected</p>
) : (
<Navbar.Brand>
<Link to={RoutesNames.LOGIN}>
<Button className="btn btn-rounded btn-primary mr-2">Connexion</Button>
</Link>
<Link to={RoutesNames.REGISTER}>
<Button className="btn btn-rounded btn-normal">S'inscrire</Button>
</Link>
</Navbar.Brand>
)}
</Container>
</Navbar>
</header>
)}
</AuthConsumer>
)
}
So how can I re render my header after login part ? Thank's !
You need to change the isAuth state in a lifecycle component so that means whenever your app is loaded it will be initiated .
Put this function in a lifecycle method :
checkAuth = () => {
const user = new VerificationUserAuth().getUserConnected()
if (user) {
this.setState({ isAuth: true })
}
}
componentDidMount(){
checkAuth()
}
In a functional component :
checkAuth = () => {
const user = new VerificationUserAuth().getUserConnected()
if (user) {
this.setState({ isAuth: true })
}
}
React.useEffect(()=>{
checkAuth()
} , [])
In my ReactJS app, routes are configured in below way:
class App extends React.Component{
constructor(props){
super(props);
this.state={
isLoggedin: false
}
}
componentDidMount(){
if(localStorage.getItem('name'))
{
this.setState({
isLoggedin: true
})}
}
render(){
return(
<>
<Switch>
<Route exact path="/" render={()=><Login isLoggedin={this.state.isLoggedin} />} />
<Route exact path="/login" render={<Login isLoggedin={this.state.isLoggedin} />} />
<Route exact path="/home" render={()=><Home isLoggedin={this.state.isLoggedin} />} />
</Switch></>
);
}
}
In Login.js:
class Login extends React.Component{
render(){
if(this.props.isLoggedin) return <Redirect to="/home" />;
return(
<h1>Login here</h1>
);
}
}
In Home.js:
class Home extends React.Component{
render(){
if(!this.props.isLoggedin) return <Redirect to="/login" />;
return(
<h1>Home</h1>
);
}
}
So what this code will do is that when the user visits the /, it would first go to Login component and as soon as isLoggedin is set to true, it would redirect the user to Home.js. Same thing would happen if user is not loggedin and he tries to access /home, he would be redirected to /login. Since I am using local storage, all of this would happen in flash of eye. It is also working just fine.
But I doubt if it is the best method to achieve my goal. I want to know if there is any more advisable method to do this.
Thanks!!
A more advisable method would be to decouple the auth checks from the components and abstract this into custom route components.
PrivateRoute - if the user is authenticated then a regular Route is rendered and the props are passed through, otherwise redirect to the "/login" path for user to authenticate.
const PrivateRoute = ({ isLoggedIn, ...props }) => {
return isLoggedIn ? <Route {...props} /> : <Redirect to="/login" />;
};
AnonymousRoute - Basically the inverse of the private route. If the user is already authenticated then redirect them to the "/home" path, otherwise render a route and pass the route props through.
const AnonymousRoute = ({ isLoggedIn, ...props }) => {
return isLoggedIn ? <Redirect to="/home" /> : <Route {...props} />;
};
From here you render the Login and Home components into their respective custom route components.
<Switch>
<PrivateRoute
isLoggedIn={this.state.isLoggedIn} // *
path="/home"
component={Home}
/>
<AnonymousRoute
isLoggedIn={this.state.isLoggedIn} // *
path={["/login", "/"]}
component={Login}
/>
</Switch>
* NOTE: The isLoggedIn={this.state.isLoggedIn} prop is only required here since the isLoggedIn state resides in the App component. A typical React application would store the auth state in a React Context or in global state like Redux, and thus wouldn't need to be explicitly passed via props, it could be accessed from within the custom route component.
Full sandbox code:
const PrivateRoute = ({ isLoggedIn, ...props }) => {
return isLoggedIn ? <Route {...props} /> : <Redirect to="/login" />;
};
const AnonymousRoute = ({ isLoggedIn, ...props }) => {
return isLoggedIn ? <Redirect to="/home" /> : <Route {...props} />;
};
class Login extends Component {
render() {
return (
<>
<h1>Login here</h1>
<button type="button" onClick={this.props.login}>
Log in
</button>
</>
);
}
}
class Home extends Component {
render() {
return (
<>
<h1>Home</h1>
<button type="button" onClick={this.props.logout}>
Log out
</button>
</>
);
}
}
export default class App extends Component {
state = {
isLoggedIn: false
};
componentDidMount() {
if (localStorage.getItem("isLoggedIn")) {
this.setState({
isLoggedIn: true
});
}
}
componentDidUpdate(prevProps, prevState) {
if (prevState.isLoggedIn !== this.state.isLoggedIn) {
localStorage.setItem("isLoggedIn", JSON.stringify(this.state.isLoggedIn));
}
}
logInHandler = () => this.setState({ isLoggedIn: true });
logOutHandler = () => this.setState({ isLoggedIn: false });
render() {
return (
<div className="App">
<div>Authenticated: {this.state.isLoggedIn ? "Yes" : "No"}</div>
<ul>
<li>
<Link to="/">/</Link>
</li>
<li>
<Link to="/home">home</Link>
</li>
<li>
<Link to="/login">log in</Link>
</li>
</ul>
<Switch>
<PrivateRoute
isLoggedIn={this.state.isLoggedIn}
path="/home"
render={() => <Home logout={this.logOutHandler} />}
/>
<AnonymousRoute
isLoggedIn={this.state.isLoggedIn}
path={["/login", "/"]}
render={() => <Login login={this.logInHandler} />}
/>
</Switch>
</div>
);
}
}
There is a HeaderComponent and a BodyComponent in my project.
This is the TabComponent from the HeaderComponent:
TabComponent
import React, {Component} from 'react'
import Context from '../../provider'
import {Nav, NavItem, NavLink} from 'reactstrap'
import {Redirect} from 'react-router-dom'
import classnames from 'classnames'
export default class TabComponent extends Component {
render() {
return (
<Context.Consumer>
{context => (
<Nav tabs color='primary'>
<NavItem>
<NavLink
className={classnames({ active: context.activeTab === '1' })}
onClick={() => { context.toggleTab('1'); }}
>
Home
</NavLink>
</NavItem>
<NavItem>
<NavLink
className={classnames({ active: context.activeTab === '2' })}
onClick={() => { context.toggleTab('2'); }}
>
Popular
</NavLink>
</NavItem>
<NavItem>
<NavLink
className={classnames({ active: context.activeTab === '3' })}
onClick={() => { context.toggleTab('3'); }}
>
All
</NavLink>
</NavItem>
</Nav>
)}
</Context.Consumer>
)
}
}
BodyComponent
import React, {Component} from 'react'
import Context from '../provider'
import SwitchTab from './OtherComponents/SwitchTab'
import { BrowserRouter, Route, Redirect } from "react-router-dom";
export default class BodyComponent extends Component {
render() {
return (
<Context.Consumer>
{context => {
return (
<React.Fragment>
<BrowserRouter>
<React.Fragment>
<Route exact path='/' render={() => <Redirect to='/home/' /> } />
<Route eaxt path='/home/' render={() => context.activetab='1'} />
<Route eaxt path='/popular/' render={() => context.activetab='2'} />
<Route eaxt path='/all/' render={() => context.activetab='3'} />
</React.Fragment>
</BrowserRouter>
<SwitchTab />
</React.Fragment>
)
}}
</Context.Consumer>
)
}
}
This is my provider.js which has the Context
import React, {Component} from 'react';
const Context = React.createContext();
class Provider extends Component {
state = {
loggedIn: false,
username: '',
password: '',
loginModalOpen: false,
signupModalOpen: false,
navbarOpen: false,
activeTab: '3',
toggleLoggedIn: () => {
this.setState({loggedIn: !this.state.loggedIn});
},
toggleLoginModal: () => {
this.setState({loginModalOpen: !this.state.loginModalOpen});
},
toggleSignupModal: () => {
this.setState({signupModalOpen: !this.state.signupModalOpen});
},
toggleNavbar: () => {
this.setState({navbarOpen: !this.state.navbarOpen})
},
toggleTab: (tab) => {
if(this.state.activeTab !== tab) {
this.setState({activeTab: tab});
}
}
}
render() {
return (
<Context.Provider value={this.state}>
{this.props.children}
</Context.Provider>
)
}
}
export {Provider};
export default Context;
And, this is my SwitchTab
import React, {Component} from 'react'
import Context from "../../../provider";
import { Container, TabContent, TabPane } from "reactstrap";
import Home from '../../Content/Home'
import Popular from '../../Content/Popular'
import All from '../../Content/All'
export default class SwitchTab extends Component {
render() {
return (
<Context.Consumer>
{context => {
return (
<Container>
<TabContent activeTab={context.activeTab}>
<TabPane tabId="1">
<Home />
</TabPane>
<TabPane tabId="2">
<Popular />
</TabPane>
<TabPane tabId="3">
<All />
</TabPane>
</TabContent>
</Container>
)
}}
</Context.Consumer>
)
}
}
The functionality that I want to achieve is:
Whenever the URL is '/', redirect to '/home/'.
Whenever the URL is '/home/', change the activeTab from the
Context and make SwitchTab re-render so that home tab gets
opened.
The current working state is whenever I click on a tab, the content changes but the URL remains as /home/. If the Change the URL to /popular/ or /all/, it doesn't change the content.
The error clearly states what is wrong with your problem, you need to wrap your Routes within a div as a Router accepts only one child element.
Also in order to set the state in Provider you need to use setState or provide a handler
class Provider extends Component {
state = {
loggedIn: false,
username: '',
password: '',
loginModalOpen: false,
signupModalOpen: false,
navbarOpen: false,
activeTab: '3',
setActiveTab: (tab) => {
this.setState({activeTab: tab});
}
toggleLoggedIn: () => {
this.setState({loggedIn: !this.state.loggedIn});
},
toggleLoginModal: () => {
this.setState({loginModalOpen: !this.state.loginModalOpen});
},
toggleSignupModal: () => {
this.setState({signupModalOpen: !this.state.signupModalOpen});
},
toggleNavbar: () => {
this.setState({navbarOpen: !this.state.navbarOpen})
},
toggleTab: (tab) => {
if(this.state.activeTab !== tab) {
this.setState({activeTab: tab});
}
}
}
render() {
return (
<Context.Provider value={this.state}>
{this.props.children}
</Context.Provider>
)
}
}
also since you can't set state in render directly, you would need to create a component that handles this case
class Handler extends React.Component {
componentDidMount() {
this.props.setActiveTab(this.props.activeTab);
}
render() {
return null;
}
}
and your Router config would look like
<BrowserRouter>
<div>
<Route exact path='/' render={() => <Redirect to='/home/' /> } />
<Route exact path='/home/' render={() => <Handler setActiveTab={setActiveTab} activeTab={'1'} />}/>
<Route exact path='/popular/' render={() => <Handler setActiveTab={setActiveTab} activeTab={'2'} />}/>
<Route exact path='/all/' render={() => <Handler setActiveTab={setActiveTab} activeTab={'3'} />}/>
</div>
</BrowserRouter>
I have created a reactJS app using create-react-app using Flux as an architecture where I want to have some routes accessible without being authenticated and some only accessible while authenticated. Using the flux design pattern I am passing the state of the store down through each component using props so the store state is available for all child components that require it.
I have studied the documentation here (the example is also pasted below) to try and understand how to achieve the above outcome in my app.
I can't see how I can adapt the example to pass state down to the component called within the protected route without doing this with an explicit name like how component is passed.I want to acheive...
Pass the component to PrivateRoute so it can be called if my user is authenticated.
Pass all props from the parent component to the component called by PrivateRoute (this is to allow me to keep cascading the store state down through the props and also to check in the store state if the user is logged in).
I think I am perhaps misunderstanding something fundamental here. Can anyone advise please?
import React from "react";
import {
BrowserRouter as Router,
Route,
Link,
Redirect,
withRouter
} from "react-router-dom";
////////////////////////////////////////////////////////////
// 1. Click the public page
// 2. Click the protected page
// 3. Log in
// 4. Click the back button, note the URL each time
const AuthExample = () => (
<Router>
<div>
<AuthButton />
<ul>
<li>
<Link to="/public">Public Page</Link>
</li>
<li>
<Link to="/protected">Protected Page</Link>
</li>
</ul>
<Route path="/public" component={Public} />
<Route path="/login" component={Login} />
<PrivateRoute path="/protected" component={Protected} />
</div>
</Router>
);
const fakeAuth = {
isAuthenticated: false,
authenticate(cb) {
this.isAuthenticated = true;
setTimeout(cb, 100); // fake async
},
signout(cb) {
this.isAuthenticated = false;
setTimeout(cb, 100);
}
};
const AuthButton = withRouter(
({ history }) =>
fakeAuth.isAuthenticated ? (
<p>
Welcome!{" "}
<button
onClick={() => {
fakeAuth.signout(() => history.push("/"));
}}
>
Sign out
</button>
</p>
) : (
<p>You are not logged in.</p>
)
);
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route
{...rest}
render={props =>
fakeAuth.isAuthenticated ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: "/login",
state: { from: props.location }
}}
/>
)
}
/>
);
const Public = () => <h3>Public</h3>;
const Protected = () => <h3>Protected</h3>;
class Login extends React.Component {
state = {
redirectToReferrer: false
};
login = () => {
fakeAuth.authenticate(() => {
this.setState({ redirectToReferrer: true });
});
};
render() {
const { from } = this.props.location.state || { from: { pathname: "/" } };
const { redirectToReferrer } = this.state;
if (redirectToReferrer) {
return <Redirect to={from} />;
}
return (
<div>
<p>You must log in to view the page at {from.pathname}</p>
<button onClick={this.login}>Log in</button>
</div>
);
}
}
export default AuthExample;
Here is a section from my actual code...
class Page extends React.Component{
// constructor(props) {
// super(props);
// }
render(){
return(
<Router>
<div>
<Route exact path="/" render={()=><HomePage {...this.props}/>}/>
<Route path="/login" render={()=><LoginPage {...this.props}/>}/>
<PrivateRoute exact path="/protected" component={Main} extra="Boo!"/>
</div>
</Router>);
}
}
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route
{...rest}
render={(props) =>
(console.log(this.props.extra) || 1) ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: "/login",
state: { from: props.location }
}}
/>
)
}
/>
);
This.props.extra is undefined.
If you are looking for a way to pass extra props to the PrivateRoute you can do:
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route
{...rest}
render={ (props) =>
( console.log(props.extra) || 1) ? (
<Component {...props} {...rest} />
) : (
<Redirect
to={{
pathname: "/login",
state: { from: props.location }
}}
/>
)
}
/>
);
then
<PrivateRoute exact path="/protected" component={Main} extra="Boo!"/>
and Main should now receive extra prop (together with path and exact).
My app currently has three components, User to view a person's profile, Self for a user to view their dashboard, notifications, and settings and a login page.
Both User and Self share common components Nav and Side, where User would pass the self object and call the fetchUser action from redux to Nav and Side, while Self would pass the user and self object along with calling the fetchSelf action.
User.js
class User extends React.Component {
componentDidMount() {
this.props.fetchUser(this.props.username);
}
render() {
const { page, self, user } = this.props
return (
<main>
<Nav
self={self}
/>
<Side
page={page} user={user}
/>
<div>
.....
</div>
</main>
)
}
}
const mapStateToProps = state => ({
page: state.store.page,
self: state.store.self
});
export default connect(mapStateToProps, {fetchUser})(User);
Self.js
class Self extends React.Component {
componentDidMount() {
this.props.fetchSelf();
}
render() {
const { page, self } = this.props
return (
<main>
<Nav
self={self}
/>
<Side
page={page} self={self}
/>
{
tab === 'Dashboard'
? <Dashboard />
: tab === 'Notifications'
? <Notifications />
: tab === 'Settings'
? <Settings />
: null
}
</main>
)
}
}
const mapStateToProps = state => ({
page: state.store.page,
self: state.store.self
});
export default connect(mapStateToProps, {fetchSelf})(Self);
Login.js
class Login extends React.Component {
.....
handleChange = event => {
.....
}
render() {
return (
<div id="login">
.....
</div>
)
}
Side.js
const Side = (props) => {
const { page, self, user } = props;
return (
<aside>
{
page === 'user'
? <div>
<img src={'img/' + user.profile.avatar} alt={`${user.username}'s avatar`} />
</div>
: <div>
<img src={'img/' + self.profile.avatar} alt={`${self.username}'s avatar`} />
<div>
}
</aside>
)
}
What I'd like to do here is instead of using react-router like this
<BrowserRouter>
<Switch>
<Route path="/login" exact={true} component={Login} />
<Route path="/self" exact={true} component={Self} />
<Route path="/:username" component={User} />
</Switch>
</BrowserRouter>
I'd want to be able to do something like this instead
const LayoutForLoginAndSignup = (props) => {
return (
<div class="loginOrSignUp">
<ComponentToBePassedIn />
</div>
)
}
class LayoutWithNavAndSide extends React.Component {
componentDidMount() {
this.props.fetchSelf();
// this.props.fetchUser('someusername')
}
render() {
return (
<main>
<Nav self={this.props.self} />
<Side page={this.props.page} self={this.props.self} user={this.props.user} />
{Content of component that was passed in}
</main>
)
}
}
const mapStateToProps = state => ({
page: state.store.page,
self: state.store.self,
user: state.store.user
});
export default connect(mapStateToProps, {fetchUser, fetchSelf})(LayoutWithNavAndSide);
<BrowserRouter>
<Switch>
<LayoutForLoginAndSignup path="/login" exact={true} component={Login} />
<LayoutWithNavAndSide path='/self' component={Self} />
<LayoutWithNavAndSide path="/:username" component={User} />
</Switch>
</BrowserRouter>
Here's where I get confused as I'm still new to react/redux/react router, how do I get the component of User or Self to show up in the layout? how do I get it to call fetchUser (on componentDidMount) only if someone is accessing /someuser vice versa with fetchSelf only when they goto the /self route? is it possible to do the layout as a function rather than a class?
Create the component you will make a route for that contains both layouts and a condition.
const Layout = (props) => (
{props.layout ? <SelfLayout /> : <UserLayout />}
)
Create two layouts.
const SelfLayout = () => (
<div> Self Layout </div>
)
const UserLayout = () => )
<div> User Layout </div>
)
Create your route.
<Route path={`/layout/${this.state.somelayout}`} render={() => <Layout
layout={this.state.somelayout}/>} />
this.state.somelayout should be the conditional that decides which layout we are on and you can tailor it to the needs of your app this is just a guideline.