Skip to content

Commit

Permalink
useProtected (#79)
Browse files Browse the repository at this point in the history
  • Loading branch information
aradmargalit authored Jan 2, 2021
1 parent 9eb4747 commit 5efa8ca
Show file tree
Hide file tree
Showing 11 changed files with 96 additions and 64 deletions.
11 changes: 3 additions & 8 deletions client/src/components/DraftPage/DraftPage-redux.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
import React from 'react';
import { useSelector } from 'react-redux';
import { Redirect } from 'react-router-dom';
import { RootState } from '../../reducers';
import { User } from '../../types';
import { useProtected } from '../../hooks/useProtected';

import Drafts from '../Drafts';

function DraftPageRedux(): JSX.Element {
const user: User = useSelector((state: RootState) => state.auth);

if (!user) return <Redirect to="/" push />;

useProtected();
return (
<div className="width80">
<Drafts />
Expand Down
19 changes: 5 additions & 14 deletions client/src/components/Drafts/Drafts-redux.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import axios from 'axios';
import React, { useEffect, useState } from 'react';
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { Redirect } from 'react-router-dom';
import { setReview, updateDraftId, updateDrafts, fetchUser } from '../../actions';
import { useHistory } from 'react-router-dom';
import { setReview, updateDraftId, updateDrafts } from '../../actions';
import { RootState } from '../../reducers';
import { Review } from '../../types';
import SearchableReviewDisplay from '../SearchableReviewDisplay';
Expand All @@ -11,16 +11,7 @@ export default function DraftsRedux(): JSX.Element {
const dispatch = useDispatch();
const drafts: Review[] = useSelector((state: RootState) => state.drafts);
const renderMath: boolean = useSelector((state: RootState) => state.user.renderMath);
const [redirectHome, setRedirectHome] = useState(false);

useEffect(() => {
dispatch(fetchUser());
}, [dispatch]);

// go home if back arrow is pressed
if (redirectHome) {
return <Redirect to="/dashboard" push />;
}
const { back } = useHistory();

// function to delete the specified draft
const deleteDraft = (draftToDelete: Review) => {
Expand All @@ -38,7 +29,7 @@ export default function DraftsRedux(): JSX.Element {

const pageHeaderProps = {
title: 'Your Drafts',
onBack: () => setRedirectHome(true),
onBack: () => back(),
};

return (
Expand Down
9 changes: 3 additions & 6 deletions client/src/components/Home/Home-container.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import React from 'react';
import HomeView, { HomeViewProps } from './Home-view';
import HomeView from './Home-view';

// These are the same, for now. If they ever change, replace this alias.
type HomeContainerProps = HomeViewProps;

export default function HomeContainer(props: HomeContainerProps): JSX.Element {
return <HomeView {...props} />;
export default function HomeContainer(): JSX.Element {
return <HomeView />;
}
19 changes: 4 additions & 15 deletions client/src/components/Home/Home-redux.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,8 @@
import React, { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { fetchUser } from '../../actions';
import { RootState } from '../../reducers';
import { User } from '../../types';
import React from 'react';
import { useProtected } from '../../hooks/useProtected';
import HomeContainer from './Home-container';

export default function HomeRedux(): JSX.Element {
const dispatch = useDispatch();
const user: User = useSelector((state: RootState) => state.auth);

// by passing [dispatch] as the second argument of useEffect, we replicate the behavior
// of componentDidMount + componentDidUnmount, but not componentDidUpdate
useEffect(() => {
dispatch(fetchUser());
}, [dispatch]);

return <HomeContainer user={user} />;
useProtected();
return <HomeContainer />;
}
11 changes: 1 addition & 10 deletions client/src/components/Home/Home-view.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,12 @@
import React from 'react';
import { Redirect } from 'react-router-dom';
import { Row, Col } from 'antd';
import PaperSearchBar from '../PaperSearchBar';
import ReadingList from '../ReadingList';
import ReviewReader from '../ReviewReader';

import './Home.scss';
import { User } from '../../types';
import { blankUser } from '../../templates';

export interface HomeViewProps {
user: User;
}

export default function HomeView({ user }: HomeViewProps): JSX.Element {
if (user === blankUser) return <Redirect to="/" push />;

export default function HomeView(): JSX.Element {
return (
<div className="width80">
<Row>
Expand Down
7 changes: 0 additions & 7 deletions client/src/components/Home/Home.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,6 @@ import { RootState } from '../../reducers';
const renderHome = (initialState?: RootState) => renderWithRouterRedux(<Home />, { initialState });

describe('<Home />', () => {
describe('when nobody is logged in', () => {
it('redirects when no user is present', () => {
renderWithRouterRedux(<Home />, { redirectTo: '/' });
expect(screen.getByText(/Redirected to a new page./)).toBeDefined();
});
});

describe('with a user logged in', () => {
const initialState: RootState = { ...getBlankInitialState(), auth: { ...blankUser, displayName: 'Jim Henderson' } };

Expand Down
6 changes: 3 additions & 3 deletions client/src/components/Login/Login.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
/* eslint-disable react/no-unescaped-entities */
import React, { useEffect } from 'react';
import React from 'react';
import { useSelector } from 'react-redux';
import { Card, Col, notification, Row } from 'antd';
import { CalendarOutlined, SmileOutlined, TeamOutlined } from '@ant-design/icons';
import { Card, Col, Row } from 'antd';
import { CalendarOutlined, TeamOutlined } from '@ant-design/icons';
import { Redirect } from 'react-router-dom';
import LazyHero from 'react-lazy-hero';
import { Location } from 'history';
Expand Down
1 change: 1 addition & 0 deletions client/src/hooks/useProtected/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './useProtected';
40 changes: 40 additions & 0 deletions client/src/hooks/useProtected/useProtected.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React from 'react';
import { useProtected } from '.';
import { blankUser } from '../../templates';
import { getBlankInitialState, renderWithRouterRedux } from '../../testUtils/reduxRender';

const mockHistoryPush = jest.fn();

jest.mock('react-router-dom', () => ({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
...(jest.requireActual('react-router-dom') as any),
useHistory: () => ({
push: mockHistoryPush,
}),
}));

// Because you can't run a hook outside of a react component, we'll make a small test case:
function TestComponent({ redirect }: { redirect?: string }): JSX.Element {
useProtected({ redirectTo: redirect });
return <h1>Hi</h1>;
}

describe('useProtected', () => {
describe('redirect behavior', () => {
it('redirects to the default path if a user is present', () => {
renderWithRouterRedux(<TestComponent />);
expect(mockHistoryPush).toHaveBeenCalledWith('/');
});

it('redirects to a custom path if one is specific', () => {
renderWithRouterRedux(<TestComponent redirect="customPath" />);
expect(mockHistoryPush).toHaveBeenCalledWith('customPath');
});

it('does not redirect if no user is present', () => {
const initialState = { ...getBlankInitialState(), auth: { ...blankUser, displayName: 'John' } };
renderWithRouterRedux(<TestComponent />, { initialState });
expect(mockHistoryPush).not.toHaveBeenCalled();
});
});
});
35 changes: 35 additions & 0 deletions client/src/hooks/useProtected/useProtected.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// This hook will check if the user is logged in.
// If yes, do nothing.
// If not, this will redirect the user to login.

import { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useHistory } from 'react-router-dom';
import { fetchUser } from '../../actions';
import { RootState } from '../../reducers';
import { initialState as initialBlankUser } from '../../reducers/reducer_auth';

import { User } from '../../types';

interface UseProtectedOptions {
redirectTo?: string;
}

const DEFAULT_REDIRECT_PATH = '/';

export function useProtected(options?: UseProtectedOptions): void {
const { push } = useHistory();
const dispatch = useDispatch();
const auth: User = useSelector((state: RootState) => state.auth);

// by passing [dispatch] as the second argument of useEffect, we replicate the behavior
// of componentDidMount + componentDidUnmount, but not componentDidUpdate
useEffect(() => {
dispatch(fetchUser());
}, [dispatch]);

// If this doesn't exist or is equivalent to an empty user, redirect
if (!auth || auth === initialBlankUser) {
push(options?.redirectTo || DEFAULT_REDIRECT_PATH);
}
}
2 changes: 1 addition & 1 deletion client/src/reducers/reducer_auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { FetchUserAction } from '../actions/types';
import { blankUser } from '../templates';
import { User } from '../types';

const initialState: User = blankUser;
export const initialState: User = blankUser;

const reducer: Reducer<User, FetchUserAction> = (state = initialState, action) => {
switch (action.type) {
Expand Down

0 comments on commit 5efa8ca

Please sign in to comment.