Skip to content

Commit

Permalink
refactor: setup typescript support for useSelector & useDispatch
Browse files Browse the repository at this point in the history
- Create `useTypedSelector` & `useTypedDispatch` hooks for typescript support
- Replace all files with the created hooks
  • Loading branch information
royallsilwallz committed Jan 25, 2025
1 parent 75e6f9d commit 011b3cd
Show file tree
Hide file tree
Showing 92 changed files with 333 additions and 340 deletions.
8 changes: 4 additions & 4 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ import { RouterProvider } from 'react-router-dom';
import { Toaster } from 'react-hot-toast';
import ReactPlaceholder from 'react-placeholder';
import { useMeta } from 'react-meta-elements';
import { useSelector } from 'react-redux';
import { useTypedSelector } from '@Store/hooks';
import { ErrorBoundary } from '@sentry/react';
import { QueryClientProvider, QueryClient } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';

import './assets/styles/index.scss';

import { getUserDetails } from './store/actions/auth';
import { RootStore, store } from './store';
import { store } from './store';
import { ORG_NAME, MATOMO_ID } from './config';
import { Preloader } from './components/preloader';
import { FallbackComponent } from './views/fallback';
Expand Down Expand Up @@ -40,8 +40,8 @@ const queryClient = new QueryClient({
const App = () => {
useMeta({ property: 'og:url', content: import.meta.env.REACT_APP_BASE_URL });
useMeta({ name: 'author', content: ORG_NAME });
const isLoading = useSelector((state: RootStore) => state.loader.isLoading);
const locale = useSelector((state: RootStore) => state.preferences.locale);
const isLoading = useTypedSelector((state) => state.loader.isLoading);
const locale = useTypedSelector((state) => state.preferences.locale);

useEffect(() => {
// fetch user details endpoint when the user is returning to a logged in session
Expand Down
7 changes: 3 additions & 4 deletions frontend/src/api/notifications.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { useSelector } from 'react-redux';
import { useTypedSelector } from '@Store/hooks';
import { useQuery } from '@tanstack/react-query';

import { backendToQueryConversion } from '../hooks/UseInboxQueryAPI';
import { remapParamsToAPI } from '../utils/remapParamsToAPI';
import api from './apiClient';
import { RootStore } from '../store';

export const useNotificationsQuery = (inboxQuery: string) => {
const token = useSelector((state: RootStore) => state.auth.token);
const token = useTypedSelector((state) => state.auth.token);
const fetchNotifications = async (signal: AbortSignal, queryKey: string) => {
const [, inboxQuery] = queryKey;
const response = await api(token).get(`notifications/?${serializeParams(inboxQuery)}`, {
Expand All @@ -25,7 +24,7 @@ export const useNotificationsQuery = (inboxQuery: string) => {
};

export const useUnreadNotificationsCountQuery = () => {
const token = useSelector((state: RootStore) => state.auth.token);
const token = useTypedSelector((state) => state.auth.token);
const fetchUnreadNotificationCount = async (signal: AbortSignal) => {
const response = await api(token).get('notifications/queries/own/count-unread/', {
signal,
Expand Down
5 changes: 2 additions & 3 deletions frontend/src/api/organisations.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { useQuery } from '@tanstack/react-query';
import { useSelector } from 'react-redux';
import { useTypedSelector } from '@Store/hooks';

import api from './apiClient';
import { RootStore } from '../store';

export const useUserOrganisationsQuery = (userId: string | number) => {
const token = useSelector((state: RootStore) => state.auth.token);
const token = useTypedSelector((state) => state.auth.token);

const fetchOrganisations = ({ signal }: {
signal: AbortSignal;
Expand Down
19 changes: 9 additions & 10 deletions frontend/src/api/projects.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
import axios from 'axios';
import { subMonths, format } from 'date-fns';
import { QueryKey, QueryOptions, useQuery, UseQueryOptions } from '@tanstack/react-query';
import { useSelector } from 'react-redux';
import { useTypedSelector } from '@Store/hooks';

import { remapParamsToAPI } from '../utils/remapParamsToAPI';
import api from './apiClient';
import { API_URL, UNDERPASS_URL } from '../config';
import { RootStore } from '../store';
import { UNDERPASS_URL } from '../config';

export const useProjectsQuery = (
fullProjectsQuery: string,
action: string,
queryOptions: QueryOptions,
) => {
const token = useSelector((state: RootStore) => state.auth.token);
const locale = useSelector((state: RootStore) => state.preferences['locale']);
const token = useTypedSelector((state) => state.auth.token);
const locale = useTypedSelector((state) => state.preferences['locale']);

const fetchProjects = async (signal: AbortSignal | undefined, queryKey: QueryKey) => {
const [, fullProjectsQuery, action] = queryKey;
Expand Down Expand Up @@ -45,8 +44,8 @@ export const useProjectsQuery = (
};

export const useProjectQuery = (projectId: string, otherOptions: any) => {
const token = useSelector((state: RootStore) => state.auth.token);
const locale = useSelector((state: RootStore) => state.preferences['locale']);
const token = useTypedSelector((state) => state.auth.token);
const locale = useTypedSelector((state) => state.preferences['locale']);
const fetchProject = ({ signal }: { signal: AbortSignal }) => {
return api(token, locale).get(`projects/${projectId}/`, {
signal,
Expand All @@ -63,8 +62,8 @@ export const useProjectQuery = (projectId: string, otherOptions: any) => {
type ProjectSummaryQueryOptions = Omit<UseQueryOptions<any>, ('queryKey' | 'queryFn' | 'select')>;

export const useProjectSummaryQuery = (projectId: string, otherOptions?: ProjectSummaryQueryOptions) => {
const token = useSelector((state: RootStore) => state.auth.token);
const locale = useSelector((state: RootStore) => state.preferences['locale']);
const token = useTypedSelector((state) => state.auth.token);
const locale = useTypedSelector((state) => state.preferences['locale']);

const fetchProjectSummary = ({ signal }: { signal: AbortSignal }) => {
return api(token, locale).get(`projects/${projectId}/queries/summary/`, {
Expand Down Expand Up @@ -158,7 +157,7 @@ export const useProjectTimelineQuery = (projectId: string) => {
};

export const useTaskDetail = (projectId: string, taskId: number, shouldRefetch: boolean) => {
const token = useSelector((state: RootStore) => state.auth.token);
const token = useTypedSelector((state) => state.auth.token);

const fetchTaskDetail = ({ signal }: { signal: AbortSignal }) => {
return api(token).get(`projects/${projectId}/tasks/${taskId}/`, {
Expand Down
7 changes: 3 additions & 4 deletions frontend/src/api/questionsAndComments.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { useQuery } from '@tanstack/react-query';
import { useSelector } from 'react-redux';
import { useTypedSelector } from '@Store/hooks';

import api from './apiClient';
import { RootStore } from '../store';

export const useCommentsQuery = (projectId: string, page: number) => {
const token = useSelector((state: RootStore) => state.auth.token);
const locale = useSelector((state: RootStore) => state.preferences['locale']);
const token = useTypedSelector((state) => state.auth.token);
const locale = useTypedSelector((state) => state.preferences['locale']);

const getComments = ({ signal }: {
signal: AbortSignal;
Expand Down
5 changes: 2 additions & 3 deletions frontend/src/api/teams.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { useSelector } from 'react-redux';
import { useTypedSelector } from '@Store/hooks';
import { useQuery } from '@tanstack/react-query';
import api from './apiClient';
import type { RootStore } from '../store';

export const useTeamsQuery = (params: any, otherOptions: any) => {
const token = useSelector((state: RootStore) => state.auth.token);
const token = useTypedSelector((state) => state.auth.token);

const fetchUserTeams = ({ signal }: {
signal: AbortSignal;
Expand Down
7 changes: 3 additions & 4 deletions frontend/src/api/user.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { useSelector } from 'react-redux';
import { useTypedSelector } from '@Store/hooks';
import { useQuery } from '@tanstack/react-query';

import api from './apiClient';
import { RootStore } from '../store';

export const useLockedTasksQuery = () => {
const token = useSelector((state: RootStore) => state.auth.token);
const locale = useSelector((state: RootStore) => state.preferences['locale']);
const token = useTypedSelector((state) => state.auth.token);
const locale = useTypedSelector((state) => state.preferences['locale']);

const fetchLockedTasks = ({ signal }: {
signal: AbortSignal;
Expand Down
5 changes: 2 additions & 3 deletions frontend/src/components/comments/commentInput.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useRef, useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
import { useTypedSelector } from '@Store/hooks';
import MDEditor from '@uiw/react-md-editor';
import Tribute from 'tributejs';
import { FormattedMessage, useIntl } from 'react-intl';
Expand All @@ -17,7 +17,6 @@ import { htmlFromMarkdown, formatUserNamesToLink } from '../../utils/htmlFromMar
import { iconConfig } from './editorIconConfig';
import messages from './messages';
import { CurrentUserAvatar } from '../user/avatar';
import { RootStore } from '../../store';

function CommentInputField({
comment,
Expand All @@ -42,7 +41,7 @@ function CommentInputField({
placeholderMsg?: any;
markdownTextareaProps?: any;
}) {
const token = useSelector((state: RootStore) => state.auth.token);
const token = useTypedSelector((state) => state.auth.token);
const textareaRef = useRef();
const isBundle = useRef(false);
const [isShowPreview, setIsShowPreview] = useState(false);
Expand Down
5 changes: 2 additions & 3 deletions frontend/src/components/contributions/myProjectsDropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import { useSelector } from 'react-redux';
import { useTypedSelector } from '@Store/hooks';
import Select from 'react-select';
import { FormattedMessage } from 'react-intl';
import { useFetch } from '../../hooks/UseFetch';
import messages from './messages';
import { RootStore } from '../../store';

export default function MyProjectsDropdown({ className, setQuery, allQueryParams }: {
className?: string;
setQuery: any;
allQueryParams: any;
}) {
const username = useSelector((state: RootStore) => state.auth.userDetails?.username);
const username = useTypedSelector((state) => state.auth.userDetails?.username);
const [, , projects] = useFetch(`projects/queries/${username}/touched/`);

const onSortSelect = (projectId: string) => {
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/deleteModal/index.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { forwardRef, useState, useRef } from 'react';
import { useSelector } from 'react-redux';
import { useTypedSelector } from '@Store/hooks';
import { useNavigate } from 'react-router-dom';
import { FormattedMessage } from 'react-intl';
import Popup from 'reactjs-popup';
Expand All @@ -15,7 +15,7 @@ const DeleteTrigger = forwardRef((props, ref) => <DeleteButton {...props} />);
export function DeleteModal({ id, name, type, className, endpointURL, onDelete }) {
const navigate = useNavigate();
const modalRef = useRef();
const token = useSelector((state) => state.auth.token);
const token = useTypedSelector((state) => state.auth.token);
const [deleteStatus, setDeleteStatus] = useState(null);
const [error, setErrorMessage] = useState(null);

Expand Down
10 changes: 5 additions & 5 deletions frontend/src/components/editor.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useEffect, useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { useTypedDispatch, useTypedSelector } from '@Store/hooks';
import { useIntl } from 'react-intl';
import { gpx } from '@tmcw/togeojson';
import * as iD from '@hotosm/id';
Expand All @@ -9,11 +9,11 @@ import { OSM_CLIENT_ID, OSM_CLIENT_SECRET, OSM_REDIRECT_URI, OSM_SERVER_URL } fr
import messages from './messages';

export default function Editor({ setDisable, comment, presets, imagery, gpxUrl }) {
const dispatch = useDispatch();
const dispatch = useTypedDispatch();
const intl = useIntl();
const session = useSelector((state) => state.auth.session);
const iDContext = useSelector((state) => state.editor.context);
const locale = useSelector((state) => state.preferences.locale);
const session = useTypedSelector((state) => state.auth.session);
const iDContext = useTypedSelector((state) => state.editor.context);
const locale = useTypedSelector((state) => state.preferences.locale);
const [customImageryIsSet, setCustomImageryIsSet] = useState(false);
const windowInit = typeof window !== 'undefined';
const customSource =
Expand Down
5 changes: 2 additions & 3 deletions frontend/src/components/footer/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Fragment } from 'react';
import { useSelector } from 'react-redux';
import { useTypedSelector } from '@Store/hooks';
import { Link, matchRoutes, useLocation } from 'react-router-dom';
import { FormattedMessage } from 'react-intl';
import {
Expand All @@ -21,7 +21,6 @@ import {
ORG_PRIVACY_POLICY_URL,
} from '../../config';
import './styles.scss';
import { RootStore } from '../../store';

const socialNetworks = [
{ link: ORG_TWITTER, icon: <TwitterIcon style={{ height: '20px', width: '20px' }} noBg /> },
Expand All @@ -33,7 +32,7 @@ const socialNetworks = [

export function Footer() {
const location = useLocation();
const userDetails = useSelector((state: RootStore) => state.auth.userDetails);
const userDetails = useTypedSelector((state) => state.auth.userDetails);

const footerDisabledPaths = [
'projects/:id/tasks',
Expand Down
8 changes: 4 additions & 4 deletions frontend/src/components/formInputs.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useEffect, useState, useRef } from 'react';
import { useSelector } from 'react-redux';
import { useTypedSelector } from '@Store/hooks';
import { Field } from 'react-final-form';
import Select from 'react-select';
import { FormattedMessage } from 'react-intl';
Expand Down Expand Up @@ -53,8 +53,8 @@ export const SwitchToggle = ({
);

export const OrganisationSelect = ({ className, orgId, onChange }) => {
const userDetails = useSelector((state) => state.auth.userDetails);
const token = useSelector((state) => state.auth.token);
const userDetails = useTypedSelector((state) => state.auth.userDetails);
const token = useTypedSelector((state) => state.auth.token);
const [organisations, setOrganisations] = useState([]);

useEffect(() => {
Expand Down Expand Up @@ -100,7 +100,7 @@ export function OrganisationSelectInput({ className }) {
}

export function UserCountrySelect({ className, isDisabled = false }) {
const locale = useSelector((state) => state.preferences.locale);
const locale = useTypedSelector((state) => state.preferences.locale);
const [options, setOptions] = useState([]);

useEffect(() => {
Expand Down
13 changes: 6 additions & 7 deletions frontend/src/components/header/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { Fragment, useRef } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { useTypedDispatch, useTypedSelector } from '@Store/hooks';
import { Link, useNavigate, useLocation } from 'react-router-dom';
import Popup from 'reactjs-popup';
import { FormattedMessage } from 'react-intl';
Expand All @@ -24,17 +24,16 @@ import { useDebouncedCallback } from '../../hooks/UseThrottle';
import { HorizontalScroll } from '../horizontalScroll';

import './styles.scss';
import { RootStore } from '../../store/index';

export const Header = () => {
const dispatch = useDispatch();
const dispatch = useTypedDispatch();
const location = useLocation();
const navigate = useNavigate();
const menuItemsContainerRef = useRef(null);

const userDetails = useSelector((state: RootStore) => state.auth.userDetails);
const organisations = useSelector((state: RootStore) => state.auth.organisations);
const showOrgBar = useSelector((state: RootStore) => state.orgBarVisibility.isVisible);
const userDetails = useTypedSelector((state) => state.auth.userDetails);
const organisations = useTypedSelector((state) => state.auth.organisations);
const showOrgBar = useTypedSelector((state) => state.orgBarVisibility.isVisible);

const linkCombo = 'link mh3 barlow-condensed blue-dark f4 ttu lh-solid nowrap pv2';

Expand Down Expand Up @@ -294,7 +293,7 @@ export const ActionItems = ({
);

export const PopupItems = (props: any) => {
const dispatch = useDispatch();
const dispatch = useTypedDispatch();

return (
<div className="v-mid tc">
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/components/header/notificationBell.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useState, useEffect, useRef, useCallback } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { useTypedDispatch, useTypedSelector } from '@Store/hooks';

import { TopNavLink } from './NavLink';
import { BellIcon } from '../svgIcons';
Expand All @@ -10,8 +10,8 @@ import { useOnResize } from '../../hooks/UseOnResize';
import { useNotificationsQuery, useUnreadNotificationsCountQuery } from '../../api/notifications';

export const NotificationBell = () => {
const token = useSelector((state) => state.auth.token);
const dispatch = useDispatch();
const token = useTypedSelector((state) => state.auth.token);
const dispatch = useTypedDispatch();

const [bellPosition, setBellPosition] = useState(0);
/* these below make the references stable so hooks doesn't re-request forever */
Expand Down
9 changes: 4 additions & 5 deletions frontend/src/components/header/updateEmail.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { useTypedDispatch, useTypedSelector } from '@Store/hooks';
import { func } from 'prop-types';
import { FormattedMessage } from 'react-intl';

Expand All @@ -8,15 +8,14 @@ import { updateUserEmail } from '../../store/actions/auth';
import { PROFILE_RELEVANT_FIELDS } from '../user/forms/personalInformation';
import { ORG_PRIVACY_POLICY_URL } from '../../config';
import { Button } from '../button';
import { RootStore } from '../../store/index.js';

export const UpdateEmail = ({ closeModal }: {
closeModal: () => void;
}) => {
const dispatch = useDispatch();
const dispatch = useTypedDispatch();

const userDetails = useSelector((state: RootStore) => state.auth.userDetails);
const token = useSelector((state: RootStore) => state.auth.token);
const userDetails = useTypedSelector((state) => state.auth.userDetails);
const token = useTypedSelector((state) => state.auth.token);
const [userState, setUserState] = useState({ email: '', success: false, details: '' });

const onChange = (e) => {
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/homepage/jumbotron.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useLayoutEffect, useState } from 'react';
import { useSelector } from 'react-redux';
import { useTypedSelector } from '@Store/hooks';
import { Link } from 'react-router-dom';
import Popup from 'reactjs-popup';
import { FormattedMessage } from 'react-intl';
Expand All @@ -12,7 +12,7 @@ import bannerLR from '../../assets/img/banner_824.jpg';
import { HOMEPAGE_VIDEO_URL, HOMEPAGE_IMG_HIGH, HOMEPAGE_IMG_LOW } from '../../config';

function JumbotronButtons() {
const token = useSelector((state) => state.auth.token);
const token = useTypedSelector((state) => state.auth.token);
return (
<div className="buttons">
<Link to={'explore'}>
Expand Down
Loading

0 comments on commit 011b3cd

Please sign in to comment.