React Getting error with BroadcastChannel API

4.1k Views Asked by At

I am using sessionStorage to keep the auth information and need to replicate this information to other tabs being opened. I have created a codesandbox to recreate the issue. I didn't include code in this post since it is quite lengthy.

When the Dashboard link is opened in another window, it is not rendered due to the error being popped up.

EDIT: I have included all the necessary codes below.

App.js

export default function App() {
  return (
    <>
      <Router>
        <Switch>
          <Route exact path="/">
            <Home />
          </Route>
          <Route path="/login">
            <Login />
          </Route>
          <Route path="/dashboard">
            <Dashboard />
          </Route>
        </Switch>
      </Router>
    </>
  );
}

Dashboard.js

export default (props) => {
  const [isLogged, setIsLogged] = useState(false);
  const [email, setEmail] = useState();
  const [token, setToken] = useState();
  const authChannel = new BroadcastChannel("auth");

  useEffect(() => {
    setToken(sessionStorage.getItem("token"));
    setEmail(sessionStorage.getItem("email"));
    setIsLogged(sessionStorage.getItem("token") ? true : false);
    authChannel.postMessage({ action: "created", source: "Dashboard" });
  }, []);

  useEffect(() => {
    authChannel.onmessage = function (e) {
      if (
        e.data.action === "login" &&
        (e.data.target === "*" || e.data.target === "Dashboard")
      ) {
        setIsLogged(true);
        setEmail(e.data.data.user.email);
        setToken(e.data.data.token);
        sessionStorage.setItem("email", e.data.data.user.email);
        sessionStorage.setItem("token", e.data.data.token);
      }
    };
  }, [authChannel]);

  return (
    <>
      <h1>Dashboard</h1>
      {isLogged && <h1>You are logged in</h1>}
      {!isLogged && <h1>Please log in</h1>}
    </>
  );
};

Home.js

import React, { useState, useEffect } from "react";

export default (props) => {
  const [isLogged, setIsLogged] = useState(false);
  const [email, setEmail] = useState();
  const [token, setToken] = useState();
  const [user, setUser] = useState();
  const authChannel = new BroadcastChannel("auth");

  useEffect(() => {
    setToken(sessionStorage.getItem("token"));
    setEmail(sessionStorage.getItem("email"));
    setIsLogged(sessionStorage.getItem("token") ? true : false);
  }, []);

  useEffect(() => {
    authChannel.onmessage = function (e) {
      if (
        e.data.action === "login" &&
        (e.data.target === "*" || e.data.target === "Home")
      ) {
        setIsLogged(true);
        setEmail(e.data.data.user.email);
        setToken(e.data.data.token);
        setUser(e.data.data.user);
        sessionStorage.setItem("email", e.data.data.user.email);
        sessionStorage.setItem("token", e.data.data.token);
      } else if (e.data.action === "created") {
        authChannel.postMessage({
          action: "login",
          target: e.data.source,
          data: { user: user, token: token }
        });
      }
    };
  }, [authChannel]);

  const logout = () => {
    setIsLogged(false);
    setToken(null);
    setUser({});
    setEmail();
    sessionStorage.removeItem("email");
    sessionStorage.removeItem("token");
  };

  return (
    <p>
      {isLogged && (
        <>
          <p>
            <a href="/dashboard">Dashboard</a>
          </p>
          <button onClick={() => logout()}>logout</button>
        </>
      )}
      {!isLogged && (
        <>
          <p>
            Please <a href="/login">login</a>
          </p>
        </>
      )}
    </p>
  );
};

Login.js

import React, { useState, useEffect } from "react";

export default (props) => {
  const [isLogged, setIsLogged] = useState(false);
  const [token, setToken] = useState();
  const [email, setEmail] = useState();
  const [user, setUser] = useState({});
  const authChannel = new BroadcastChannel("auth");

  const [input, setInput] = useState({
    email: "[email protected]",
    password: "quantum"
  });

  useEffect(() => {
    authChannel.onmessage = function (e) {
      if (e.data.action === "logout") {
        setIsLogged(false);
        setToken();
        setUser({});
      }
    };
  }, [authChannel]);

  useEffect(() => {
    setToken(sessionStorage.getItem("token"));
    setEmail(sessionStorage.getItem("email"));
    setIsLogged(sessionStorage.getItem("token") ? true : false);
  }, []);

  const handleLogin = () => {
    // handle login here...
    authChannel.postMessage({
      action: "login",
      target: "*",
      data: {
        user: { email: "[email protected]" },
        token: "kjdhlfjkgdjgdlfghjldhjgh76lgj"
      }
    });
    setIsLogged(true);
  };

  const handleInputChange = (e, source) => {
    setInput({ ...input, [source]: e.target.value });
  };

  return (
    <div>
      {!isLogged && (
        <>
          <div>
            <label>Email</label>
            <input
              type="text"
              value={input.email || ""}
              onChange={(e) => handleInputChange(e, "email")}
            />
          </div>
          <div className="flex">
            <label>Password</label>
            <input
              type="password"
              value={input.password || ""}
              onChange={(e) => handleInputChange(e, "password")}
            />
          </div>
          <button onClick={() => handleLogin()}>Login</button>
        </>
      )}
      {isLogged && <h1>You are logged now</h1>}
    </div>
  );
};

Please do let me know what can be the reason for this.

1

There are 1 best solutions below

1
On BEST ANSWER

You are getting an error because you are sending undefined data here:

Home.js

authChannel.postMessage({
  action: "login",
  target: e.data.source,
  data: { user: user, token: token } //--> user and token are undefined
});

Also I recommend you to change this line:

 const authChannel = new BroadcastChannel("auth");

Because every time the component is rendered, a new instance of BroadcastChannel is created. To avoid this you need to store the BroadcastChannel instance in useRef:

  const authChannelRef = useRef(new BroadcastChannel("auth"));
  const authChannel = authChannelRef.current;