Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

> you no longer know when code is executed - it's magic

Kind of. That is what makes react a framework and not a library in my opinion. That being said, it's still learnable and managable (in pure react).

> state can be managed in ~5 different ways, with a lot of ceremony and idiosyncrasies

Not a valid argument. Just use react state and be done with it. If you go for anything on top, well yeah. But to be honest, other frameworks have the same problem, even in the backend.

> It is, absolutely, an eDSL (`useState` & `useEffect` introduce new semantics using JS syntax), and a proper DSL would be a lot easier to learn.

That is absolutely true and a valid point. It's mostly a shortcoming of javascript/typescript where it would be too annoying to pass down dependencies/props in a long chain of elements. So in an insufficient programming language you have the choice between typesafety+verbosity (react without hooks) or conciseness+unsafety (hooks are not typesafe, they are not regular functions and hence can't be refactored like them etc.). Basically, they break composibility.

Honestly, the frontend world circles around that problem and every blue moon something things they found the enlightenment and then they give up the previous benefits for new benefits. And the circle repeats.

I wonder if maybe effect.website might be able to change that in the future. It has the potential.



> It's mostly a shortcoming of javascript/typescript where it would be too annoying to pass down dependencies/props in a long chain of elements

Do you know a language that has solution to this on a language level?

The only similar thing I can think of are dynamic vars in Lisp. At a quick glance they remind me of React Context.


I personally use that style in Scala with ZIO. Because the same problem exists in basically every language. I think more and more languages capture something like that and more under the term "capabilities". In a sense, the Rust borrowchecker is a very specialized case.

https://effect.website/ offers something very similar that in their effect-type; the "environment" parameter.

Basically, imagine you have many foos like `fun foo1(props: Foo1Props): string {...}` and so on, and they all call each other. Now you have one foo999 at the very bottom that is called by a foo1 at the very top. But not directly. foo1 calls foo5 which calls foo27 and so on, which then calls foo999.

Now if foo999 needs a new dependency (let's say it needs access to user-profile info that it didn't need before) you have to change the type signature and update Foo999Props. To provide it, you need to update the caller, foo567, and also add it there. And so on, until you find the first fooX that already has it. That is annoying, noisy (think of PR reviews) and so on.

Using the effect-type of effect.website you basically can the move the dependency into the returntype. So instead of returning `string`, `fun foo1` will now return `Effect<string, Error, Dependencies>` where Dependencies would be Foo1Props - which means it will not have the props parameter anymore. (or, they way I do it, I only use the Dependencies for long-lived services, not for one-off parameters)

Inside of fun1 (or funX) you can now access the "Dependencies". It's basically a kind of "inversion of dependecies" if you want so.

Seems like just moving the required dependencies from the parameter into some weird complex result type. But, there is a big difference! You have to annotate the parameter, but you can have the compiler infer the return type.

So now if you have a huge call chain / call graph, the compiler will infer automatically all dependencies of all functions for you (thank god typescript has good union types that make that work - many other languages fail at being able to do so).

So you write:

    def foo5(nonReleventProps: Props) = 
      (props) => { const x1 = foo10(); const x2 = foo20(); return x1+x2; }
Where foo10 is of type `Props => ServiceA` and foo20 is `Props => ServiceB` and the compiler will infer that foo5 returns an effect that needs both ServiceA and ServiceB - and so on. (note that you have to combine the results in a different style, I used just `const x = ...` to keep it simple)

So if you make a change very far down the call graph, it will automatically propagate up (as long as you don't explicitly annotate types). But, you still have full typesafety and can see for each function (from the inferred type) which dependencies it needs and so you know during a test hat you have to pass and what not.

Lisp is a completely different world because it is dynamically typed. In a sense it doesn't have the problem from the beginning, but it sacrifices type-safety for that.

> At a quick glance they remind me of React Context.

Yes, the intend is basically the same, but React Context is... well, an inferior version, because if you use a context where it has not been provided, it just blows up at runtime and no one protects you from that.




Consider applying for YC's Fall 2025 batch! Applications are open till Aug 4

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: