Karthi
Karthi

Reputation: 3489

How to listen to localstorage value changes in react?

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>

        )
    }

}

Upvotes: 44

Views: 91670

Answers (3)

crevulus
crevulus

Reputation: 2418

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.

Upvotes: 110

mnagdev
mnagdev

Reputation: 514

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();
   .

Upvotes: 4

Fyodor Yemelyanenko
Fyodor Yemelyanenko

Reputation: 11848

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.

Upvotes: 21

Related Questions