How to access to Home component even though not logged in? - javascript

In a React project, I have created certain components which have access only when logged in or else would be redirected to Login Page. While I was told to make few changes that are, the user should have access to the Home page even though not logged in. For accessing other components, the user must have a token. See the following code for reference
export const UserContext = createContext();
const Routing = () => {
const history = useHistory()
const { state, dispatch } = useContext(UserContext)
const [value, setValue] = useState(null)
const user = sessionStorage.getItem('token')
useEffect(() => {
if(user) {
dispatch({ type: "USER", payload: user })
} else {
history.push('/login')
}}, [])
return (
<>
<Router>
<Switch>
{/* Give access to user even though not logged in or has token */}
<Route exact path="/" component="Home" />
{/* I won't let user access this Component, unless token is available */}
<Route exact path="/videoCall" component="VideoCall" />
</Switch>
</Router>
</>
)
}
const App = () => {
const [state, dispatch] = useReducer(reducer, initialState)
return (
<UserContext.Provider value={{state, dispatch}}>
<Router>
<Switch>
<Route exact path="/login" component={LoginPage} />
<Routing />
</Switch>
</Router>
</UserContext.Provider>
)
}
export default App;
So, what could be the best possible solution, give access to the Home page even though not logged in, but, redirect the user to Login Page when trying to access other components like VideoCall.

Maybe something like this will help - a Router comonent of your own, which will check whether the user is authenticated and will decide whether to let him in the component or not. If you want the Home component to be rendered, just make it render, and components you don't want to be rendered if user is not logged in, just add a check in the component (or in the router component of you own) whether the user is authenticated...
BTW, every render, the user const is assigned the sessionstorage.getItem() value... every render. If you want it to happen only once, or only when a specfic variable changes - you should totally use useMemo (const user = useMemo(()=>sessionStorage.getItem(), [])) (https://reactjs.org/docs/hooks-reference.html#usememo)

Related

Can't store the data of my state to localStorage due to useContext

I'm trying to keep the input value on page refresh, basically, when I type something in an input and hit refresh, I want the input to still hold the value that I typed. I've done some searching and found that I could solve that problem with the help of localStorage. I've tried using it on a practice react app, where I have only one input and I'm using the state that I declared in that component. Like this :
function App() {
const [data, setData] = useState("");
useEffect(() => {
setData(window.localStorage.getItem('data'));
}, []);
useEffect(() => {
window.localStorage.setItem('data',data);
}, [data]);
return (
<div className="container">
<input type="text" placeholder="Name" value={data} onChange={(e)=>setData(e.target.value)}/>
</div>
)
}
export default App;
And this works perfectly fine, I'm able to keep the data as I wanted to. However, when I try to implement the same method in my actual project, it simply doesn't work that way and I suspect it's because I'm using useContext to pass the state from another component but I have no idea how to fix the problem since the console doesn't give me any errors, it just doesn't work.
Here's my project's App.js file, where I have set up my state and use context to pass it through my components :
function App() {
const [name, setName] = useState("");
return (
<FormContext.Provider value={{name,setName}}>
<Router>
<Routes>
<Route path='/' element={<Home/>}/>
<Route path='/Personal' element={<Personal/>}/>
<Route path='/Experience' element={<Experience/>}/>
<Route path='/Completed' element={<Completed/>}/>
</Routes>
</Router>
</FormContext.Provider>
);
}
export default App;
And here's my Personal.jsx file, where I have the input:
const {name,setName} = useContext(FormContext)
useEffect(() => {
setName(window.localStorage.getItem('name'));
}, []);
useEffect(() => {
window.localStorage.setItem('name',name);
}, [name]);
Here's what the input looks like:
<Input value={name} onChange={(e)=>setName(e.target.value)} type="text" placeholder="Name *"/>
I implement the same method as in my practice app, but It doesn't work here.
It does not work because you override name each time you invoke <Personal /> element by executing that second useEffect. That second effect is executing both initially and whenever name changes - and that initial execution is one that was overriding your name, because name initially have "" as value from context, and you basically use that "" to update local storage each time you invoke element.
You should move local storage part one level up, to context. Rewrite to something like this:
function App() {
const [name, setInputName] = useState(window.localStorage.getItem('name')); // Here you are initially trying to retrieve last saved value
const setName = (name) => {
setInputName(name);
window.localStorage.setItem('name',name); // And here you are saving to local storage each time user change input content
}
return (
<FormContext.Provider value={{name, setName}}>
<Router>
<Routes>
<Route path='/' element={<Home/>}/>
<Route path='/Personal' element={<Personal/>}/>
<Route path='/Experience' element={<Experience/>}/>
<Route path='/Completed' element={<Completed/>}/>
</Routes>
</Router>
</FormContext.Provider>
);
}
export default App;
And like this no need to bother with local storage inside Personal.jsx, you hide that implementation inside form context, and expose only getter and setter to Personal. So Personal should be rewritten to something like this:
const {name,setName} = useContext(FormContext)
return <Input value={name} onChange={(e)=>setName(e.target.value)} type="text" placeholder="Name *"/>
As you can see there is no need for useEffects now, you just use bare minimums, only getter and setter that are exposed from context, and context will do any additional work.

React Router and keycloak Route Guarding is redirecting upon page refresh

I am using React Router v5, keycloak-js and #react-keycloak/web to implement route guarding and user authentication. The main idea is that the SecuredPage component is to be protected from unauthenticated users, wherein they should be redirected to /login if they are not yet logged in.
Here is how my app is set up:
App.js:
const App = (props) => {
const keycloak = new Keycloak("/keycloak.json");
return (
<div className="app">
<ReactKeycloakProvider authClient={keycloak}>
<Switch>
<Route path="/login" exact>
<Login />
</Route>
<RouteGuard path="/secured" exact component={SecuredPage} />
</Switch>
</ReactKeycloakProvider>
</div>
);
};
And the following is my RouteGuard component:
const RouteGuard = ({ component: Component, ...rest }) => {
const { keycloak } = useKeycloak();
const isLoggedIn = keycloak.authenticated;
return (
<Route
{...rest}
render={(props) => {
if (isLoggedIn) {
console.log("USER IS LOGGED IN, RENDERING COMPONENT");
return <Component />;
} else {
console.log("USER NOT LOGGED IN, REDIRECT TO LOGIN");
return (
<Redirect
to={{
pathname: "/login",
state: {
error: "You must login to continue.",
from: props.location.pathname,
redirected: true,
},
}}
/>
);
}
}}
/>
);
};
The app works great if I manually navigate to /secured, then I get redirected to keycloak's login page. The problem is, once I am already logged in and I go to /secured page, and from within that page, reload (F5), I get thrown into /login even though my keycloak session is still up. This doesn't happen if I navigate normally or when using the back and forward buttons of the browser. It only seems to happen on page reload or when I manually type into the URL bar.
I'm thinking maybe the RouteGuard component returns the Redirect before the isLoggedIn variable gets initialized during a page reload, but I can't figure out how I should make it so that the RouteGuard component's redirect waits to check the isLoggedIn variable?
Is there a better way on implementing Route Guards using React Router?

Context is reset to null when browser URL is manually changed

I have created AuthContext that holds currently logged in user:
// auth-context.ts
export interface IAuthContext {
auth: IMe | null;
setAuth: (user: IMe | null) => void;
}
const AuthContext = React.createContext<IAuthContext>({
auth: null,
setAuth: (auth: IMe | null) => {}
});
export default AuthContext;
The simplified main rendered App component where I use the context provider looks like this:
const App: FC = () => {
const [auth, setAuth] = useState<IMe | null>(null); // problematic line
return (
<AuthContext.Provider value={{auth, setAuth}}>
<BrowserRouter>
<Routes>
<Route path='/' element={<Main/>}/>
<Route path="/login" element={<Login/>}/>
<Route path="/register" element={<Register/>}/>
<Route path="/about" element={<About/>}/>
</Routes>
</BrowserRouter>
</AuthContext.Provider>
);
}
Overall, the application works fine.
However, when I navigate the application by manual URL changes in the web browser, followed-up by hitting the Enter (therefore: page refresh), then there is a problem with auth context reset to null...
I think when I change the URL manually then the whole App component is re-rendered and therefore the AuthContext state is reseted to null by the problematic line.
The idea is that the Login component is using the setAuth inside of it after the form-related promise is positively resolved.
Any tip how can I protect myself from that re-render problem, and therefore context resettng to default null value? I think this scenario is kind of basic, but I am beginner in frontend stuff. Thank you!

While using react-router 5 with redux 7 react-router <Link> is not resetting state after going to new route

I'm using the following versions:
`"react-router": "^5.2.0",`
`"react-router-domreact-router": "^5.2.0",`
Not sure if my current setup is React-router 5 friendly or not, I was using a version prior to v5 before this.
The problem in this example is with <Route component={withTracker(InterviewContainer)} path="/interviews/companies/:companyId" /> and <Link/>
Here's my scenario:
Home page loads with a list of company links
Click on a company <Link /> which routes me to /interviews/companies/:companyId
Page loads fine, I see images, etc. for that particular company
Click browser's Back button
Click on a different company <Link /> that points to a different companyId
Problem: for #5, when the company page initially loads, it's loading with stale images and data for some reason. So in other words, I'm seeing the previous company's data & images from step #2 briefly until my React hook makes a new call to get data for this new CompanyId and repaints the browser with the right data (data for the companyId represented in the new route)
index.tsx (note the use of BrowserRouter here)
import { BrowserRouter as Router } from 'react-router-dom';
//...more code and then:
render(
<>
<div className="Site">
<Provider store={store}>
<Router>
<App />
</Router>
</Provider>
</div>
<Footer />
</>,
);
App.ts
import { Route, RouteComponentProps, Switch } from 'react-router-dom';
...more code and then here are my routes:
<Switch>
<Route component={withTracker(HomePageContainer)} exact path="/" />
<Route
path="/companies/:companyId/details"
render={(props: RouteComponentProps<{ companyId: string }>) => (
<CompanyDetailContainer {...props} fetchCompanyNew={fetchCompanyNew} httpRequest={Request} useFetchCompany={useFetchCompany} />
)}
/>
<Route component={withTracker(InterviewContainer)} path="/interviews/companies/:companyId" />
<Route component={withTracker(About)} path="/about" />
<Route component={withTracker(Container)} path="/" />
<Route component={withTracker(NotFound)} path="*" />
</Switch>
Here is how the company Link is coded:
Note: I am using Redux State
"react-redux": "^7.2.1",
"redux": "^4.0.5",
"redux-thunk": "^2.3.0",
InterviewContainer.tsx (the parent that does the company fetching)
class InterviewContainer extends Component<PropsFromRedux & RouteComponentProps<{ companyId: string }>> {
componentDidMount() {
const { fetchCompany } = this.props;
const { companyId } = this.props.match.params;
fetchCompany(companyId);
}
render() {
const { company } = this.props;
return (company && <Interview className="ft-interview" company={company} />) || null;
}
}
const mapState = (state: RootState) => ({
company: state.company.company,
});
const mapDispatch = {
fetchCompany: fetchCompanyFromJSON,
};
const connector = connect(mapState, mapDispatch);
type PropsFromRedux = ConnectedProps<typeof connector>;
export default withRouter(connect(mapState, mapDispatch)(InterviewContainer));
LinkItem.tsx (one of the children rendered by InterviewContainer and receives the company from InterviewContainer)
render() {
const { company } = this.props,
uri = company.notInterviewed ? `companies/${company.id}/details` : `/interviews/companies/${company.id}`,
className = `margin-top-10 margin-bottom-10 ${company.notInterviewed ? 'ft-company-not-interviewed' : ''}`;
const link = (
<Link className={className} id={company.id.toString()} to={uri}>
<span id="company-name">{company.name}</span>
</Link>
);
}
I think I may have to reset Redux state on route change. I see people in the past have used LOCATION_CHANGE but that's outdated and that's a constant provided by third party redux libs that are no longer supported. So not sure how to do that with Redux v7+
So I think I just need a way to detect a location change and then somehow update my react store to reset company (set company: state.company.company, to undefined from my redux action)
I know things like this can be cumbersome. Have you tried passing in state with the Link as <Link to={uri} state={...someState} />. Then wherever it is loading it should rerender or reset props according to that. Maybe throw some skeleton loaders or conditional rendering logic.

The props of a component rendered inside a route will never update

What I am trying to do is getting the login user's information from the login component, so that the App component could pass the login user's information to the main component.
Here is some code in the App.js:
state = {
usersArr: [], // all the users
logInUser: 0, // the login user, default value as 0
}
getLogInUser = () => {
return this.state.logInUser;
}
assignLogInUser = user =>{
this.setState({logInUser: user});
console.log("LogIn: " + this.state.logInUser.username);
}
render(){
return(
<div className="App">
<Router>
<div className="App">
<Switch>
<Route path="/main" exact render={() => <Main logInUser={() => this.getLogInUser()}/>}/>
<Route path="" exact render={() => <Landing usersArr={this.state.usersArr} getUser = {this.assignLogInUser}/>}></Route>
</Switch>
</div>
</Router>
)
}
The first page would be landing page and the landing component would be rendered. The landing component will render the login component. The login component will check if the username and password from input would match any user in the usersArr. If it does match, the login component will call the assignLogInUser to assign the login user.
landing.jsx:
render(){
return(
<Login usersArr={this.props.usersArr} getUser={this.props.getUser}/>
)
}
login.jsx:
constructor(props){
super(props);
this.userName = React.createRef(); // username referrence
this.password = React.createRef(); // password referrence
}
validateLogin = () => {
this.props.usersArr.forEach(user => {
if(user.username === this.userName.current.value && user.password === this.password.current.value){
this.props.getUser(user);
}
})
}
The problem is that the logInUser passed into the main is always 0.
Looks like once the route has been set up, it will never update the component it's going to render. No matter what I tried, I would get the default value 0 for logInUser in the main component.
I can probably use redux, but I have to refactor all of the code.
I have tried forceUpdate() in the main component, or using key in the main component. None of them works.
Any help would be appreciated.

Categories