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>
Related
I'm relatively new to React and JavaScript and I'm building a website but I'm having a bit of an issue with passing Data via components from child to parent.
So:
I have my App.js script which is used as a router with react-router-dom, but I would like to store a boolean value using the useState hook. This boolean value that I would like stored should be passed on from a component called Login. I have the script setup to pass that data however the boolean is only stored as long as the Login COmponent page is active and when it is not rendered the boolean store by the useState hook in the App.js script just goes to 'undefined'. I'm assuming that this is happening because the app.js page constantly re-loads and re-renders, so how could I store that value even when the login page is not being rendered?
This is the code setup to pass that data:
app.js
const [authValue, setAuthValue] = useState(false);
const changeValue = (value) => {
setAuthValue(value)
}
And where the Login is called:
<Route path='/signin' element={<Login changeValue={changeValue}value={authValue} />} />
Login.jsx:
const Login = ({changeValue, value}) => {
const [isValid, setIsValid] = useState(true)
changeValue(isValid)
}
The useState hook in React is a solution for component-level state management in functional components.
So, the value of isValid is stored only in Login.js and can be passed as a props to its children components.
Of course, you can also pass some state (or values) from child to parent via functions passed from parent into the child, but this is not the way you should consider if you want to use a state in whole app.
If you need to have the state that should be persistent across the app components, even after one component was unmounted, you should consider global management solutions like React Context API, Redux, MobX or similar libraries.
Try putting the changeValue(isValid) into a useEffect() hook. So:
useEffect(() => {
changeValue(isValid)
}
The smarann app on this link, in this app, take-test page should open on a new window
In this image, take-test route needs to open on a new window.
I have tried to authenticate (which is checking the local storage where user details are saved) while opening on a new tab and then updating a state to verify that the user is authenticated and then rendering the protected component based on this state.
const PrivateRoute = ({component: Component, ...otherProps}) => {
const {isAuthenticated} = useAuth()
const [isNewTabAuthenticated, setIsNewTabAuthenticated] = useState(false);
useEffect(() => {
const user = JSON.parse(localStorage.getItem('smarann-user-data'));
if(user.email) {
setIsNewTabAuthenticated(true);
}
}, [])
return (
<Route
render={() =>
isAuthenticated === true || isNewTabAuthenticated === true ? (
<Component {...otherProps}/>
) : (
<Redirect to="/" />
)
}
/>
);
};
I have also tried running above useEffect in App.js file and updating the state there. But it is not re-rendering the page after updating the state to check whether it is authenticated or not.
This is the limitation of react-router-dom as the states are not updating on opening the route on a new window.
Can we achieve this using redux as the state manager and using redux's state persistent functionality ?
Issue
I can't speak to the value of isAuthenticated from the useAuth hook on the initial render, but there is an issue with the local isNewTabAuthenticated state. The useEffect hook runs at the end of the render cycle, so by then it's too late to check localStorage and set the isNewTabAuthenticated state, the false value is used and the redirect is likely issued.
Solution
Initialize the isNewTabAuthenticated state directly from localStorage. It's all synchronous code, so start with the correct initial state.
Example:
const PrivateRoute = props => {
const {isAuthenticated} = useAuth();
const [isNewTabAuthenticated, setIsNewTabAuthenticated] = useState(() => {
const user = JSON.parse(localStorage.getItem('smarann-user-data'));
return !!user?.email;
});
return isAuthenticated || isNewTabAuthenticated
? (
<Route {...props} />
) : (
<Redirect to="/" />
);
};
You can check if the useAuth hook and state has a similar issue.
Can we achieve this using redux as the state manager and using redux's
state persistent functionality?
Yes, you certainly could. The redux state should also be persisted to some longer-term storage, typically localStorage. You might need additional "pending"/"loading" type logic if the auth state is checked on the initial app render.
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.
I have to get information about user before link work and I don't Know how can I do this.
It is not all my code but similar. On click I have to get info and then give it to component in which I link to, but link works first and info does not have time to geted.
const [userId, setUserId] = useState(null);
const filterUserbyId = (id) => {
setUserId(id);
}
return(
<Link
onClick={()=>filterUserbyId(props.id)}
to={{
pathname: `/user/${props.id}`,
state: {
userId: userId
}
}}>
)
Also this is the warning but it says exactly that I tell above
Warning: Can't perform a React state update on an unmounted component.
This is a no-op, but it indicates a memory leak in your application.
To fix, cancel all subscriptions and asynchronous tasks in a useEffect
cleanup function.
You have over-complicated such a simple task.
Instead of trying to fetch the data from the database and then pass that fetched data to the new component to which you will redirect to, you could just pass the user id as a URL parameter
return(
<Link to={`/user/${props.id}`} />
);
In the other component, extract the user id from the URL parameter using the useParams() hook
const { userID } = useParams();
Note: userID is the dynamic route parameter. For the above statement to work, route to this component should be defined as:
<Route path="/user/:userID" component={/* insert component name */}/>
Once you have the user id, use the useEffect() hook to fetch the data from the database and display it to the user.
useEffect(() => {
// fetch the data here
}, []);
Alternative Solution - (not recommended)
You could also do it the way originally tried but for this to work, you need to change the Link component to a normal button element and when that button is clicked, fetch the data from the database and then programmatically change the route using the useHistory() hook, passing along the fetched data to the new route.
const routerHistory = useHistory();
const filterUserbyId = (id) => {
// fetch user data
...
// redirect to another route
routerHistory.push(`/user/${props.id}`, { state: data });
}
return(
<button onClick={() => filterUserbyId(props.id)}>
Button
</button>
)
I suggest that you don't use this solution because you don't want to wait for the data to be fetched from the database before changing the route. Route should be changed as soon as user clicks and data should be fetched inside the new component.
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.