27

I want to show a button when user is logged.If user is not logged then I m not showing button.When user logged i will set local storage values.when i set local storage in login Component,Header component must listen to that event and show the button.I m using addEventListener for listening.But its not listening.

I don't know where to listen in header Component.

// HeaderComponent(header.js):

class HeaderComponent extends Component {
    componentDidMount(){
        if(typeof window!='undefined'){
            console.log(localStorage.getItem("token"));
            window.addEventListener("storage",function(e){
               this.setState({ auth: true});
            })
        }
    } 
    render() {

    return (
        <div className="header">
            <div className="container">
                <div className="header-content">
                    <img src={logo} alt="logo"></img>
                    <div className="nav-links" >
                        <ul >
                            <li>Home</li>
                            <li>About</li>
                            <li>Services</li>
                            <li><NavLink activeClassName="active" to="/upload" >Upload</NavLink></li>
                            <li><NavLink activeClassName="active" to="/signup"> Sign Up</NavLink></li>


                           { this.state.auth? <li onClick={this.onLogout}>Logout</li> :null}

                        </ul>
                    </div>
                </div>
            </div>

        </div>
    );
   }  
}

//loginComponent(login.js)

class LoginComponent extends Component {
    constructor(props) {
        super(props);
        this.onSubmit = this.onSubmit.bind(this);
    }
    onSubmit(event) {
        const data = {
            username: document.getElementById('name').value,
            password: document.getElementById('password').value
        }
        axios.post(`http://localhost:4000/user/login`, data).then(res => {
            this.props.history.push("/");
            localStorage.setItem("token",res.data.token);
            localStorage.setItem("auth",true);
        }).catch(err => console.log(err));
    }


    render() {
        return (
            <section class="log-in">
                <div class="card-col">
                    <form>
                        <h3>LOG IN</h3>
                        <div class="form-controls">
                            <input id="name" type="text" placeholder="username" class="input"></input>
                        </div>
                        <div class="form-controls">
                            <input id="password" type="password" placeholder="password" class="input"></input>
                        </div>
                        <button type="submit" onClick={this.onSubmit} class="button" >Log in</button>
                    </form>
                </div>

           </section>

        )
    }

}
Neha
  • 2,136
  • 5
  • 21
  • 50
Karthi
  • 3,019
  • 9
  • 36
  • 54
  • Do both of these components share a common parent? If so, have `LoginComponent` set the `auth` state in that parent, and then pass it in to `HeaderComponent` as a `prop`. – sallf Jun 19 '19 at 04:48
  • if they are in one component just call setState. but if they are in separate components you can dispatch an action to reducer and connect the component wich contain the button to redux. feel free to ask more if you confused! – Cherik Jun 19 '19 at 05:39
  • @Lobster Confusing.. can u provide sample code – Karthi Jun 19 '19 at 07:31

3 Answers3

57

The current answers are overlooking a really simple and secure option: window.dispatchEvent.

Where you set your localStorage item, if you dispatch an event at the same time then the eventListener in the same browser tab (no need to open another or mess with state) will also pick it up:

const handleLocalStorage = () => {
    window.localStorage.setItem("isThisInLocalStorage", "true");
    window.dispatchEvent(new Event("storage"));
};
window.addEventListener('storage', () => {
    console.log("Change to local storage!");
    // ...
})

EDIT:

Because this seems to be helpful, I'd also recommended checking out the useLocalStorage hook from the usehooks-ts team. You don't need to install it as a package; you can just copy the hook wholesale. This hook makes use of the solution I originally shared, but adds a whole lot more sophisticated logic to it.

rens
  • 45
  • 8
crevulus
  • 1,658
  • 12
  • 42
17

Please take note of two things

  1. storage event works only when the same application opened in two browser tabs (it is used to exchange info between different tabs of the same app). Storage event will not fire when both components shown on the same page.

  2. When adding event listerner, you're passing function(), not array function. function() doe not capture this so you should explicitly bind(this) or change it to arrow function.

    For example

    window.addEventListener("storage",(function(e){
           this.setState({ auth: true});
        }).bind(this));
    

    Or do with arrow function

    window.addEventListener("storage",(e) => {
           this.setState({ auth: true});
        });
    

Here is simple example.

Be sure to open it in two tabs (the same link). Store value in one tab and see this value in another tab.

Fyodor Yemelyanenko
  • 11,264
  • 1
  • 30
  • 38
  • 2
    It works fine when I open in 2 tabs.But I want it to listen in single tab – Karthi Jun 19 '19 at 09:32
  • 2
    storage event will not fire when opened in single tab. Goal of storage event is to exchange info between tabs. To exchange information between components on single page use lift state up https://reactjs.org/docs/lifting-state-up.html. So `this.state.auth` will be in parent component of `LoginComponent` and `HeaderComponent` and will be passed down as props – Fyodor Yemelyanenko Jun 19 '19 at 11:00
  • 1
    can we achive this with redux in react – Karthi Jun 21 '19 at 10:16
  • Yes, redux also manages the state, but do it centrally. I recommend to start with react state, but lift it up. It is easier than using redux – Fyodor Yemelyanenko Jun 21 '19 at 13:14
4

I found a really bad hack to accomplish this:

I have a Toolbar and a Login Component where the Toolbar component listens to changes in localStorage and displays the logged-in user name when the Login Component updates local storage if authentication is successful.

The Toolbar Component

(similar to the Header component in your case)

const [loggedInName, setLoggedInName] = useState(null);

    useEffect(() => {
        console.log("Toolbar hi from useEffect")
        setLoggedInName(localStorage.getItem('name') || null)
        window.addEventListener('storage', storageEventHandler, false);

    }, []);

    function storageEventHandler() {
        console.log("hi from storageEventHandler")
        setLoggedInName(localStorage.getItem('name') || null)
    }

    function testFunc() {  
        console.log("hi from test function")
        storageEventHandler();
    }

Add a hidden button to your Toolbar component. This hidden button will call the testFunc() function when clicked which will update the logged-in user's name as soon as local storage is updated.

   <button style={{ display: 'none' }} onClick={testFunc} id="hiddenBtn">Hidden Button</button>

Now, in your Login component

   .
   .
   .
   //login was successful, update local storage
   localStorage.setItem("name",someName)


   //now click the hidden button using Javascript
   document.getElementById("hiddenBtn").click();
   .
mnagdev
  • 384
  • 3
  • 11