Is there a way to cast an IO to TaskEither - fp-ts

I was trying to start using fp-ts and I had a really hard time with side-effects.
I wrote a little test program that will read a file, print the file to screen and return an Either<Error, string>.
I used the basic do notation as an example and ended up with a code that looks somthing like that
const program = pipe(
TE.tryCatch(() => readFile(), () => new Error('Failed to read file')),
TE.chainFirst((text) => pipe(log(text), TE.fromIO)),
);
This code compiles, but typescript infers that the program variable gets the type TE.TaskEither<unknown, string> and I was expecting the type TE.TaskEither<Error, string>.
Is there a way to keep the error type inside the TaskEither? Or am I using this librery all wrong?
p.s I am using fp-ts version 2.8.6

You can use chainFirstIOK:
export declare const chainFirstIOK: <A, B>(f: (a: A) => IO<B>) => <E>(first: TaskEither<E, A>) => TaskEither<E, A>
const program = pipe(
TE.tryCatch(() => readFile(), () => new Error('Failed to read file')),
TE.chainFirstIOK((text) => pipe(log(text), TE.fromIO)),
);
Now returns TaskEither<Error, string>.
You can also simplify it even more using flow:
const program = pipe(
TE.tryCatch(() => readFile(), () => new Error('Failed to read file')),
TE.chainFirstIOK(flow(log, TE.fromIO)),
);

Related

Typescript allows to call a function with incorrect parameter's type

There is a function with next signature:
const verify = (address?: string) => void
There is a Component with props type:
type VerifyButtonProps = { onClick: () => void; }
There is a Component with props type:
type TButtonProps = { onClick?: React.MouseEventHandler<HTMLButtonElement>; children: React.ReactNode; };
[Codesanbox example]
(https://codesandbox.io/s/react-ts-playground-forked-v24gs7?file=/src/index.tsx/)
I'm getting the runtime error when click on the button and expect typescript points out to it, but compilation passes without any errors.
How can I prevent runtime error with help of typescript on the compiling step?
Your issue is basically following case (playground):
const verify = (address?: string) => address?.toLowerCase()
const verifyEmpty: () => void = verify
const onClick: (event: object) => void = verifyEmpty
onClick({ this: 'is not a string'})
Typescript allows each of these steps, however combined it produces a runtime error. This unsoundness is known, however Typescript does not guarantee soundness (no runtime errors if if there are no type errors) and this is one case where they decided to leave it unsound.
This means it is up to you to catch such errors. In your case, you could use onClick={() => verify()} to fix the error.
To avoid this situation you can replace
() => void
with
(...args: undefined[]) => void;
With that replacement you'll explicitly tell to your component, that function doesn't allow any number of arguments.
So, you can still pass verify function to your component. But inside of the component you can't pass it down to any function props with optional arguments, e.g. <Button onClick={verify} />
From the index.tsx file, the problem with your code is that your trying to run .toLowerCase() on an event.
Here is your code:
const verify = (address?: string) => { console.log("address = ", address?.toLowerCase());};
const App = (props) => {
return <VerifyButton onClick={verify} />;
};
I suggest you look into handlers but passing your function as you have in the onClick handler means that you get every argument passed to the verify function as address.
Log the address to console and see what I mean.
You may write your change handlers this way:
onClick={(e) => yourFunction(e)}
This is useful if you need something from the event, for example a value from an input.
OR
onClick={() => yourFunction()}
This will prevent you from passing unwanted arguments to your functions. Hope this helps.
u need to correctly type the verify function to match the expected onClick prop type in each component.
For VerifyButtonProps, the verify function can be passed like:
const VerifyButton: React.FC<VerifyButtonProps> = ({ onClick }) => (
<button onClick={onClick}>Verify</button>
);
const App = () => {
const handleVerify = () => {
verify();
};
return (
<div>
<VerifyButton onClick={handleVerify} />
</div>
);
};
For TButtonProps, the verify function needs to be converted to a proper React.MouseEventHandler:
const TButton: React.FC<TButtonProps> = ({ onClick, children }) => (
<button onClick={onClick}>{children}</button>
);
const App = () => {
const handleVerify = (event: React.MouseEvent<HTMLButtonElement>) => {
verify();
};
return (
<div>
<TButton onClick={handleVerify}>Verify</TButton>
</div>
);
};
when u make these changes, TypeScript will catch the type mismatch and display an error during the compilation step, rather than at runtime.

How can I best use rxjs to perform multiple fetch operations in a single action using a forEach approach

I am new to React and tasked with modifying an existing application which already performs multiple api calls. The pattern that is in use in the app is demonstrated by the below example. EG if an action mapperActions.selectPolygon of type CaseReducerActions<CaseReducers> is raised, then the below code will execute, calling the fetchPolygon function to retrieve data asynchronously from an AWS lambda and the returned result will be processed by the fetchPolygonCompleted function.
let fetchPolygonStatsEpic = (action$: any, state$: StateObservable<RootState>) => action$.pipe(
ofType(
mapperActions.selectPolygon.type,
mapperActions.alterQueryIndexname.type,
),
filter(() => state$.value.mapper.selectedPolygon !== undefined),
switchMap(() =>
concat(
of(globalActions.startLoading('polygon')),
fetchPolygon(state$.value.mapper).pipe(
map(result => mapperActions.fetchPolygonCompleted(result)),
catchError(e => of(globalActions.errorOccurred(e.message)))),
of(globalActions.stopLoading('polygon')),
)
)
)
The change I need to implement is as follows:
I have a type 'Framework'...
export type Framework = {
....
}
...a collection of these...
export const frameworks: {[framework: string]: Framework} = {
liveng0: {
...
},
liveng1: {
...
},
habmosCairngorms: {
...
},
spaceintCairngorms: {
...
}
}
..and an API method that retrieves data for a specified Framework...
export let fetchHabitats = (requiredFramework: Framework): Observable<FrameworkHabitats> => {
...
}
When the action mapperActions.initialise of type CaseReducerActions<CaseReducers> is raised, I need to execute a block of code which will execute and process 4 separate asynchronous API calls to fetch the data for each of the 4 framework types above in turn.
A naive approach of simply copy-and-pasting the API call for each framework in turn within the app's establised pattern for fetching data actually works correctly and produces the results I require, ie:
let fetchHabitatsEpic = (action$: any, state$: StateObservable<RootState>) => action$.pipe(
ofType(
mapperActions.initialise.type,
),
switchMap(() =>
concat(
of(globalActions.startLoading('habitats')),
fetchHabitats(frameworks.liveng0).pipe(
map(result => mapperActions.fetchHabitatsCompleted(result)),
catchError(e => of(globalActions.errorOccurred(e.message)))),
fetchHabitats(frameworks.liveng1).pipe(
map(result => mapperActions.fetchHabitatsCompleted(result)),
catchError(e => of(globalActions.errorOccurred(e.message)))),
fetchHabitats(frameworks.habmosCairngorms).pipe(
map(result => mapperActions.fetchHabitatsCompleted(result)),
catchError(e => of(globalActions.errorOccurred(e.message)))),
fetchHabitats(frameworks.spaceintCairngorms).pipe(
map(result => mapperActions.fetchHabitatsCompleted(result)),
catchError(e => of(globalActions.errorOccurred(e.message)))),
of(globalActions.stopLoading('habitats')),
)
)
)
Although the above works as required, it clearly needs to be replaced with a forEach approach that operates over each Framework in turn and treats each API call atomically, whilst still being able to trigger all 4 from a single action. Could you please explain the syntax required to achieve this?
You can use Object.values() to iterate over the frameworks and Array.map() to map them to API calls, as follows:
concat(
of(globalActions.startLoading('habitats')),
...Object.values(frameworks).map(framework =>
fetchHabitats(framework).pipe(
map(result => mapperActions.fetchHabitatsCompleted(result)),
catchError(e => of(globalActions.errorOccurred(e.message)))
)
)
)

Typescript:There is no exported member when I was importing a function

I wanna use getSingerInfoRequest in redux. but when I import{..}from '../../api/request', the vscode told me that there is no export member getSingerInfoRequest. The question is that all of those functions are the same except the URL
VSCode can find all of those functions except the getSingerInfoRequest
If I copied this function into redux file => no exported member getSingerInfoRequest
//...other functions
export const getRankListRequest = () => {
return axiosInstance.get(`/toplist/detail`);
};
export const getAlbumDetailRequest = (id: number) => {
return axiosInstance.get(`/playlist/detail?id=${id}`)
};
//I need to import this function
export const getSingerInfoRequest = (id: number) => {
return axiosInstance.get(`/artists?id=${id}`);
};
I don't know what's wrong with this function. Need ur help guys, thanks
And I have tried to write some new test functions behind getAlbumDetailRequest. Same, the VScode can't find them.

fp-ts: Filter out certain left values, and error on right

I'd like to ignore certain errors using fp-ts (if they happen, it means everything went well, i.e. missing account during signup process).
I have the following code:
export const handleSignup = async (server: FastifyInstance): Promise<void> => {
server.post('/signup', async (req, res) => {
const {email} = req.body as SignupPostData
const {redirectUri} = req.query as Record<'redirectUri', string>
return pipe(
withDb(lookupAccountByEmail)(email),
TE.chain(() => flow(generateMagicLinkToken, TE.fromEither)(email)),
TE.chain(sendSignupEmail(email, redirectUri))
)().then(foldReply<SignupApiResponse>(res))
})
}
The lookupAccountByEmail function will either return an Account, or will return an error object.
If an account is returned, I need to return an error with code 'account-exists'. If an error object with the code 'account-not-found' is returned, I'd like everything to continue as if there were no problem. If an error object with any other code is returned, it should still error.
What's the best way to handle this in fp-ts?
You can use TE.fold.
const doSignup = pipe(
generateMagicLinkToken(email),
TE.fromEither,
TE.chain(sendSignupEmail(email, redirectUri))
)
return pipe(
email,
withDb(lookupAccountByEmail),
TE.fold(
left => left.error === 'account-not-found' ? doSignup : TE.left(left)
right => TE.left({error: 'account-exits'})
),
T.map(foldReply<SignupApiResponse>(res))
)()

how to remove number from array of numbers using TypeScript and React

I have a parent component (App.tsx). In this component I'm defining a hook that holds and sets an array of numbers:
const [valuesList, setValuesList] = useState<number[]>([]);
for a previous childcomponent (AddNumber.tsx) I defined a function that adds a number to the array of numbers:
const addNumberToList = (num: number) => {
setValuesList((prev) => prev.concat(num));
};
then I passed the function to the child component. That worked fine.
However now I need to create a function to delete a number from the array.
Does anyone now how to implement that function?
In a previous app I implemented deleting a number from the list like this:
setValuesList(valuesList.filter((e) => e !== value));
But because I'm using TypeScript, I can't do it like that anymore, because I need to pass the deleteNumberFromList method to the childcomponent(NumberList.tsx)
Thanks for your help,
Anthony
It will be pretty similar, create a new function that takes the number as argument and updates the state after removing the number from the valuesList.
const removeNumberFromList = (num: number) => {
setValuesList((prev) => prev.filter(el => el !== num))
}
// can be called like removeNumberFromList(10)
// or can be passed as onClick={() => removeNumberFromList(10)}
The type definition is pretty much the same
interface AppProps {
addNumberToList: (num: number) => void;
removeNumberFromList: (num: number) => void;
}
You can still use that filter bit of code, you just have to add types to it:
setValuesList((prev) => prev.filter((e:number) => e !== value));

Resources