How to hide a button unless a variable has a value - javascript

I am trying to create a logoff button in react, and although it works (logs the user out) I want to make the button hidden unless my localstorage has a value for 'token'. At this point when it is not null, I would like my logoff button to then appear on screen for a user to logoff.
The section where I call this button is below, which currently only has an onclick event if my localstorage has a value assigned to 'token'
import React from "react";
import { render } from "react-dom";
import Login from "./Login";
import Register from "./Register";
import Logout from "./logout";
import Button from "react-bootstrap/Button";
render(
<>
<Login />
<div>
<h1>OR</h1>
</div>
<Register />
</div>
<Button onClick={event => {if(localStorage.getItem('token') !== null)
Logout()}} }>Logout</Button>
</>,
document.getElementById("root")
);
Currently this works as intended, but is not hidden. I have thought about creating a function that exports only a button if the paramater is fulfilled as posted below;
import Logout from "./logout";
import Button from "react-bootstrap/Button";
import React from "react";
function logoffBut(){
if (localStorage.getItem("token") !== null) {
return <Button onClick={event => Logout()}>Logout</Button>;
}
}
export default logoffBut;
then calling
<logoffBut />
in my render above, in place of the current logout button that is there.
However when I log in and store the 'token' in local storage,
it does not generate the logoffBut at this point, despite the token being correctly stored in my local storage?
I would just like some help to see how I should be writing this function to be checking if my 'token' in local storage is not null, and if it is not null, to then generate the logoff button.
Thank you.

You can a ternary operator to check and render only if required
localStorage.getItem('token') !== null ? <Button onClick={event => {if(localStorage.getItem('token') !== null) Logout()}} }>Logout</Button> : null;
Suggestion
I suggest you also to split your content to another component something like
import React from "react";
import { render } from "react-dom";
import Login from "./Login";
import Register from "./Register";
import Logout from "./logout";
import Button from "react-bootstrap/Button";
import loginPage from "./LoginPage";
render(<loginPage></loginPage>,document.getElementById("root")
);

You can use && operator to render the button when localStorage is not null
{localStorage.getItem('token') !== null && <Button> ... </Button>}

Use conditional rendering in JSX
import React from "react";
import { render } from "react-dom";
import Login from "./Login";
import Register from "./Register";
import Logout from "./logout";
import Button from "react-bootstrap/Button";
render(
<>
<Login />
<div>
<h1>OR</h1>
</div>
<Register />
</div>
{
localStorage.getItem('token') !== null ? <Button onClick={event => Logout()>Logout</Button>: null
}
</>,
document.getElementById("root")
);

I love using the display-if prop.
<div display-if={someCondition}>MY TEXT</div>
You can use it after installing the babel plugin
https://www.npmjs.com/package/babel-plugin-jsx-display-if

Related

How do I pass useState from one component to another?

I am trying to make a login modal that changes to the signup modal when the signup button is clicked within the Login.js. Currently, I have a header with a login button. When the login button is clicked, the current state is displayed (default value is ). I would like the signup button within Login.js to be able to update the state in order for that modal to .
Header.js:
import React, { useState } from 'react';
import Login from "../modals/Login";
import Signup from "../modals/Signup";
export default function Header() {
const [lmodal, setModal] = useState(<Login />);
return (
...
{lmodal}
...
)
}
Login.js
import React, { useState } from 'react';
import Signup from "../modals/Signup";
export default function Login() {
return (
...
**// Clicking the button should change the state from Header.js to <Signup />**
<button onClick={() => { setModal(<Signup />) }} className="btn btn-primary">Signup</button>
...
)
}
Thanks for the help in advance!
States and components are different in concept.
What you coould possible do to reach the wanted behavior is to use the state to toogle the component, for exemple:
import React, { useState } from 'react';
import Login from "../modals/Login";
import Signup from "../modals/Signup";
export default function Header() {
const [modal, setModal] = useState(false);
return (
...
{modal && <Login />}
<Signup modal={modal} setModal={setModal}/>
...
)}
import React, { useState } from 'react';
import Signup from "../modals/Signup";
export default function Login({modal, setModal}) {
return (
...
<button onClick={() => setModal(true)} className="btn btn-primary">Signup</button>
...
)
}
you can't setModal from within the Login component because setModal is in the Header component.

My app doesn't automatically scroll up after adding ScrollToTop Component

First time using React this week and I'm trying to create a fetch Github app, everything is working but the scroll to the top. When I scroll down and click on the view Repo I want the screen to automatically scroll up, I've copied the code via the React Docs and I am using React-Router-Dom version 5 however nothing is scrolling up. Please see below for my code, where am I going wrong guys?
import { useEffect } from "react";
import { useLocation } from "react-router-dom";
export default function ScrollToTop() {
const { pathname } = useLocation();
useEffect(() => {
window.scrollTo(0, 0);
}, [pathname]);
return null;
}
App.js
import { BrowserRouter as Router, Switch, Route} from 'react-router-dom';
import './App.css';
import RepoPage from './components/RepoPage';
import ScrollToTop from './components/ScrollToTop';
function App() {
return (
<Router>
<div className="App">
<h1>Want to see other people's projects? Enter a Github username and Voila!</h1>
<ScrollToTop />
<Switch>
<Route>
<RepoPage />
</Route>
</Switch>
</div>
</Router>
);
}
export default App;
Snippet of my Repo.js component
import React, {useEffect, useState} from 'react';
import axios from 'axios';
import RepoDetails from './RepoDetails';
import ScrollToTop from './ScrollToTop';
import '../App.css'
function renderRows(repo) {
return(
<div
className='repo-button'
onClick={() => getDetails(repo.name)}
key={repo.id}
>
<h3 className='repo-name'>
{repo.name}
<button onCLick={<ScrollToTop />}>View Repo</button>
</h3>
</div>
)
}
There might be some misunderstanding with how ScrollToTop function component is used. I think when used in the App component, it is meant to automatically scroll to the top when the pathname changes. We can probably add this similar behavior for when the View Repo button is clicked by passing a callback function to onClick like () => window.scrollTo(0, 0) instead of the function component.
Not to worry I've fixed the issue, I've set the window scroll to the top within the button onClick tag. Thanks anyway

why my context doesn't update when the context value updates?

so I am using React's context API in my Gatsby app(which is written in React basically) to deal with user authentication. I have two components that use that context: dashboard and navBar. When I try to log in and log out, my navBar will behave differently according to my userContext, but my dashboard won't respond. Is it something related to the structure, like navBar is the direct "children" to layout, but dashboard is not? I assume not though, after all, that's why I use contextAPI then just pass a normal prop.
Here are the codes:
//layout.js
import React, { useContext, useState, createContext } from "react"
import Navbar from "../components/navBar"
import {monitorAuth} from "../firebase/firebaseService"
export const UserStateContext = createContext(null)
export const SetUserContext = createContext()
const Layout = ({ children }) => {
const [user, setUser] = useState()
console.log(user)
monitorAuth(setUser)// everytime a layout component renders, it will grab a user if it is logged inthen setUser, then I will use it in the context
return (
<>
<UserStateContext.Provider value={user}>
<SetUserContext.Provider value={setUser}>
<div>
<SEO />
<Navbar />
<main>{children}</main>
</div>
</SetUserContext.Provider >
</UserStateContext.Provider>
</>
)
}
export default Layout
import React, { useState, useContext } from "react"
import AppBar from "#material-ui/core/AppBar"
import { signOut } from "../firebase/firebaseService"
import {UserStateContext} from "./layout"
export default function NavBar() {
const user = useContext(UserStateContext)
console.log(user) // when I log in/ log out, it will console.log the right user status, user/null
const renderMenu = () => {
return (
<>
{user? (
<>
<Button onClick={signOut}>Sign Out</Button>
<Button>My profile</Button>
</>)
:<Button>Sign In</Button> }
</>
)
}
return (
<AppBar position="static" className={classes.root}>
...
{renderMenu()}
...
</AppBar>
)
}
//dashboard.js
import React, { useContext } from 'react'
import Layout from '../components/layout'
import LoggedIn from '../components/dashboard/loggedIn'
import NotLoggedIn from '../components/dashboard/notLoggedIn'
import {UserStateContext} from "../components/layout"
const Dashboard = props => {
console.log("within dashboard")
const user = useContext(UserStateContext)
console.log(user)
const renderDashboard = () =>{
return (
<>
{user? <LoggedIn /> : <NotLoggedIn />}
</>
)
}
return(
<Layout>
{renderDashboard()}
</Layout>
)
}
export default Dashboard
One more clue, I console.log user in all three components and when I refresh the page:
within dashboard
dashboard.js:17 null
layout.js:15 undefined
navBar.jsx:54 undefined
layout.js:15 [user...]
navBar.jsx:54 [user...]
layout.js:15 [user...]
That means, at first, user is not set yet, so all three components log the user as undefined, but later, layout detect the user and then updates it, so navbarknows too, but dashboard doesn't. Is it something about re-render? Thanks!
The reason it's not working is because your <Dashboard> component is not a child of the context provider. If you use React devtools, you'll see the component tree looks like
<Dashboard>
<Layout>
<UserStateContext.Provider>
<SetUserContext.Provider>
...
</SetUserContext.Provider>
</UserStateContext.Provider>
</Layout>
</Dashboard>
When the context value changes, it looks for components in its subtree that useContext. However, Dashboard is not a child, it's the parent!
If you want to follow this pattern, a solution may be to create a parent component of Dashboard and put the context there.

How to remove Login button from nav bar after logging in

I am building a website that has 3 different pages (Home, Login, and User), and I am using Switch Component in React Router to move between pages.
The issue I have is when I get to the user page, I still see the login in the navigation list as shown in the image below
Now I want the Login button to be removed once I get to the user page or switch the text to "Logout". Do you have any suggestions on how this could be done. I also included the code so it's more clear
Header (Navbar) Component
import React from "react";
import { Link } from "react-router-dom";
import './App.css';
const Header = () => (
<header >
<nav className='topnav'>
<ul>
<li>
<Link to="/Login">Login</Link>
</li>
<li>
<Link to="/">Home</Link>
</li>
</ul>
</nav>
</header>
);
export default Header;
Login Component
const Login = () => (
<Switch>
<div className="LoginWrap">
<Route exact path="/Login" component={LoginForm} />
<Route path="/Login/:number" component={User} />
</div>
</Switch>
);
export default Login;
and the User component is here.
Edit: Based on your suggestions, I tried to do this:
<Link to="/Login">
{
console.log("Header localStorage.getItem isLoggedIn is :" + localStorage.getItem("isLoggedIn"))
}
{
localStorage.getItem("isLoggedIn") === true? "Logout" : "Login"
}
</Link>
However, it was showing "Login" all the time.
I have no idea why. Even though in the console it prints "Header localStorage.getItem isLoggedIn is :true" and "Header localStorage.getItem isLoggedIn is :false" correctly when you login through the login form and then press the Link button on the header
Even more strange was that when I changed the code to
localStorage.getItem("isLoggedIn") ? "Logout" : "Login"
, it always shows "Logout"
If you want to use Redux to solve your issues, can check out my simple redux sample in my github repository (https://github.com/saamerm/ReactAndRedux-CounterApp) that will be able to explain everything in details.
You just need to change up the index.js and the Counter.js (Login in your case) files for your application :
Index.js
import React from "react";
import { render } from "react-dom";
import { BrowserRouter } from "react-router-dom";
import App from "./components/App";
import { Provider } from "react-redux";
import { createStore } from "redux";
const initialState = {
  isLoggedIn: false
};
function reducer(state = initialState, action) {
  switch (action.type) {
    case "LOGIN":
      return {
        isLoggedIn: true
      };
    case "LOGOUT":
      return {
        isLoggedIn: false
      };
    default:
      return state;
  }
}
const store = createStore(reducer);
render(
  <Provider store={store}>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </Provider>,
  document.getElementById("root")
);
Login component
import React from "react";
import { connect } from "react-redux";
class Login extends React.Component {
    login = () => {
        this.props.dispatch({ type: "LOGIN" });
    };
    logout = () => {
        this.props.dispatch({ type: "LOGOUT" });
    };
    handleLoginClick=()=>
    {
      if (this.props.isLoggedIn === true){  
        this.logout()
      }
      else{
        this.login()
      }
    }  
    
    render(){...}
}
function mapStateToProps(state) {
    return {
        isLoggedIn: state.isLoggedIn
    };
}
export default connect(mapStateToProps)(Login);
Basically the convention is to use an authentication context to manage the state or you can also put the authenticate method in the context too. I just googled the example quickly, and find one clean example here.
One of the best basic method is setting a localStorage value when the user logins, and deleting this value when the user logouts. Something like this:
const auth = localStorage.getItem("islogin");
if (islogin) { // if there is a value named as islogin...
<Button>Logout</Button>
} else {
<Button>Login</Button>
}

react-router (v4) how to go back?

Trying to figure out how can I go back to the previous page. I am using [react-router-v4][1]
This is the code I have configured in my first landing page:
<Router>
<div>
<Link to="/"><div className="routerStyle"><Glyphicon glyph="home" /></div></Link>
<Route exact path="/" component={Page1}/>
<Route path="/Page2" component={Page2}/>
<Route path="/Page3" component={Page3}/>
</div>
</Router>
In order to forward to subsequent pages, I simply do:
this.props.history.push('/Page2');
However, how can I go back to previous page?
Tried few things like mentioned below but no luck:
1. this.props.history.goBack();
Gives error:
TypeError: null is not an object (evaluating 'this.props')
this.context.router.goBack();
Gives error:
TypeError: null is not an object (evaluating 'this.context')
this.props.history.push('/');
Gives error:
TypeError: null is not an object (evaluating 'this.props')
Posting the Page1 code here below:
import React, {Component} from 'react';
import {Button} from 'react-bootstrap';
class Page1 extends Component {
constructor(props) {
super(props);
this.handleNext = this.handleNext.bind(this);
}
handleNext() {
this.props.history.push('/page2');
}
handleBack() {
this.props.history.push('/');
}
/*
* Main render method of this class
*/
render() {
return (
<div>
{/* some component code */}
<div className="navigationButtonsLeft">
<Button onClick={this.handleBack} bsStyle="success">< Back</Button>
</div>
<div className="navigationButtonsRight">
<Button onClick={this.handleNext} bsStyle="success">Next ></Button>
</div>
</div>
);
}
export default Page1;
I think the issue is with binding:
constructor(props){
super(props);
this.goBack = this.goBack.bind(this); // i think you are missing this
}
goBack(){
this.props.history.goBack();
}
.....
<button onClick={this.goBack}>Go Back</button>
As I have assumed before you posted the code:
constructor(props) {
super(props);
this.handleNext = this.handleNext.bind(this);
this.handleBack = this.handleBack.bind(this); // you are missing this line
}
UPDATED:
Now we have hook, so we can do it easily by using useHistory
const history = useHistory()
const goBack = () => {
history.goBack()
}
return (
<button type="button" onClick={goBack}>
Go back
</button>
);
ORIGINAL POST:
this.props.history.goBack();
This is the correct solution for react-router v4
But one thing you should keep in mind is that you need to make sure this.props.history is existed.
That means you need to call this function this.props.history.goBack(); inside the component that is wrapped by < Route/>
If you call this function in a component that deeper in the component tree, it will not work.
EDIT:
If you want to have history object in the component that is deeper in the component tree (which is not wrapped by < Route>), you can do something like this:
...
import {withRouter} from 'react-router-dom';
class Demo extends Component {
...
// Inside this you can use this.props.history.goBack();
}
export default withRouter(Demo);
For use with React Router v4 and a functional component anywhere in the dom-tree.
import React from 'react';
import { withRouter } from 'react-router-dom';
const GoBack = ({ history }) => <img src="./images/back.png" onClick={() => history.goBack()} alt="Go back" />;
export default withRouter(GoBack);
Each answer here has parts of the total solution. Here's the complete solution that I used to get it to work inside of components deeper than where Route was used:
import React, { Component } from 'react'
import { withRouter } from 'react-router-dom'
^ You need that second line to import function and to export component at bottom of page.
render() {
return (
...
<div onClick={() => this.props.history.goBack()}>GO BACK</div>
)
}
^ Required the arrow function vs simply onClick={this.props.history.goBack()}
export default withRouter(MyPage)
^ wrap your component's name with 'withRouter()'
Here is the cleanest and simplest way you can handle this problem, which also nullifies the probable pitfalls of the this keyword. Use functional components:
import { withRouter } from "react-router-dom";
wrap your component or better App.js with the withRouter() HOC this makes history to be available "app-wide". wrapping your component only makes history available for that specific component``` your choice.
So you have:
export default withRouter(App);
In a Redux environment export default withRouter( connect(mapStateToProps, { <!-- your action creators -->})(App), ); you should even be able to user history from your action creators this way.
in your component do the following:
import {useHistory} from "react-router-dom";
const history = useHistory(); // do this inside the component
goBack = () => history.goBack();
<btn btn-sm btn-primary onclick={goBack}>Go Back</btn>
export default DemoComponent;
Gottcha useHistory is only exported from the latest v5.1 react-router-dom so be sure to update the package. However, you should not have to worry.
about the many snags of the this keyword.
You can also make this a reusable component to use across your app.
function BackButton({ children }) {
let history = useHistory()
return (
<button type="button" onClick={() => history.goBack()}>
{children}
</button>
)
}```
Cheers.
Can you provide the code where you use this.props.history.push('/Page2');?
Have you tried the goBack() method?
this.props.history.goBack();
It's listed here https://reacttraining.com/react-router/web/api/history
With a live example here https://reacttraining.com/react-router/web/example/modal-gallery
If using react hooks just do:
import { useHistory } from "react-router-dom";
const history = useHistory();
history.go(-1);
UPDATE 2022 w V6
navigate(-1)
to omit the current page from history:
navigate(-1, { replace: true })
Try:
this.props.router.goBack()
Simply use
<span onClick={() => this.props.history.goBack()}>Back</span>
Hope this will help someone:
import React from 'react';
import * as History from 'history';
import { withRouter } from 'react-router-dom';
interface Props {
history: History;
}
#withRouter
export default class YourComponent extends React.PureComponent<Props> {
private onBackClick = (event: React.MouseEvent): void => {
const { history } = this.props;
history.goBack();
};
...
Maybe this can help someone.
I was using history.replace() to redirect, so when i tried to use history.goBack(), i was send to the previous page before the page i was working with.
So i changed the method history.replace() to history.push() so the history could be saved and i would be able to go back.
I am not sure if anyone else ran into this problem or may need to see this. But I spent about 3 hours trying to solve this issue:
I wanted to implement a simple goBack() on the click of a button. I thought I was off to a good start because my App.js was already wrapped in the Router and I was importing { BrowserRouter as Router } from 'react-router-dom'; ... Since the Router element allows me to assess the history object.
ex:
import React from 'react';
import './App.css';
import Splash from './components/Splash';
import Header from './components/Header.js';
import Footer from './components/Footer';
import Info from './components/Info';
import Timer from './components/Timer';
import Options from './components/Options';
import { BrowserRouter as Router, Route } from 'react-router-dom';
function App() {
return (
<Router>
<Header />
<Route path='/' component={Splash} exact />
<Route path='/home' component={Info} exact />
<Route path='/timer' component={Timer} exact />
<Route path='/options' component={Options} exact />
<Footer />
</Router>
);
}
export default App;
BUT the trouble was on my Nav (a child component) module,
I had to 'import { withRouter } from 'react-router-dom';'
and then force an export with:
export default withRouter(Nav);
ex:
import React from 'react';
import { withRouter } from 'react-router-dom';
class Nav extends React.Component {
render() {
return (
<div>
<label htmlFor='back'></label>
<button id='back' onClick={ () => this.props.history.goBack() }>Back</button>
<label htmlFor='logOut'></label>
<button id='logOut' ><a href='./'>Log-Out</a>
</button>
</div>
);
}
}
export default withRouter(Nav);
in summary, withRouter was created because of a known issue in React where in certain scenarios when inheritance from a router is refused, a forced export is necessary.
You can use history.goBack() in functional component. Just like this.
import { useHistory } from 'react-router';
const component = () => {
const history = useHistory();
return (
<button onClick={() => history.goBack()}>Previous</button>
)
}

Categories