From 607db76becf4029041186d5cb621e22f4624702a Mon Sep 17 00:00:00 2001 From: Richard Xia Date: Mon, 9 Jan 2023 19:54:27 -0800 Subject: [PATCH] Mmigrate APIHooks and DataService to TypeScript (#1214) * Migrate DataService to TypeScript. This takes a shortcut by using `any` in many of the type signatures. There's not much we can really do about this, since most of these functions are generic and either call `JSON.stringify()` on an arguments or `resp.json()` on responses. For better type safety, we probably want the wrapper functions that call these ones to have stricter type annotations. * Migrate APIHooks to TypeScript. These use type assertions on the API responses, since we do not do any runtime validation of data coming from the API. This also fixes a bug in the ServiceDiscoveryResults type signatures, where it was expecting a type of CategoryRefinement, but in reality we only had the data from a Category (no Algolia-specific data in it). --- .../listing/feedback/FeedbackForm.tsx | 4 ++-- app/hooks/{APIHooks.js => APIHooks.ts} | 13 +++++----- .../ServiceDiscoveryResults.tsx | 3 ++- app/utils/{DataService.js => DataService.ts} | 24 ++++++++++++------- package-lock.json | 16 ++++++------- package.json | 1 + 6 files changed, 36 insertions(+), 25 deletions(-) rename app/hooks/{APIHooks.js => APIHooks.ts} (62%) rename app/utils/{DataService.js => DataService.ts} (72%) diff --git a/app/components/listing/feedback/FeedbackForm.tsx b/app/components/listing/feedback/FeedbackForm.tsx index fbd7fbd27..7e3325710 100644 --- a/app/components/listing/feedback/FeedbackForm.tsx +++ b/app/components/listing/feedback/FeedbackForm.tsx @@ -68,8 +68,8 @@ export const FeedbackForm = ({ service, resource, closeModal }: { }; const [source, sourceId] = !service - ? ['resources', resource.id] - : ['services', service.id]; + ? ['resources', resource.id] as const + : ['services', service.id] as const; addFeedback(source, sourceId, feedback) .then(() => { diff --git a/app/hooks/APIHooks.js b/app/hooks/APIHooks.ts similarity index 62% rename from app/hooks/APIHooks.js rename to app/hooks/APIHooks.ts index 169f81a44..ee00c02a4 100644 --- a/app/hooks/APIHooks.js +++ b/app/hooks/APIHooks.ts @@ -3,6 +3,7 @@ import { useState, useEffect } from 'react'; import * as dataService from '../utils/DataService'; +import type { Category, Eligibility } from '../models/Meta'; // TODO: Handle failure? @@ -10,13 +11,13 @@ import * as dataService from '../utils/DataService'; * * Returns the list of eligibilities. Returns null until the request succeeds. */ -export const useEligibilitiesForCategory = categoryID => { - const [eligibilities, setEligibilities] = useState(null); +export const useEligibilitiesForCategory = (categoryID: string | null): Eligibility[] | null => { + const [eligibilities, setEligibilities] = useState(null); useEffect(() => { if (categoryID !== null) { dataService.get(`/api/eligibilities?category_id=${categoryID}`).then(response => { - setEligibilities(response.eligibilities); + setEligibilities(response.eligibilities as Eligibility[]); }); } }, [categoryID]); @@ -28,13 +29,13 @@ export const useEligibilitiesForCategory = categoryID => { * * Returns the list of categories. Returns null until the request succeeds. */ -export const useSubcategoriesForCategory = categoryID => { - const [subcategories, setSubcategories] = useState(null); +export const useSubcategoriesForCategory = (categoryID: string | null) : Category[] | null => { + const [subcategories, setSubcategories] = useState(null); useEffect(() => { if (categoryID !== null) { dataService.get(`/api/categories/subcategories?id=${categoryID}`).then(response => { - setSubcategories(response.categories); + setSubcategories(response.categories as Category[]); }); } }, [categoryID]); diff --git a/app/pages/ServiceDiscoveryResults/ServiceDiscoveryResults.tsx b/app/pages/ServiceDiscoveryResults/ServiceDiscoveryResults.tsx index c0d047754..96803297c 100644 --- a/app/pages/ServiceDiscoveryResults/ServiceDiscoveryResults.tsx +++ b/app/pages/ServiceDiscoveryResults/ServiceDiscoveryResults.tsx @@ -12,6 +12,7 @@ import { Loader } from 'components/ui'; import SearchResults from 'components/search/SearchResults/SearchResults'; import Sidebar from 'components/search/Sidebar/Sidebar'; import { Header } from 'components/search/Header/Header'; +import { Category } from 'models/Meta'; import { useEligibilitiesForCategory, useSubcategoriesForCategory } from '../../hooks/APIHooks'; import config from '../../config'; @@ -104,7 +105,7 @@ const InnerServiceDiscoveryResults = ({ onSearchStateChange, searchRadius, setSearchRadius, expandList, setExpandList, userLatLng, }: { eligibilities: object[]; - subcategories: ServiceCategory[]; + subcategories: Category[]; categoryName: string; categorySlug: string; algoliaCategoryName: string; diff --git a/app/utils/DataService.js b/app/utils/DataService.ts similarity index 72% rename from app/utils/DataService.js rename to app/utils/DataService.ts index c68465823..1fcfd82a1 100644 --- a/app/utils/DataService.js +++ b/app/utils/DataService.ts @@ -1,6 +1,8 @@ -import * as _ from 'lodash/fp/object'; +import * as _ from 'lodash'; -function setAuthHeaders(resp) { +import type { VoteType } from '../components/listing/feedback/constants'; + +function setAuthHeaders(resp: Response): void { const { headers } = resp; if (headers.get('access-token') && headers.get('client')) { // console.log('we would set new auth headers except for an API bug giving us invalid tokens', @@ -16,7 +18,7 @@ function setAuthHeaders(resp) { } } -export function post(url, body, headers) { +export function post(url: RequestInfo | URL, body: any, headers?: HeadersInit): Promise { let queryHeaders = { Accept: 'application/json', 'Content-Type': 'application/json', @@ -36,7 +38,7 @@ export function post(url, body, headers) { }); } -export function get(url, headers) { +export function get(url: RequestInfo | URL, headers?: HeadersInit): Promise { let queryHeaders = { 'Content-Type': 'application/json', }; @@ -55,7 +57,7 @@ export function get(url, headers) { }); } -export function put(url, body, headers) { +export function put(url: RequestInfo | URL, body: any, headers: HeadersInit): Promise { let queryHeaders = { Accept: 'application/json', 'Content-Type': 'application/json', @@ -75,7 +77,7 @@ export function put(url, body, headers) { }); } -export function APIDelete(url, headers) { +export function APIDelete(url: RequestInfo | URL, headers: HeadersInit): Promise { let queryHeaders = { 'Content-Type': 'application/json', }; @@ -92,8 +94,14 @@ export function APIDelete(url, headers) { }); } -export const addFeedback = (source, sourceId, body) => ( +interface FeedbackBody { + rating: VoteType; + tags: string[]; + review: string; +} + +export const addFeedback = (source: 'resources' | 'services', sourceId: number, body: FeedbackBody): Promise => ( post(`/api/${source}/${sourceId}/feedbacks`, body).then(res => res.json()) ); -export const getResourceCount = () => get('/api/resources/count'); +export const getResourceCount = (): Promise => get('/api/resources/count'); diff --git a/package-lock.json b/package-lock.json index fa8d5b31e..b8d3e415e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -51,6 +51,7 @@ "@types/chai": "^4.2.22", "@types/enzyme": "^3.10.9", "@types/google-map-react": "^2.1.3", + "@types/lodash": "^4.14.191", "@types/mocha": "^9.0.0", "@types/qs": "^6.9.6", "@types/react": "^17.0.3", @@ -4825,11 +4826,10 @@ } }, "node_modules/@types/lodash": { - "version": "4.14.182", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.182.tgz", - "integrity": "sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q==", - "dev": true, - "license": "MIT" + "version": "4.14.191", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.191.tgz", + "integrity": "sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==", + "dev": true }, "node_modules/@types/minimatch": { "version": "3.0.5", @@ -33149,9 +33149,9 @@ } }, "@types/lodash": { - "version": "4.14.182", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.182.tgz", - "integrity": "sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q==", + "version": "4.14.191", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.191.tgz", + "integrity": "sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==", "dev": true }, "@types/minimatch": { diff --git a/package.json b/package.json index 0de3e1e09..41e0a70b6 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "@types/chai": "^4.2.22", "@types/enzyme": "^3.10.9", "@types/google-map-react": "^2.1.3", + "@types/lodash": "^4.14.191", "@types/mocha": "^9.0.0", "@types/qs": "^6.9.6", "@types/react": "^17.0.3",