Hide Some Element With Local Storage In React - reactjs

I want to hide some navigation when the user haven't login yet, so I use local storage to save user's id and use if logic to hide and show the navigation, but when i clear the data in local storage and compare it to null, the navigation still showed up.
Here is the code to save data in local storage
loginUser = () => {
Axios.post('http://private-6fdd31-intern1.apiary-mock.com/interns/login', this.state.user)
.then((res) => {
if(res.data.role === "admin")
{
localStorage.setItem("user", res.data.user_id)
this.props.history.push('/member-list');
}
}, (err) => {
console.log(err);
})
}
This is how I compare and clear the data when logout navigation is clicked
handleLogout = () => {
localStorage.clear("user");
}
render() {
return(
<Router>
<Fragment>
<div className="navigation">
{ localStorage.getItem("user") !== null?
<Fragment>
<Link to="/member-list">Member</Link>
<Link to="/override-list">Override</Link>
<Link onClick={this.handleLogout} to="/">Logout</Link>
</Fragment>
: null
}
</div>
<Route path="/" exact component={routeProps => <Login {...routeProps}/>}/>
<Route path="/member-list" component={MemberDashboard}/>
<Route path="/override-list" component={OverrideDashboard}/>
</Fragment>
</Router>
)
}

react native wont call render() method as long as there is no state or props update, you need to call this.forceUpdate() to force a rerender. Documentation: https://facebook.github.io/react/docs/component-api.html like
handleLogout = () => {
localStorage.clear("user");
this.forceUpdate()
}

Maybe use the state to save the user when you set to local storage and same thing when you handleLogout?
if(res.data.role === "admin")
{
this.setState({ user: res.data.user_id})
localStorage.setItem("user", res.data.user_id)
this.props.history.push('/member-list');
}
handleLogout = () => {
localStorage.clear("user");
this.setState({ user: ""})
}
So when state is updated, the component re-renders.

Related

Make react-router-dom v6 pass path as key to rendered element

I think I may need a paradigm shift in my thinking here, so I'm open to those kinds of answers as well here.
Consider the following simplified example:
export const App = (p: { apiClient: SomeApiClient }) => {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/posts/:postId" element={<Post apiClient={p.apiClient} />} />
</Routes>
);
}
export const Home = () => {
return <h1>Home!</h1>
}
export const Post = (p: { apiClient: SomeApiClient }) => {
const { postId } = useParams();
const [ state, setState ] = useState<PostState>({ status: "loading" });
// When the component mounts, get the specified post from the API
useEffect(() => {
if (state.status === "loading") {
(async () => {
const post = await p.apiClient.getPost(postId);
setState({ status: "ready", post });
})();
}
})
return (
<h2>Posts</h2>
{
state.status === "loading"
? <p>Loading....</p>
: <div className="post">
<h3>{state.post.title}</h3>
<div className="content">{state.post.content}</div>
</div>
}
)
}
export type PostState =
| { status: "loading" }
| { status: "ready"; post: BlogPost };
export type BlogPost = { title: string; content: string };
This works fine the first time, but pretend there's a <Link /> on the page that goes to the next post. When I click that link, the URL changes, but the page content doesn't, because React Router is not actually re-mounting the <Post .../> component. That component correctly receives the updated postId and is re-rendered, but since it doesn't get re-mounted, the useEffect logic doesn't run again and the content stays the same.
I've been solving this very awkwardly by creating intermediary components like so:
export const App = (p: { apiClient: SomeApiClient }) => {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/posts/:postId" element={<PostRenderer apiClient={p.apiClient} />} />
</Routes>
);
}
export const PostRenderer = (p: { apiClient: SomeApiClient }) => {
const { postId } = useParams();
return <Post key={postId} postId={postId} apiClient={p.apiClient} />
}
export const Post = (p: { postId: string; apiClient: SomeApiClient }) => {
// ....
}
But I'm starting to get a lot of those, and literally all they do is take the param from the URL and use it as a key on the actual target component. I've read through the react-router-dom docs and am not finding anything that indicates there's a way to automate this. I must be thinking about this wrong.... Any suggestions are appreciated.
I think a more common and practical solution is to add the postId as a dependency to the useEffect to rerun the asynchronous logic when the route param changes.
Example:
export const Post = (p: { apiClient: SomeApiClient }) => {
const { postId } = useParams();
const [state, setState] = useState<PostState>({ status: "loading" });
// When the post id updates, get the specified post from the API
useEffect(() => {
const fetchPostById = async (postId) => {
setState({ status: "loading" });
const post = await p.apiClient.getPost(postId);
setState({ status: "ready", post });
};
fetchPostById(postId);
}, [postId]);
return (
<h2>Posts</h2>
{
state.status === "loading"
? <p>Loading....</p>
: <div className="post">
<h3>{state.post.title}</h3>
<div className="content">{state.post.content}</div>
</div>
}
)
};

Value of react context is not updated [duplicate]

This question already has answers here:
Why does calling react setState method not mutate the state immediately?
(9 answers)
Closed 1 year ago.
I have a client implemented in react typescript, which needs to work with user data.
Therefore, I've created an AppContext.
//appState.ts
export interface UserStateProperties {
authenticated: boolean,
user: GetUserResponse | undefined,
notificationManager: NotificationManager | undefined
}
export interface AppContextProperties {
userState: UserStateProperties,
setUserState: Dispatch<SetStateAction<UserStateProperties>>
}
const AppContext = React.createContext<AppContextProperties>({
userState: {
authenticated: false,
user: undefined, // userData like name, level, ...
notificationManager: undefined // contains socket to receive notifications
},
setUserState: () => {}
});
export default AppContext;
In my App component, I instantiate a state for the user and passed it as value to an AppContext.Provider.
// App.tsx
function App() {
const [userState, setUserState] = useState<UserStateProperties>({
authenticated: false,
user: undefined,
notificationManager: undefined
});
return (
<Suspense fallback={'Die Seite lädt...'}>
<AppContext.Provider value ={{userState, setUserState}}>
<Router history={createBrowserHistory()}>
<Switch>
<Route path='/' exact component={ Home }/>
<Route path='/auth/forgot' exact component = { Forgot } />
<Route path='/auth/:type' exact component={ Auth }/>
// A lot more components
<Route component={ ErrorPage }/>
</Switch>
</Router>
</AppContext.Provider>
</Suspense>
);
}
Each of my components (e.g Home)
// Home.tsx
...
return(
<Frame current='/'>
<section className='home-landingPage'>
...
</Frame>
)
are wrapped in a Frame component
// Frame.tsx
interface FrameProperties {
children: React.ReactNode | React.ReactNode[],
current?: string
}
export default function Frame(props: FrameProperties) {
return (
<div className='frame-container'>
<NavigationBar current={ props.current } />
{ props.children }
<Footer/>
</div>
)
}
which adds a NavigationBar to the component.
In this NavigationBar, I am rendering things like signin/signup button (in case authenticated == false) or signout button, profile picture, level progress (in case authenticated == true).
To ensure that the navigation bar displays the correct information, I use an effect hook, which updates the userStatus.
//Navigation.tsx
import AppContext from '../../../context/appState';
...
export default function NavigationBar(props: NavigationBarProps) {
const {userState, setUserState} = useContext(AppContext)
const updateUser = async () => {
fetchGetOwnUser().then(response => {
if(response.status === 200) {
setUserState({...userState, user: response.data}); // update user
}
}).catch(err => {
console.error(err);
});
console.log("USERSTATE AFTTER: ");
console.log(userState);
}
const updateAuthenticationStatus = async () => {
const accessToken = localStorage.getItem('accessToken');
if(accessToken) {
fetchVerifyToken({token: accessToken})
.then(response => {
if(response.status == 200){
const userId = getTokenPayload(accessToken).sub;
setUserState({authenticated: true, user: userState.user, notificationManager: userState.notificationManager || new NotificationManager(userId)}); //update authentication status of user
}
})
.catch(err => {
console.error(err);
});
console.log("USERSTATE AFTER: ");
console.log(userState);
}
useEffect(() => {
console.log("USERSTATE BEFORE: ");
console.log(userState);
if(userState.authenticated){
updateUser();
}else{
updateAuthenticationStatus();
}
}, []);
}
However, although updateAuthenticationStatus and updateUser are executed succesfully, the userState object does not change. The console shows the following output.
USERSTATE BEFORE: Object { authenticated: false, user: undefined, notificationManager: undefined }
USERSTATE AFTTER: Object { authenticated: false, user: undefined, notificationManager: undefined }
Thanks for any help in advance!
Your code looks fine, you just have written your log statements in spots where they are useless. fetchVerifyToken is asynchronous, so a log statement on the next line will run before fetchVerifyToken is complete, and even if you awaited the promise, userState is a local const and is never going to change.
What you really care about is that the component rerenders with the new value, so put your console.log in the body of the component to verify that it rerenders. For example:
export default function NavigationBar(props: NavigationBarProps) {
const {userState, setUserState} = useContext(AppContext)
console.log('rendered with', userState);
// ...

Firebase.auth().onstateChanged() not working after clearing browser cache

I cleared my browser cache and now my app cant login
export function IsUserRedirect({ user, loggedInPath, children, ...rest}){
return (
<Route
{...rest}
render={() => {
if(!user){
return children;
}
if(user){
return (
<Redirect
to={{
pathname: loggedInPath
}}
/>
)
}
return null;
}}
/>
)
}
export function ProtectedRoute({ user, children, ...rest}){
return(
<Route
{...rest}
render={({location}) => {
if(user){
return children;
}
if(!user){
return (
<Redirect
to={{
pathname: 'signin',
state: { from : location}
}}
/>
)
}
return null;
}}
/>
)
}
I think it stored my login info on the browser as a localstorage but after clearing it still recognizes it as the user is logged in and takes me to the next page.
But on the next page i have kept a loading state for getting user data, as it doesnt has any user it just keeps loading and goes nowhere. can someone help
export default function useAuthListener(){
const [user, setUser] = useState(JSON.parse(localStorage.getItem('authUser')));
const {firebase} = useContext(FirebaseContext);
useEffect(() => {
const listener = firebase.auth().onAuthStateChanged((authUser) => {
if(authUser){
localStorage.setItem('authUser', JSON.stringify(authUser));
setUser(authUser);
}else {
localStorage.removeItem('authUser');
setUser(null);
}
});
return ()=> listener();
}, []);
return { user};
}
just a quick suggestion:
localStorage.clear();
Source:
(Another aproach might be a reboot, to see if it acts differently...)
Greetings,
Alexander

Simulate localStorage data in test

I am using Create React App.
I am trying to simulate isLoggedIn behaviour in my component to get all lines code coverage.
To do that localStorage key: user must exist with data.accessToken
I tried set localStorage data in the test but it is not working. the same method actually working in isLoggedIn function and generate 100% line coverage.
isLoggedIn function
export const isLoggedIn = () => {
const userFromLocalStorage = store('user');
return _get(userFromLocalStorage, 'data.accessToken', false);
};
PrivateRoute.js:
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route
{...rest}
render={props =>
isLoggedIn() ? (
<Component {...props} />
) : (
<Redirect to={{ pathname: 'login' }} />
)
}
/>
);
PrivateRoute.spec.js
import store from 'store2';
describe('PrivateRoute Logged In', () => {
store('user', {
data: {
accessToken: 'dfg',
},
});
const ShallowPrivateRoute = shallow(
<PrivateRoute path="/" name="Home" component={TestComponent} />
);
it('should cover logged in case', () => {
expect(ShallowPrivateRoute).toBeDefined();
});
});
Is there the way I can mock isLoggedIn function to return true just for one test??
What is the best way to test that kind of behaviour?
You could mock the entire file like this:
jest.mock("you-module", () =>({...methodsMock}));
or you could recieve isLoggedIn in props, that way you only need to pass a mock function when you render your component in test.
<Component isLoggedIn={jest.fn().mockReturnValue(true)} />

Code splitting route components wrapped in a HOC with React Loadable

I am running into problems using React Loadable with route based code splitting using Webpack 3.11.
When I try to render my app on the server my async modules immediately resolve without waiting for the promise. Thus the SSR output becomes <div id="root"></div>.
App.js:
const App = () => (
<Switch>
{routes.map((route, index) => (
<Route key={index} path={route.path} render={routeProps => {
const RouteComponent = route.component
return <RouteComponent {...routeProps} />
}} />
))}
</Switch>
)
I've defined my async route components with React Loadable like this:
Routes.js
function Loading ({ error }) {
if (error) {
return 'Oh nooess!'
} else {
return <h3>Loading...</h3>
}
}
const Article = Loadable({
loader: () => import(/* webpackChunkName: "Article" */ '../components/contentTypes/Article'),
loading: Loading
})
const Page = Loadable({
loader: () => import(/* webpackChunkName: "Page" */ '../components/contentTypes/Page'),
loading: Loading,
render (loaded, props) {
let Component = WithSettings(loaded.default)
return <Component {...props}/>
}
})
export default [
{
path: `/:projectSlug/:env${getEnvironments()}/article/:articleSlug`,
component: Article,
exact: true
},
{
path: `/:projectSlug/:env${getEnvironments()}/:menuSlug?/:pageSlug?`,
component: Page
}
]
WithSettings.js
export default (WrappedComponent: any) => {
class WithSettings extends React.Component<WithSettingsProps, WithSettingsState> {
static displayName = `WithSettings(${WrappedComponent.displayName || WrappedComponent.name || 'Component'})`
state = {
renderWidth: 1200
}
componentDidMount () {
this.loadSettings({ match: { params: { projectSlug: '', env: '' } } })
window.addEventListener('resize', this.onResize)
this.onResize()
}
componentWillUnmount () {
if (isClient) {
window.removeEventListener('resize', this.onResize)
}
}
componentDidUpdate (oldProps) {
this.loadSettings(oldProps)
}
onResize = () => {
this.setState({ renderWidth: this.getLayoutWidth() })
}
getLayoutWidth () {
return (document.body && document.body.offsetWidth) || 0
}
loadSettings (oldProps) {
const { settings, request, getNewSettings } = this.props
const { projectSlug: oldProjectSlug, env: oldEnv } = oldProps.match.params
const { projectSlug: newProjectSlug, env: newEnv } = this.props.match.params
if (
(
oldProjectSlug !== newProjectSlug ||
oldEnv !== newEnv
) ||
(
settings === undefined ||
(request.networkStatus === 'ready')
)
) {
getNewSettings()
}
}
render () {
const { settings, request, history, location, match } = this.props
const { renderWidth } = this.state
if (!settings || !request || request.networkStatus === 'loading') {
return <div />
}
if (request.networkStatus === 'failed') {
return <ErrorBlock {...getErrorMessages(match.params, 'settings')[4044]} fullscreen match={match} />
}
return (
<WrappedComponent
settings={settings}
settingsRequest={request}
history={history}
location={location}
match={match}
renderWidth={renderWidth}
/>
)
}
}
hoistNonReactStatic(WithSettings, WrappedComponent)
return connect(mapStateToProps, mapDispatchToProps)(WithSettings)
}
I've managed to narrow it down to the WithSettings HOC that I am using to wrap my route components in. If I don't use the WithSettings HOC (as with the Article route) then my SSR output waits for the async import to complete, and the server generated html includes markup related to the route (good!). If I do use the HOC (as with the Page route) then the module immediately resolves and my SSR output turns into <div id="root"></div because it no longer waits for the dynamic import to complete before rendering. Problem is: I need the WithSettings HOC in all my routes as it fetches required data from the server that I need to render the app.
Can anyone tell me how I can use a HOC and use React Loadable's async component for route components so that it works on the server?
Managed to figure it out.
It was due to my HOC. In the render method it would return <div /> when settings or request where undefined or request.networkStatus is loading. It turns out this tripped up server side rendering. Removing this block was enough to make it work.

Resources