Use useHistory and useLocation hooks

This commit is contained in:
Przemek Wiech
2021-11-04 21:09:32 +01:00
parent 6e8b6c7b9e
commit 536d9d4210
5 changed files with 36 additions and 46 deletions

View File

@@ -11,10 +11,11 @@ import {IndiInfo} from 'topola';
import {Intro} from './intro'; import {Intro} from './intro';
import {Loader, Message, Portal, Tab} from 'semantic-ui-react'; import {Loader, Message, Portal, Tab} from 'semantic-ui-react';
import {Media} from './util/media'; import {Media} from './util/media';
import {Redirect, Route, RouteComponentProps, Switch} from 'react-router-dom'; import {Redirect, Route, Switch} from 'react-router-dom';
import {TopBar} from './menu/top_bar'; import {TopBar} from './menu/top_bar';
import {TopolaData} from './util/gedcom_util'; import {TopolaData} from './util/gedcom_util';
import {useEffect, useState} from 'react'; import {useEffect, useState} from 'react';
import {useHistory, useLocation} from 'react-router';
import { import {
Chart, Chart,
ChartType, ChartType,
@@ -104,7 +105,7 @@ interface Arguments {
selection?: IndiInfo; selection?: IndiInfo;
chartType: ChartType; chartType: ChartType;
standalone: boolean; standalone: boolean;
freezeAnimation?: boolean; freezeAnimation: boolean;
showSidePanel: boolean; showSidePanel: boolean;
config: Config; config: Config;
} }
@@ -171,20 +172,7 @@ function getArguments(location: H.Location<any>): Arguments {
}; };
} }
/** export function App() {
* Returs true if the changes object has values that are different than those
* in state.
*/
function hasUpdatedValues<T>(state: T, changes: Partial<T> | undefined) {
if (!changes) {
return false;
}
return Object.entries(changes).some(
([key, value]) => value !== undefined && state[key] !== value,
);
}
export function App(props: RouteComponentProps) {
/** State of the application. */ /** State of the application. */
const [state, setState] = useState<AppState>(AppState.INITIAL); const [state, setState] = useState<AppState>(AppState.INITIAL);
/** Loaded data. */ /** Loaded data. */
@@ -208,6 +196,8 @@ export function App(props: RouteComponentProps) {
const [config, setConfig] = useState(DEFALUT_CONFIG); const [config, setConfig] = useState(DEFALUT_CONFIG);
const intl = useIntl(); const intl = useIntl();
const history = useHistory();
const location = useLocation();
/** Sets the state with a new individual selection and chart type. */ /** Sets the state with a new individual selection and chart type. */
function updateDisplay(newSelection: IndiInfo) { function updateDisplay(newSelection: IndiInfo) {
@@ -296,17 +286,17 @@ export function App(props: RouteComponentProps) {
useEffect(() => { useEffect(() => {
(async () => { (async () => {
if (props.location.pathname !== '/view') { if (location.pathname !== '/view') {
if (state !== AppState.INITIAL) { if (state !== AppState.INITIAL) {
setState(AppState.INITIAL); setState(AppState.INITIAL);
} }
return; return;
} }
const args = getArguments(props.location); const args = getArguments(location);
if (!args.sourceSpec) { if (!args.sourceSpec) {
props.history.replace({pathname: '/'}); history.replace({pathname: '/'});
return; return;
} }
@@ -321,6 +311,7 @@ export function App(props: RouteComponentProps) {
setSelection(args.selection); setSelection(args.selection);
setStandalone(args.standalone); setStandalone(args.standalone);
setChartType(args.chartType); setChartType(args.chartType);
setFreezeAnimation(args.freezeAnimation);
setConfig(args.config); setConfig(args.config);
try { try {
const data = await loadData(args.sourceSpec, args.selection); const data = await loadData(args.sourceSpec, args.selection);
@@ -371,13 +362,12 @@ export function App(props: RouteComponentProps) {
}); });
function updateUrl(args: queryString.ParsedQuery<any>) { function updateUrl(args: queryString.ParsedQuery<any>) {
const location = props.location;
const search = queryString.parse(location.search); const search = queryString.parse(location.search);
for (const key in args) { for (const key in args) {
search[key] = args[key]; search[key] = args[key];
} }
location.search = queryString.stringify(search); location.search = queryString.stringify(search);
props.history.push(location); history.push(location);
} }
/** /**
@@ -516,15 +506,14 @@ export function App(props: RouteComponentProps) {
return ( return (
<> <>
<Route <Route
render={(props: RouteComponentProps) => ( render={() => (
<TopBar <TopBar
{...props}
data={data?.chartData} data={data?.chartData}
allowAllRelativesChart={ allowAllRelativesChart={
sourceSpec?.source !== DataSourceEnum.WIKITREE sourceSpec?.source !== DataSourceEnum.WIKITREE
} }
showingChart={ showingChart={
props.history.location.pathname === '/view' && history.location.pathname === '/view' &&
(state === AppState.SHOWING_CHART || (state === AppState.SHOWING_CHART ||
state === AppState.LOADING_MORE) state === AppState.LOADING_MORE)
} }

View File

@@ -5,10 +5,10 @@ import {IndiInfo, JsonGedcomData} from 'topola';
import {Link} from 'react-router-dom'; import {Link} from 'react-router-dom';
import {Media} from '../util/media'; import {Media} from '../util/media';
import {MenuType} from './menu_item'; import {MenuType} from './menu_item';
import {RouteComponentProps} from 'react-router-dom';
import {SearchBar} from './search'; import {SearchBar} from './search';
import {UploadMenu} from './upload_menu'; import {UploadMenu} from './upload_menu';
import {UrlMenu} from './url_menu'; import {UrlMenu} from './url_menu';
import {useHistory, useLocation} from 'react-router';
import {WikiTreeLoginMenu, WikiTreeMenu} from './wikitree_menu'; import {WikiTreeLoginMenu, WikiTreeMenu} from './wikitree_menu';
enum ScreenSize { enum ScreenSize {
@@ -37,14 +37,16 @@ interface Props {
showWikiTreeMenus: boolean; showWikiTreeMenus: boolean;
} }
export function TopBar(props: RouteComponentProps & Props) { export function TopBar(props: Props) {
const history = useHistory();
const location = useLocation();
function changeView(view: string) { function changeView(view: string) {
const location = props.location;
const search = queryString.parse(location.search); const search = queryString.parse(location.search);
if (search.view !== view) { if (search.view !== view) {
search.view = view; search.view = view;
location.search = queryString.stringify(search); location.search = queryString.stringify(search);
props.history.push(location); history.push(location);
} }
} }

View File

@@ -4,8 +4,8 @@ import {analyticsEvent} from '../util/analytics';
import {Dropdown, Icon, Menu} from 'semantic-ui-react'; import {Dropdown, Icon, Menu} from 'semantic-ui-react';
import {FormattedMessage} from 'react-intl'; import {FormattedMessage} from 'react-intl';
import {MenuType} from './menu_item'; import {MenuType} from './menu_item';
import {RouteComponentProps} from 'react-router-dom';
import {SyntheticEvent} from 'react'; import {SyntheticEvent} from 'react';
import {useHistory, useLocation} from 'react-router';
function loadFileAsText(file: File): Promise<string> { function loadFileAsText(file: File): Promise<string> {
return new Promise((resolve) => { return new Promise((resolve) => {
@@ -27,7 +27,10 @@ interface Props {
} }
/** Displays and handles the "Open file" menu. */ /** Displays and handles the "Open file" menu. */
export function UploadMenu(props: RouteComponentProps & Props) { export function UploadMenu(props: Props) {
const history = useHistory();
const location = useLocation();
async function handleUpload(event: SyntheticEvent<HTMLInputElement>) { async function handleUpload(event: SyntheticEvent<HTMLInputElement>) {
const files = (event.target as HTMLInputElement).files; const files = (event.target as HTMLInputElement).files;
if (!files || !files.length) { if (!files || !files.length) {
@@ -68,9 +71,8 @@ export function UploadMenu(props: RouteComponentProps & Props) {
// Use history.replace() when reuploading the same file and history.push() when loading // Use history.replace() when reuploading the same file and history.push() when loading
// a new file. // a new file.
const search = queryString.parse(props.location.search); const search = queryString.parse(location.search);
const historyPush = const historyPush = search.file === hash ? history.replace : history.push;
search.file === hash ? props.history.replace : props.history.push;
historyPush({ historyPush({
pathname: '/view', pathname: '/view',

View File

@@ -3,18 +3,19 @@ import {analyticsEvent} from '../util/analytics';
import {Button, Form, Header, Icon, Input, Modal} from 'semantic-ui-react'; import {Button, Form, Header, Icon, Input, Modal} from 'semantic-ui-react';
import {FormattedMessage} from 'react-intl'; import {FormattedMessage} from 'react-intl';
import {MenuItem, MenuType} from './menu_item'; import {MenuItem, MenuType} from './menu_item';
import {RouteComponentProps} from 'react-router-dom';
import {useEffect, useRef, useState} from 'react'; import {useEffect, useRef, useState} from 'react';
import {useHistory} from 'react-router';
interface Props { interface Props {
menuType: MenuType; menuType: MenuType;
} }
/** Displays and handles the "Open URL" menu. */ /** Displays and handles the "Open URL" menu. */
export function UrlMenu(props: RouteComponentProps & Props) { export function UrlMenu(props: Props) {
const [dialogOpen, setDialogOpen] = useState(false); const [dialogOpen, setDialogOpen] = useState(false);
const [url, setUrl] = useState(''); const [url, setUrl] = useState('');
const inputRef = useRef<Input>(null); const inputRef = useRef<Input>(null);
const history = useHistory();
useEffect(() => { useEffect(() => {
if (dialogOpen) { if (dialogOpen) {
@@ -28,7 +29,7 @@ export function UrlMenu(props: RouteComponentProps & Props) {
setDialogOpen(false); setDialogOpen(false);
if (url) { if (url) {
analyticsEvent('url_selected'); analyticsEvent('url_selected');
props.history.push({ history.push({
pathname: '/view', pathname: '/view',
search: queryString.stringify({url}), search: queryString.stringify({url}),
}); });

View File

@@ -5,24 +5,20 @@ import {Button, Form, Header, Input, Modal} from 'semantic-ui-react';
import {FormattedMessage, useIntl} from 'react-intl'; import {FormattedMessage, useIntl} from 'react-intl';
import {getLoggedInUserName} from '../datasource/wikitree'; import {getLoggedInUserName} from '../datasource/wikitree';
import {MenuItem, MenuType} from './menu_item'; import {MenuItem, MenuType} from './menu_item';
import {RouteComponentProps} from 'react-router-dom';
import {useEffect, useRef, useState} from 'react'; import {useEffect, useRef, useState} from 'react';
import {useHistory, useLocation} from 'react-router';
enum WikiTreeLoginState {
UNKNOWN,
NOT_LOGGED_IN,
LOGGED_IN,
}
interface Props { interface Props {
menuType: MenuType; menuType: MenuType;
} }
/** Displays and handles the "Select WikiTree ID" menu. */ /** Displays and handles the "Select WikiTree ID" menu. */
export function WikiTreeMenu(props: RouteComponentProps & Props) { export function WikiTreeMenu(props: Props) {
const [dialogOpen, setDialogOpen] = useState(false); const [dialogOpen, setDialogOpen] = useState(false);
const [wikiTreeId, setWikiTreeId] = useState(''); const [wikiTreeId, setWikiTreeId] = useState('');
const inputRef = useRef<Input>(null); const inputRef = useRef<Input>(null);
const history = useHistory();
const location = useLocation();
useEffect(() => { useEffect(() => {
if (dialogOpen) { if (dialogOpen) {
@@ -38,10 +34,10 @@ export function WikiTreeMenu(props: RouteComponentProps & Props) {
return; return;
} }
analyticsEvent('wikitree_id_selected'); analyticsEvent('wikitree_id_selected');
const search = queryString.parse(props.location.search); const search = queryString.parse(location.search);
const standalone = const standalone =
search.standalone !== undefined ? search.standalone : true; search.standalone !== undefined ? search.standalone : true;
props.history.push({ history.push({
pathname: '/view', pathname: '/view',
search: queryString.stringify({ search: queryString.stringify({
indi: wikiTreeId, indi: wikiTreeId,