From 8caf1e7c430b4c0ea4236bcfcd5984a87e88659d Mon Sep 17 00:00:00 2001 From: Leon Talbert Date: Mon, 28 Oct 2024 12:32:10 +0800 Subject: [PATCH] Resultify --- .../@molecules/SearchInput/SearchInput.tsx | 4 +- src/pages/legacyfavourites.tsx | 68 +++++++++++-------- src/utils/utils.ts | 30 +++++++- 3 files changed, 71 insertions(+), 31 deletions(-) diff --git a/src/components/@molecules/SearchInput/SearchInput.tsx b/src/components/@molecules/SearchInput/SearchInput.tsx index 770dd55d1..2377cc0a1 100644 --- a/src/components/@molecules/SearchInput/SearchInput.tsx +++ b/src/components/@molecules/SearchInput/SearchInput.tsx @@ -728,7 +728,7 @@ export const SearchInput = ({ size = 'extraLarge' }: { size?: 'medium' | 'extraL $state={state} data-testid="search-input-results" > - {dropdownItems.map((searchItem, index) => ( + {/* {dropdownItems.map((searchItem, index) => ( - ))} + ))} */} ) diff --git a/src/pages/legacyfavourites.tsx b/src/pages/legacyfavourites.tsx index 66a6b9a5b..e5963e050 100644 --- a/src/pages/legacyfavourites.tsx +++ b/src/pages/legacyfavourites.tsx @@ -1,7 +1,7 @@ /* eslint-disable max-classes-per-file */ -import { Effect, pipe } from 'effect' import { Dispatch, ReactElement, SetStateAction, useEffect, useState } from 'react' import styled, { css } from 'styled-components' +import { match } from 'ts-pattern' import { useChainId } from 'wagmi' import { truncateFormat } from '@ensdomains/ensjs/utils' @@ -12,8 +12,17 @@ import { Spacer } from '@app/components/@atoms/Spacer' import { Outlink } from '@app/components/Outlink' import { Content } from '@app/layouts/Content' import { ContentGrid } from '@app/layouts/ContentGrid' +import { thread } from '@app/utils/utils' -const { try: EffectTry, flatMap, map, match, runSync, sync } = Effect +type Result = { ok: true; value: T } | { ok: false; error: E | undefined } + +const Ok = (data: T): Result => { + return { ok: true, value: data } +} + +const Err = (error?: E): Result => { + return { ok: false, error } +} const Container = styled.div( ({ theme }) => css` @@ -59,51 +68,54 @@ type LegacyFavorite = { } // eslint-disable-next-line react/no-unused-prop-types -type SimpleFavorite = { name: string; expiry: Date } +type SimpleFavorite = { name: string; expiry: Date | null } class JsonParseError extends SyntaxError {} -const invalidDateCheck = (favorite: SimpleFavorite) => - favorite?.expiry?.toString() === 'Invalid Date' ? console.log('Invalid date') : null - export const getLegacyFavorites = (): string => - globalThis?.localStorage?.getItem('ensFavourites') || '{}' + globalThis?.localStorage?.getItem('ensFavourites') || '[]' -export const simplifyLegacyFavorites = (legacyFavorites: any): SimpleFavorite[] => { +export const simplifyLegacyFavorites = ( + legacyFavoritesResult: LegacyFavorite[], +): SimpleFavorite[] => { + const legacyFavorites = legacyFavoritesResult if (!legacyFavorites?.length) { return [] } - return legacyFavorites.map((favorite: any) => ({ + return legacyFavorites.map((favorite: LegacyFavorite) => ({ name: favorite.name, expiry: favorite.expiryTime ? new Date(favorite.expiryTime) : null, })) } -const jsonParseEffect = (input: string): Effect.Effect => - EffectTry({ - try: () => JSON.parse(input), - catch: (error) => new JsonParseError(error as string), - }) - -const setFavoritesProgram = (setState: Dispatch>) => - pipe( - sync(getLegacyFavorites), - flatMap(jsonParseEffect), - map(simplifyLegacyFavorites), - // Easy to 'interrupt' the computation at any point and do stuff - Effect.tap((simpleLegacyFavorites) => simpleLegacyFavorites.map(invalidDateCheck)), - match({ - onFailure: console.error, - onSuccess: setState, - }), - ) +const jsonParse = (input: string): Result => { + try { + return Ok(JSON.parse(input)) + } catch (error) { + return Err(new JsonParseError(error as string)) + } +} export default function Page() { const [favorites, setFavorites] = useState(null) const chainId = useChainId() useEffect(() => { - runSync(setFavoritesProgram(setFavorites)) + const result: Result = thread( + {}, + getLegacyFavorites, + jsonParse, + simplifyLegacyFavorites, + ) + match(result) + .with({ ok: true }, ({ value }) => { + setFavorites(value) + }) + .with({ ok: false }, ({ error }) => { + console.error(error) + setFavorites([]) + }) + .exhaustive() }, []) return ( diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 022b8d89b..fd31735fc 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -184,6 +184,16 @@ export const createDateAndValue = (value: TValue value, }) +type Result = { ok: true; value: T } | { ok: false; error: E | undefined } + +const Ok = (data: T): Result => { + return { ok: true, value: data } +} + +const Err = (error?: E): Result => { + return { ok: false, error } +} + /* Following types are based on this solution: https://stackoverflow.com/questions/53173203/typescript-recursive-function-composition/53175538#53175538 Best to just move on and not try to understand it. (This is copilot's opintion!) @@ -204,4 +214,22 @@ type LaxReturnType = F extends (...args: any) => infer R ? R : never export const thread = any, ...Array<(arg: any) => any>]>( arg: ArgType, ...f: F & AsChain -): LaxReturnType> => f.reduce((acc, fn) => fn(acc), arg) as LaxReturnType> +): Result>, any> => { + try { + const rslt = f.reduce((acc, fn) => { + if (acc && typeof acc === 'object' && 'ok' in acc) { + if (!acc.ok) return acc + return fn(acc.value) + } + return fn(acc) + }, arg) + + if (rslt && typeof rslt === 'object' && 'ok' in rslt) { + return rslt + } + + return Ok(rslt) + } catch (e) { + return Err(e) + } +}