1

Faced the problem of unloading from localStorage, perhaps even with an understanding of the work of React itself, namely:

import "bootstrap/dist/css/bootstrap.min.css";
import { React, useState, useContext } from "react";
import {
  Form,
  Row,
  InputGroup,
  Col,
  Button,
  Container,
  Card,
} from "react-bootstrap";
import { AuthContext } from "../components/AuthContext.jsx";
import { Navigate } from "react-router-dom";

function LoginPage() {
  const { setOperator } = useContext(AuthContext);
  const [validate, setValidate] = useState(true);
  const [formData, setFormData] = useState({ login: "", pass: "" });

  const handleChange = (event) => {
    setFormData({ ...formData, [event.target.name]: event.target.value });
  };

  const handleButton = () => {
    fetch("/set_operator", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        login: formData.login,
        pass: formData.pass,
      }),
    })
      .then((res) => res.json())
      .then((userData) => {
        /*   console.log(userData); */
        if (userData.error === "User is not found") {
          return console.log(userData);
        }

        setOperator(userData.operator);
        localStorage.setItem("currentUser", JSON.stringify(userData.operator));
      });
  };

  const checkStorage = localStorage.hasOwnProperty("currentUser");

  if (checkStorage) {
    return <Navigate to={"/admin"} />;
  }

  return (
    <Container>
      <Row className="justify-content-center align-items-center g-4">
        <Col xs={5}>
          <Card border="primary" style={{ width: "35rem" }}>
            <Card.Header className="text-center">Authentication</Card.Header>
            <Form noValidate validated={validate} /* onSubmit={handleSubmit} */>
              <Row className="mb-3">
                <Form.Group
                  as={Col}
                  md="7"
                  controlId="validationCustomUsername"
                >
                  <Form.Label>Username</Form.Label>
                  <InputGroup hasValidation>
                    <InputGroup.Text id="inputGroupPrepend">@</InputGroup.Text>
                    <Form.Control
                      required
                      type="text"
                      placeholder="login"
                      name="login"
                      value={formData.login}
                      onChange={handleChange}
                    />
                    <Form.Control.Feedback>
                      Username is a required field{" "}
                    </Form.Control.Feedback>
                  </InputGroup>
                </Form.Group>
              </Row>
              <Row className="mb-3">
                <Form.Group as={Col} md="7" controlId="validationCustomPass">
                  <Form.Label>Password</Form.Label>
                  <Form.Control
                    required
                    type="text"
                    placeholder="password"
                    name="pass"
                    value={formData.pass}
                    onChange={handleChange}
                  />
                  <Form.Control.Feedback>
                    Password is a required field{" "}
                  </Form.Control.Feedback>
                </Form.Group>
              </Row>
              <Button onClick={handleButton} href="/admin">
                Login
              </Button>
            </Form>
          </Card>
        </Col>
      </Row>
    </Container>
  );
}

export default LoginPage;

What happens here is writing to localStorage works crookedly, I constantly catch looping errors, then I want to receive data from localStorage using useContext and reducer My context:

import React, { createContext, useReducer, useEffect } from "react";
import { reducerUsers } from "./ReducerUsers";

export const AuthContext = createContext();

const initialState = {
  operators: null,
};

export const AuthProvider = ({ children }) => {
  const [value, dispatch] = useReducer(reducerUsers, initialState);

  useEffect(() => {
    dispatch({ type: "CHECK_LOCAL_STORAGE" });
  }, [children]);

  value.setOperator = (operatorData) => {
    dispatch({ type: "SET_OPERATOR", payload: operatorData });
  };

  return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
};

its my reducer:

export function reducerUsers(state, { type, payload }) {
  switch (type) {
    case "SET_OPERATOR":
      return {
        ...state,
        operators: payload || [],
      };
    case "CHECK_LOCAL_STORAGE":
      const storedUser = JSON.parse(localStorage.getItem("currentUser"));
      if (storedUser) {
        return {
          ...state,
          operators: storedUser,
        };
      }
      return state;
    default:
      return state;
  }
}

Next, I create a private route, which I write the following:

import React, { useContext } from "react";
import { Outlet, Navigate } from "react-router-dom";
import { AuthContext } from "../components/AuthContext";

export const PrivateRoute = ({
  element: Element,
  currentUser,
  roles,
  ...rest
}) => {
  const { operators } = useContext(AuthContext);
  console.log(operators);
  if (!currentUser) {
    // console.log(currentUser);
    return <Navigate to="/login" />;
  }

  if (roles && !roles.includes(currentUser[0].role)) {
    // console.log(`${currentUser[0].role} ${roles}`);
    return <Navigate to="*" />;
  }

  return (
    <Outlet>
      <Element {...rest} />
    </Outlet>
  );
};

Do not look that the checks are now implemented through props, it's just like a gag from hopelessness... But if you look at the console.log(operators); then first of all I get null and then an array of data

0 : id : 1 img : null login : "admin" name : "Alen" pass : "admin" role : "admin" [[Prototype]] : Object length : 1 [[Prototype]] : Array(0)

Accordingly, I can no longer make any checks, since I catch an error immediately when I go or refresh the page. And finally, my App file for the full picture of the call

import React, { useState } from "react";
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
import { ProductsContext } from "./components/ProductsContext.jsx";
import { AuthProvider } from "./components/AuthContext.jsx";
import { PrivateRoute } from "./components/PrivateRoute.jsx";

import Navibar from "./components/NaviBar.jsx";
import LoginPage from "./pages/LoginForm.jsx";
import Notfoundpage from "./pages/Notfoundpage.jsx";
import Infoproduct from "./pages/Infoproduct.jsx";
import Productpage from "./pages/Productpage.jsx";
import { PaymentPage } from "./pages/Payment.jsx";
import { BucketList } from "./pages/BucketList.jsx";
import { OrderSuccessPage } from "./pages/OrderSuccess.jsx";
import FoodDrink from "./pages/FoodDrink.jsx";
import AdminPage from "./pages/AdminPage.jsx";
import { AddProduct } from "./pages/AddProduct.jsx";
import { Orders } from "./pages/Orders.jsx";
import { ProductList } from "./pages/ProductList.jsx";
import Sidebar from "./components/SideBar.jsx";

function App() {
  const [currentUser, setCurrentUser] = useState(
    JSON.parse(localStorage.getItem("currentUser"))
  );
  return (
    <>
      <AuthProvider>
        <ProductsContext>
          <Router>
            <Navibar />
            <Sidebar />
            <Routes>
              <Route path="/" element={<Productpage />} />
              <Route path="product/:id" element={<Infoproduct />} />
              <Route path=":typeParam" element={<FoodDrink />} />
              <Route path="payment" element={<PaymentPage />} />
              <Route path="bucket" element={<BucketList />} />
              <Route path="order_success" element={<OrderSuccessPage />} />
              <Route path="login" element={<LoginPage />} />

              <Route
                path="/admin"
                element={
                  <PrivateRoute
                    element={AdminPage}
                    roles={["admin", "moderator"]}
                    currentUser={currentUser}
                  />
                }
              />
              <Route path="/admin/add_product" element={<AddProduct />} />
              <Route path="/admin/orders" element={<Orders />} />
              <Route path="/admin/product_list" element={<ProductList />} />
              <Route path="*" element={<Notfoundpage />} />
            </Routes>
          </Router>
        </ProductsContext>
      </AuthProvider>
    </>
  );
}

export default App;

Please help me understand what I'm doing wrong, how to do it better? I'm just learning React and don't understand my main mistake. I would be very grateful for any help version my react: "react": "^18.2.0" version my react-router-dom: "react-router-dom": "^6.11.1"

Update: when I want to get data from the localStorage from my component, I get null first of all, which prevents me from further work. For example, if you look at the logs, they look like this: enter image description here

if I do a check in PrivateRoute, or refer to the role element. I will get an error

  const { operators } = useContext(AuthContext);
  console.log(operators[0].role);
  if (!operators) {
    // console.log(currentUser);
    return <Navigate to="/login" />;
  }

enter image description here

and I don't understand why this is happening. And how do I properly get my data and process it

Ethernets
  • 201
  • 2
  • 12
  • *Other than* the `Outlet` component not taking any `children` prop and the `AuthProvider` component mutating the `value` state I don't see any overt issues and it's quite unclear what any specific issue is with the code. This is a bit of a code dump. Can you [edit] to clarify what specific issue you would like help with? Any information *other than* "it's not working" is generally helpful. – Drew Reese May 30 '23 at 19:20
  • @DrewReese Hello, I have updated the information as detailed as possible. Hope this helps – Ethernets May 30 '23 at 19:33
  • Why isn't `currentUser` stored in the `AuthContext`? The `operators` state is initially `null`, so the logs appear to track correctly when it's updated. What are you expecting the source of truth to be for what an authenticated user is? Do you really just help with how to implement auth state and [protected routes](/a/66289280/8690857)? – Drew Reese May 30 '23 at 19:46
  • `currentUser` this is the props from which I want to get rid of and completely switch to useContext, but it doesn’t work for me because I initially get empty data ```const [currentUser, setCurrentUser] = useState( JSON.parse(localStorage.getItem("currentUser")) );``` How can I fill in the `operator` before checking? Yes, I am studying you to use private routes and auth and protected routes with roles – Ethernets May 30 '23 at 19:53
  • If you are trying to replace, or remove, `currentUser` then why do you even care about its initialization? Think you could create a ***running*** [codesandbox](https://codesandbox.io/) demo of your code that we could inspect live? You can mock any APIs/endpoints. – Drew Reese May 30 '23 at 19:55
  • https://codesandbox.io/embed/nervous-tdd-8ob9rj?fontsize=14&hidenavigation=1&theme=dark – Ethernets May 30 '23 at 20:31
  • @DrewReese I tried to implement the error that I encountered as accurately as possible and I don’t understand how to solve it – Ethernets May 30 '23 at 20:37

1 Answers1

1

AuthContext:

  1. Provide a default context value.
  2. Initialize the operators state from localStorage.
  3. Use a useEffect hook to persist value.operators state changes to localStorage.
  4. Don't mutate the values state by injecting a setOperator function. Just add this to the provided Context value in the Provider component.
import React, { createContext, useReducer, useEffect } from "react";
import { reducerUsers } from "./ReducerUser";

export const AuthContext = createContext({
  operators: null,
  setOperator: () => {}
});

const initialState = {
  operators: JSON.parse(localStorage.getItem("currentUser"))
};

export const AuthProvider = ({ children }) => {
  const [value, dispatch] = useReducer(reducerUsers, initialState);

  useEffect(() => {
    localStorage.setItem("currentUser", JSON.stringify(value.operators));
  }, [value.operators]);

  const setOperator = (operatorData) => {
    dispatch({ type: "SET_OPERATOR", payload: operatorData });
  };

  return (
    <AuthContext.Provider value={{ ...value, setOperator }}>
      {children}
    </AuthContext.Provider>
  );
};

ReducerUser.jsx:

  • Remove the CHECK_LOCAL_STORAGE case, it's unnecessary.
export function reducerUsers(state, { type, payload }) {
  switch (type) {
    case "SET_OPERATOR":
      return {
        ...state,
        operators: payload || []
      };
    
    default:
      return state;
  }
}

PrivateRoute.jsx

  1. Update to consume only a roles prop with a default initial value.
  2. Remove the Element from Outlet. Outlet doesn't take a children prop, all nested routes of a Route are rendered into the Outlet.
import React, { useContext } from "react";
import { Outlet, Navigate } from "react-router-dom";
import { AuthContext } from "./AuthContext";

export const PrivateRoute = ({ roles = [] }) => {
  const { operators } = useContext(AuthContext);
  
  if (!operators || (roles.length && !roles.includes(operators?.role))) {
    return <Navigate to="/" />;
  }

  return <Outlet />;
};

App.jsx

  • Update to render PrivateRoute as a layout route, AdminPage is rendered as a nested route.
export default function App() {
  return (
    <AuthProvider>
      <Router>
        <Routes>
          <Route path="/" element={<LoginPage />} />
          <Route element={<PrivateRoute roles={["admin", "moderator"]} />}>
            <Route path="/admin" element={<AdminPage />} />
          </Route>
        </Routes>
      </Router>
    </AuthProvider>
  );
}

LoginPage.jsx

  • Update to use the useNavigate hook and issue an imperative navigation action to "/admin" from the submit handler.
...
import { useNavigate } from "react-router-dom";

function LoginPage() {
  const navigate = useNavigate();

  const { setOperator } = useContext(AuthContext);
  const [validate, setValidate] = useState(true);
  const [formData, setFormData] = useState({ login: "", pass: "" });

  const handleChange = (event) => {
    setFormData({ ...formData, [event.target.name]: event.target.value });
  };

  const handleButton = () => {
    setOperator({ name: "ALEN", login: "admin", pass: "admin", role: "admin" });
    navigate("/admin");
  };

  ...

};

Edit uploading-data-from-localstorage-reactjs

Drew Reese
  • 165,259
  • 14
  • 153
  • 181