fp-ts icon indicating copy to clipboard operation
fp-ts copied to clipboard

How would one use fp-ts with React?

Open amaury1093 opened this issue 5 years ago • 13 comments

📖 Documentation

From the docs:

IO actions are thunks so they are terminated by calling their () function application that executes the computation and returns the result. Ideally each application should call () only once for a root value of type Task or IO that represents the entire application.

How would one achieve something like that with a React application?

Right now, I'm calling () on Tasks and IOs multiple times, once per component, when I need to execute those Tasks/IOs.

Simple example, which i often do, and that I feel goes against what the docs advocate:

import { tryCatch } from 'fp-ts/lib/TaskEither';

const fetchJSON = tryCatch(/* fetch from remote */);

function MyComponent(props: Props) {
  const [data, setData] = useState<MyType | undefined>();
  useEffect(() => {
    pipe(
      fetchJSON,
      chain(d => rightIO(setData(d))) // I suppose all state setting should be IOs?
    )() // Calling () here, not at application root
  }, []);
}

Note: I had a look at https://github.com/gcanti/elm-ts, but I don't want to use an Elm-like structure, just plain old React functions with hooks

amaury1093 avatar Jul 06 '19 15:07 amaury1093

I believe that in order to be able to run the monad only once at the top of your component tree, you would need to wrap all components somehow, otherwise this can't work. Examples of this are react-dream or MonadicReact (I have not tried them myself).

grossbart avatar Jul 12 '19 09:07 grossbart

@amaurymartiny I would not recommend to use the hooks api at all, if you want to be as functional as possible. You would already need to wrap your useState function in an IO monad cause this function is not pure.

mlegenhausen avatar Jul 15 '19 09:07 mlegenhausen

Essentially, what you have at the moment is a tiny bit of functional code inside an imperative React app. There is strictly speaking nothing wrong about that. It just means you are using fp-ts in a rather limited scope. You could proceed to create other such functional "bubbles" inside your app. This just means that your overall architecture will remain imperative.

Having only one Task or IO means you have managed to defined your application architecture in a functional manner. Your functional React app would then contain imperative bubbles. However, the imperative bubbles would never be executed in the functional context. Instead your code would simply combine the imperative bubbles and return them towards the application root.

The benefit of going 100% functional is that reasoning about the code becomes easier since the imperative bubbles that create unexpected consequences are executed "outside" your application. However, this also means less control over what the computer is doing since you are describing ideas rather than actions.

Ultimately you need to decide what parts of your app are functional or imperative. Functional parts will have more clarity while the imperative parts give you more precise control over execution.

cyberixae avatar Jul 27 '19 21:07 cyberixae

However, this also means less control over what the computer is doing since you are describing ideas rather than actions.

This is exactly were we need to head towards! We just need to exchange "ideas" with "specifications" and need to trust that the machine or the used framework can find out the best way to do what we want. This should also be the default way cause "premature optimizations are the root of all evil".

mlegenhausen avatar Jul 30 '19 08:07 mlegenhausen

Really good question.

Still not quite clear to me as an experienced react developer but a total newcomer to fp-ts. Would it be possible to re-open this and keep it open, until there are any examples or documentation / learning resources describing how to best use fp-ts in a react application? :-)

sorenhoyer avatar Jan 06 '21 10:01 sorenhoyer

Did you try to wrap your hooks?

import * as IO from 'fp-ts/IO'
import { useEffect, useState } from 'react';

export const useEffectIO = <A>(
  ma: IO.IO<A>,
  deps: any,
) => useEffect(ma(), deps)

export const useCallbackIO = <R, A>(
  ma: (r: R) => IO.IO<A>,
  deps: any,
) => useCallback(r => ma(r)(), deps)

export const useStateIO = <A>(empty: A) => {
  const [a, setA] = useState(empty)
  return [a, (a) => () => setA(a)]
}

Disclaimer: Didn't try it on an IDE, so I hope it compiles.

SRachamim avatar Jan 06 '21 12:01 SRachamim

Really good question.

Still not quite clear to me as an experienced react developer but a total newcomer to fp-ts. Would it be possible to re-open this and keep it open, until there are any examples or documentation / learning resources describing how to best use fp-ts in a react application? :-)

check elm-ts, it's creating elm architecture with fp-ts and react and rxjs.

I think it's the purest form that you can get with fp-ts

mohaalak avatar Jan 06 '21 16:01 mohaalak

Hmm if it takes this much plumbing, maybe it would be better just to switch to ReScript.

sorenhoyer avatar Jan 06 '21 20:01 sorenhoyer

rescript is not pure FP, if you just want some functional you can use it like rescript here too.

mohaalak avatar Jan 06 '21 21:01 mohaalak

I'm currently working on a project that includes some fairly complicated data manipulation on the client side, and I ended up writing this hook to be able to work with fp-ts in React in a way that feels a little 'cleaner' to me, w/ regard to mixing imperative and functional styles. Thought it might be of interest here; I welcome feedback/bug reports.

Rossh87 avatar Jun 08 '21 02:06 Rossh87

Just my two cents: I use fp-ts with React all the time, but I take a simpler hybrid approach. I wrote React as, well, react. My business logic exists in separate TS files which expose functions that can be called from my React components/hooks. My business logic is purely functional, but I use the React APIs as-is.

As much as I love FP, there's a point where the quest for purity imposes more costs than benefits. A hybrid approach when working with something like React is ideal IMO.

craigmiller160 avatar Jul 02 '22 01:07 craigmiller160

Just my two cents: I use fp-ts with React all the time, but I take a simpler hybrid approach. I wrote React as, well, react. My business logic exists in separate TS files which expose functions that can be called from my React components/hooks. My business logic is purely functional, but I use the React APIs as-is.

As much as I love FP, there's a point where the quest for purity imposes more costs than benefits. A hybrid approach when working with something like React is ideal IMO.

If you use GraphQL and a fairly sophisticated GraphQL client such as Relay ideally there should not be much business logic left in your own client code (except a little local UI state). Most if not all would be in your GraphQL server and GraphQL client. But I get your point - you're basically saying we should not waste time wrapping non-functional api's and just use it within the functions in those cases right. Same as you'd probably do in an Express server. The point should be to simplify, not to fight hard to complete by wrapping non-functional api's.

sorenhoyer avatar Jul 03 '22 00:07 sorenhoyer

I stumbled upon the following article: https://andywhite.xyz/posts/2021-01-28-rte-react/ It does show towards the end, how to use fp-ts with React, it could be a starting point?

NB: Beware, it's main focus is to showcase ReaderTaskEither.

damien-biasotto avatar Nov 21 '22 23:11 damien-biasotto