I am currently building a react app and am in the beginning stages of setting up my user authentication. The actual user management is pretty easy, as I am using a database service (Supabase) that handles all the user authentication / session management for me. However, I have run into an issue with conditionally rendering parts of my app based on whether or not the user is signed in. For example, I am wanting to render a different navigation bar with different buttons based on if there is a user signed in or not. I have created a custom hook to hold all the sign in stuff from supabase and am attempting to create a context that I can reference elsewhere in the app. My context seems to be giving me trouble though. When I initially log in with a valid user, everything works as expected. I can see the user ID in the context store, and my navigation bar renders according to a logged in user. However, I have found that if I reload the page, my context value clears. Here is the code where I am initializing my context (please note my use of typescript):
import React, { useEffect } from "react";
import { createContext, useContext, useState } from "react";
import useCurrentUser from "../hooks/useCurrentUser";
import supabase from "../supabaseClient";
type TestT = [String, React.Dispatch<React.SetStateAction<String>>];
const UserContext = createContext<TestT>(undefined!);
export function useUser() {
return useContext(UserContext);
}
export function UserProvider({ children }: any) {
console.log("IN USER PROVIDER");
console.log(useCurrentUser());
const [test, setTest] = useState(useCurrentUser()!);
return (
<UserContext.Provider value={[test, setTest]}>
{children}
</UserContext.Provider>
);
}
export { UserContext };
So here is the problem. You will notice I am setting the value in the Provider to a state value, which is defaulting to my useCurrentUser hook. However, when I reload the page, I can see in react dev tools that the 'test' state, and therefore the Provider value, are being set to undefined. This is perplexing, because just prior to setting the 'test' state value to the useCurrentUser hook value, I am printing it, and it does in fact print a valid user ID. So, I am confused why, even though useCurrentUser DOES have a value, it is not setting to my state value and provider value. What am I doing wrong here? How can I get my context to retain the user ID even when the page reloads?
Related
I want to implement RBAC in my React project and here I have different role Like ADMIN,SUPERADMIN etc. and I am storing that user role in LocalStorage and showing information according to the user role so what actually happening that if user is ADMIN that store in localStorage have only access to limited things but when he change its role to SuperAdmin from localstorage from dev or console he get all the access of SuperAdmin what I can do?
You can Create one Middleware so every route passed or render from that Middleware and It's a better way to you can use API's call in your middleware for checking the Roles.
Example :
PrivateRoute.tsx
import React, { useContext, useEffect } from 'react'
import { Redirect, Route } from 'react-router-dom'
export const PrivateRoute = props => {
// Get User info from local storage
const userData = localStorage.getItem('user')
const userRole = userData?.role;
useEffect(() => {
// do something here for async check
},[])
const hasPermission = useMemo(() => {
// some condition checks for roles access for perticluar module
return ...
}, [userRole]);
if (hasPermission) {
return <Route {...props} />
}
return <Redirect to={'Some Path'} />
};
The fact is that handling this kind of authorization is not enough on the frontend side. You can ask your backend to give you whether the user is ADMIN or SUPERADMIN or whatever. Then you can store this authentication status in a state management, so you'd easily access that after.
For implementing different situations, You can use guard some routes or render components conditionally considering which type the user is.
In my opinion, HOCs (wrapping the components with them) & conditional rendering can help you with RBAC.
Been working with react context api to try pass data among material-ui components.
So I'm trying to submit registration data using material ui stepper.
I will try replicate what I want to achieve with links to my current code and screenshots
I used material ui stepper and stated the props that I want passed in it as shown in the code link -> https://pastebin.com/uu5j6ADB
I created the two forms independently that collect data from the user during registration namely BusinessRegisterForm (https://pastebin.com/sdFGBfmq) and UserRegisterForm (https://pastebin.com/GbcmByF2) shown in the links.
I created an index.js file in the folder where the two forms reside, imported the forms and passed them to the material UI stepper component as shown in the link (https://pastebin.com/GM9EcVvy)
Finally i Created a context to be able to manage state of the data passed between the two forms namely AuthStepperContext as shown (https://pastebin.com/TP7MSJ7Q) I passed the provider to the main index.js file at root (https://pastebin.com/f5GFKHC8)
Above explains how my project is structured.
The Problem
At the last step of the form stepper I want to submit the data from the forms. I pass the handle Submit function from props to the last button in the stepper after a user has entered all the data. reference this code for stepper (https://pastebin.com/uu5j6ADB)
The problem comes where I call the AuthStepperContext in the index.js file that passes the two forms to the material ui stepper . I want to get the handleSubmit from the context and pass it as props to the stepper and also to the Business register form as its the last form to be filled by the user before submitting all the data.
import React, { useContext, useState } from 'react';
import BusinessRegisterForm from './BusinessRegisterForm';
import UserRegisterForm from './UserRegisterForm';
import HorizontalStepper from '../../controls/HorizontalOptionalStepper';
import Container from '#material-ui/core/Container';
import AuthStepperContext from '../../../Context/AuthContext';
import axios from 'axios'
import { useAuth } from '../../../Context/AuthContext'
export default function RegisterStepper() {
// the issue happens when i introduce this context
// and try to pass it down to the stepper
const {handleSubmit} = useContext(AuthStepperContext)
const getSteps = () => {
return ['Create Account', 'Add Business'];
}
const getStepContent = (step) => {
switch (step) {
case 0:
return <UserRegisterForm />;
case 1:
return <BusinessRegisterForm />;
default:
return 'Unknown step';
}
}
const isStepOptional = (step) => {
return ;
}
const [activeStep, setActiveStep] = useState(0);
return (
<Container maxWidth='sm'>
<HorizontalStepper
getSteps = {getSteps}
getStepContent = {getStepContent}
isStepOptional = {isStepOptional}
activeStep = {activeStep}
setActiveStep = {setActiveStep}
handleSubmit = {handleSubmit}
/>
</Container>
)
}
When I call the handleSubmit function from the Context and pass it down as props to the Stepper, as shown (https://pastebin.com/63HXPJkk), I get an error TypeError: Object(...)(...) is undefined
I first thought the error was due to using objects as initial state and had to replace every field with its own state.
Is there a way to capture data and submit data in the last form when a user wants to submit the form data?
So i figured out the problem for anyone who might come upon the same issue again.
The problem was with the was with the way I was importing one of my named modules. It is supposed to be wrapped in curly braces.
A similar solution was offered in a similar post here useContext() returns undefined
Here is the line in my code after I refactored my code to apply the changes to the stepper:
import {AuthStepperContext} from '../../../Context/AuthStepperContext';
I have an app which header contains icon which should be shown when the user is logged in. I keep my logged in info in sessionStorage but when it changes my component is not rendered again. I tried to use useEffect for that and useMemo but it doesn't worked.
The updating part:
const isLoggedIn = useMemo(() => sessionStorage.getItem('isLogged'), [sessionStorage.getItem('isLogged')]);
The usage:
{isLoggedIn === 'true' ? ['left'].map((anchor) => (
...some jsx
)) : null}
The sessionStorage value is a string: "false" or "true".
I have routes and constant header, the header is not a part of routes so when it changes my header is not rerenders so I tried to use useMemo for that.
Posting my answer as per clarification gained through comments.
If you are using Redux:
I would recommend to store the user logged-in information in redux store and connect to the isolated Header component via connect HOC and mapStateToProps. Whenever you update (upon successful user login) the user login status the component will listen to store updates.
Or
You can use React context approach if there is no redux used
// Declare it outside of your App component/any other file and export it
const GlobalState = React.createContext();
// Declare state variable to store user logged in info inside of your App component
const [isLoggedIn, setIsLoggedIn] = useState(false);
// Add them to context to access anywhere in your components via useContext
// In App render or where you have route mapping
<GlobalState.Provider value={{
isLoggedIn,
setIsLoggedIn
}}>
....
</GlobalState.Provider>
// Update the status using setIsLoggedIn upon successful login where you are making login call
// In your Header get it via useContext
const context = useContext(GlobalState);
`context.isLoggedIn` is what you need.
// You can use useEffect/useMemo approach to get the login status updates
Find more about React context and useContext
sessionStorage is not an observer object and you have to store the current authentication state into a variable or React state and use that variable in your component. And when you authenticated the user, you should update the variable to true and change that to false when the user logged out.
To implement what I said, you can get help from these ways:
Redux
React context
You can implement the React context by your self from scratch or using the React-hooks-global-state
UseMemo is used for memoizing calculated values. You should be using useCallback.useCallback is used for memoizing function references.
Refer this
const isLoggedIn = useCallback(() => sessionStorage.getItem('isLogged'), [sessionStorage.getItem('isLogged')]);
Can you try to put your sessionStorage data into State and update that state? As far as I know, react will not know about the session storage. So even if you change the manipulate the data in the sessionStorage directly it won't gonna update your UI.
let [storeData, setStoreData] = useState(true);
let isLoggedIn = useMemo(() => ({ sessionData: storeData }), [storeData]);
{isLoggedIn === 'true' ? ['left'].map((anchor) => (
...some jsx
)) : null}
<button
onClick={() => {
sessionStorage.setItem("isLogged", !storeData);
setStoreData(sessionStorage.getItem("isLogged"));
}} > Update Store </button>
I developing front-end with reactjs recently.
but i have a problem in my SPA project.
my webapp have tab menu like a text-editor.
I want each tab to always keep their input value. However, these values are lost because tabs are re-rendering when they disappear or are new.
Is there a way to keep these values without using storage like local-storage?
You will need to take advantage of the useState hook to store an object that contains the data from your tabs... so you can get the data from the object whenever the state changes... assuming you don't update it wrongly.
Find illustratio below:
import React, { useState } from 'react'
function MyTabComponent (props){
const [tabData, updateTabData] = useState({})
return(
// your component jsx here
// you can then fetch your tab data from the tabData object
)
}
Hoping you find this helpful.
Hope you all are fine. I am new to react redux world. I am learning and working on a call logging project. I have a few questions and it would great if someone can guide me whether I am doing it wrong or tell me the alternative.
I am using JWT to authenticate a user. Once the user details are verified. I am dispatching success action and in the reducer, I am setting the state to authenticated true and the response. I am also storing the token and expiryTime in localStorage
In the root file which is index file. I am checking if the token exists in localStorage and if so then dispatching sign in action.
Everything is working. But I am losing other values like a response from a server. When he logged in for the first time. How can I tackle this problem ?
Secondly, there is a User initial icon on the top right corner. I get the initial when a user logs in and it gets stored in auth state. it works fine but once again if I refresh the page it becomes null and I lose that initial.
so I tried another way and stored the initial in localStorage. But the navbar already rendered on the screen and I don't see any initial until I refresh the page.
I have passed new key in mapStateToProps. ii and stored initial from localStorage in it and it working fine. Is this a valid way of doing it ???
Regards
Meet
const SignedInLinks = (props) => {
return (
<ul className="right">
<li><NavLink to="/signin" onClick=
{props.signOut}>Log Out</NavLink></li>
<li><NavLink className="btn btn-floating pink lighten-1" to="/">
{props.ii ? props.ii : null }
</NavLink></li>
</ul>
)}
const mapStateToProps = state => {
return {
auth: state.auth,
ii: window.localStorage.getItem('ui')
}
}
export default connect(mapStateToProps, { signOut })(SignedInLinks);
Rather than using localStorage in mapStateToProps, intialize your ii state in your reducer corresponding to that state and then pass it to your component via mapStateToProps. Something like this.
const iiReducer = (state = window.localStorage.getItem('ui') || false, action) => {
/*.
.
. Other Logic
.
.*/
return state
}
and then use it normally as you would from a store's state
const mapStateToProps = state => {
return {
auth: state.auth,
ii: state.ii
}
}
Hope this helps !
I believe I have an idea of what the problem is (I'm kind of a beginner in react and redux aswell, so tell me if I'm speaking nonsense).
You say that you store the token in localstorage (it is recommended not to do that btw, the recommended way is to store it in a cookie), and if a valid token is found in localstorage, you log in. I'm guessing that you store the response from the server (including the icon) in the app's state, or in the redux store? If that is the case, this information will be removed when you update the browser (f5),therefore not being loaded anymore.
The solution is to make sure to load the relevant data when the component mounts, so in your componentDidMount method (if you don't have one, make one), and set the state in that method.