React is not that at all. Unless your UI is very simple, React is not f(newState) => UI. React isn't functional. More on that here: https://mckoder.medium.com/why-react-is-not-functional-b1ed1...
React is not that at all. Unless your UI is very simple, React is not f(newState) => UI. React isn't functional. More on that here: https://mckoder.medium.com/why-react-is-not-functional-b1ed1...
The complicated bit is the handling of events and changing the state. Hence why you get Redux for state but there is no “Redux for views”
It's not wrong, it gives you one kind of separation of concerns: components. But "the view is a function of state" is completely misleading IMO.
> But then they added hooks and useState and useEffect and so on, which made the functions impure. The problem with hooks is that they “hook into” React state and lifecycle features, which means it is modifying global state.
React components don’t update the dom every time they run, they update the fiber tree / virtual DOM.
Only in the commit phase is the DOM updated and side-effects are run.
My rough understanding is that:
Hooks, excepting useEffect, all run in the render phase.
UseState hooks store data in the component in the fiber tree, and capture a sequential list of updates when you call setState.
Algebraic effects can seem a bit odd at first, but it’s really like a generalised try/catch. Instead of storing state in the function itself, you reach out up the stack to the component and store it there. And every hook you call from your hook stores it there too.
It’s why call order is important, because it’s how they know which is which.
You don’t modify any global state though.
The actual component function needs to be able to be called multiple times in the render phase (which just updates the vdom), and must have the same output each time, if the inputs/props are the same.
Every now and then, the tender phase can be interrupted to run the commit phase, which synchronously brakes a snapshot of the vdom and applies all the needed updates to the outside world.
Perhaps it’s better to think of as
render: f(props) -> descriptionOfUI commit: apply(descriptionOfUI)
I would absolutely say components and hooks are functional, with algebraic effects. And that the resulting data struct from those components, is what’s used to produce side effects.
Even useEffect, is a description of a side effect to run, it doesn’t run in your hook, it runs on commit with all the other side effects.
There’s a ton of optimisations under the hood but that’s roughly my understanding of how it works.
In functional programming, functions are not allowed to do that. The only thing a function is allowed to do is return a value.
> ...you reach out up the stack to the component and store it there.
FP functions are not allowed to do that. In FP functions can only rely on the parameters passed to it (it cannot refer to anything outside) and it should not do anything beyond return a value.
> It’s why call order is important, because it’s how they know which is which
In FP, functions can be called in any order (unless one function needs the return value of another), and the final result is not affected by the order.
> You don’t modify any global state though.
If you have state it is not FP, whether it is global or not.
Oh, yes, they are allowed to do that. You capture the effects into a list, and return the tuple of list and return value. If you feel like it, you wrap that into a monad to pretend you are only "returning" a single thing.
> In FP functions can only rely on the parameters passed to it (it cannot refer to anything outside) and it should not do anything beyond return a value.
You mean explicit parameters (props) and implicit parameters (context), right? I thing you forgot the latter. Implicit parameters have existed for quite a bit, for example in the reader monad.
> In FP, functions can be called in any order (unless one function needs the return value of another), and the final result is not affected by the order.
Yup, except if you write a bunch of functions and store them in a list with the intention of applying the result of the first entry of the list against the first entry of the list. Now, if you construct the list of functions as effects (by hiding them behind a monad) you find out that you need to call the effects in the same order as to construct the list of effects in the same order. This doesn't contradict FP, it contradicts your mental model of how React is supposed to work.
> If you have state it is not FP, whether it is global or not.
The state resides in the runtime executor, exactly like Haskell's IO monad. Again, this contradicts your mental model, not React or FP.
You keep the clarity of declaring what you want to happen, and abstract the how to the effect handler.
Take a look: https://github.com/wisercoder/eureka/tree/master/webapp/Clie...
You just need to know JavaScript, HTML and CSS, not much else. The code is simple, and yet maintainable. No need for hooks, useEffect, useState, useMemo and all that crap.
I can’t really call this code simple since it had to resort to timing hacks to get its functionality in order. A random 250ms delay and bam, bugs you cannot reliably reproduce
private onDropDownMouseOver(ev: MouseEvent): void {
if ((new Date()).getTime() < this.whenScrolled + 250) {
// We automatically get a hover event when we scroll, ignore it in order to keep our current highlight.
return;
}
const element = ev.target as HTMLElement;
if (element.classList.contains('combo-dropdown-item')) {
addClassExclusively(element, 'combo-highlighted');
}
}
Some event stores the time it has been fired, and this method checks that it has not been long since the firing. With react, which item has the highlight would have to explicitly be in the state and completely remove the need to do this optimization.And this is an example of what I consider state being stored in the DOM:
private isDropDownVisible() {
return isVisible(this.dropDown);
}
The view has logic that depends on whether the dropdown is visible, which is checked by dropdown.style.display !== none. Now any element in your page has the power to modify this component’s logic by modifying the DOM> Now any element in your page has the power to modify this component’s logic by modifying the DOM
If some part of your application intentionally does something wrong, it can break your application whether it is React or some other tech. In this particular example, if you prefer, you can have a member variable to keep track of whether the dropdown is visible or not. Still no need for React.
As you can see, none of the complexity of React is necessary. No need for hooks, useEffect, useState, useMemo and all that crap.
The code does not have any complexities of hooks (which are just a javascript approximation of do notation, I don’t believe it is that complex), but what it does have is the requirement to write down all of your mutations correctly, where every “down” is an exact opposite of the “up” and does not leave any artifacts—with no way of formally checking. At least for myself, I think that level of discipline is unreasonably high so I tend to prefer stricter typed functional solutions where the view is composed purely from the state
I love how you do React. I mean, what's more react-y than blanking the page then appending HTML? I'm gonna call that Rejqueryact, because it's jQuery inside React.
Is this really much more complicated to implement than the equivalent implementation in React? (It's probably a bit more responsive, at least.)
The comment was a reference to the fact that if you don’t use a framework, you’ll end up a building one anyway.
The code you linked is what React helped me get away from. There’s multiple files in different folders for one screen. It’s hard to see how it composes, which is a useful boundary as it lets me know in the scope of that part of the UI.
UI is _hard_. There’s complexity that needs to go somewhere, and I’m one of those who prefer to push as much if it that isn’t business logic into a framework.
This becomes exponentially more important for every engineer you add. Go success is along these lines, with typically only one way to do things it’s much easier to share code.
From the code you linked, I could find my way around, but piecing it all together took a lot more work than any of the frameworks.
You mention only needing to know JS, HTML and CSS. But you missed that you still need to know how all that code is put together, what calls what. How it updates. React needs this too, the only difference with it being a framework is that knowledge transfers to a different project.
And to be clear, I’m not saying you need to use react or a framework—going your own way is often how amazing new things are found.
What I am saying is your attitude isn’t helping you here. React is still arguably the most used framework[0]. There’s a reason so many of us like using it and it’s what you described as “other crap”.
Frameworks help with collaboration, in the same way programming languages do. To do anything interesting with a general purpose language, you’re either going to use a framework, create a new one with good documentation and build a community of engineers around it, or build a halfbaked one that few people understand.
I’m open to other ways of doing things. It’s a good way to grow. I learnt SwiftUI a little while ago, and while I don’t use it regularly it improved my understanding of UI building. I learnt imperative UI API ages ago, and know for sure I want to avoid/contain that into a more manageable declarative approach.
[0] https://trends.builtwith.com/javascript/javascript-library/t...
We didn’t have classes until ES6. And they’re not real classes, they still use prototypical inheritance under the hood.
If you want truly idiomatic JS, you shouldn’t be using classes. You should only be writing functions, and build your class-like objects with prototypes.
On a practical level, I absolutely recommend new engineers learn React. The job market is enormous, it’s a skill the market still rewards well.
And it’s a very productive framework. It rewards you for learning it, with better collaboration and velocity.
I would say the same for the other major view frameworks too, but React still has the biggest job market.
Until ES5, the industry thought JS was a Frankensteinesque monster.
Until maybe 5-7 years ago, anything that wasn’t OOP was considered a bit weird.
Calling any of these languages weird, is like policing English speech. We borrow so much from other languages that’s words have multiple meanings. Pronunciation follows contradictory rules, and regional dialects can be hilariously incompatible.
There’s a few languages closer to French, which has a strict control on what the French language is. For example, Go. There’s really only one paradigm them, only one way to do things.
Like it or not, pushing the boundaries of language design and framework design is how we improve as an industry.
You can’t not like a new thing because it’s weird at face value. You risk missing the forrest for some moss on a tree, and being left behind.
And sorry, that’s the same article this comment thread spawned from. I didn’t find it convincing at all, it’s let down by a lack of understanding in a few key areas.
For exactly the same reason OO is "put the code that manages the state near to the state and split it in manageable parts" and not "dog inherits from animal therefore the code is correct".
Leave the faith at the church and approach code with science.
If you’re fetching some data in a useEffect you might not get a result that day.
If you’re updating something with setState, you are still managing state. UseState is a reference to a mutable object now.
You’re simply not getting the same answer every time your now impure function is called.
In order to do understand what’s happening you need to recreate the whole state. Which is exactly the same reason state management is hard as in OO or procedural.
You might have a mental model that explains why it is still FP, but for all practical purposes of that paradigm it isn’t.
When you reply to "in FP, functions cannot do that (change state anywhere)", you say "you are allowed to do that: capture the effects into a list, return the tuple of list and return value" without realizing you're saying exactly the same thing?! The distinction between "return value" and "return value plus some other thing" does not exist. What matters is that the function's only observable modification is in what it returns, be it one value of many. This is basic FP.
The function's only observable behavior is that it returns a tuple with JSX and some effect descriptors.
No, this is not basic FP. This is somewhat advanced FP.
function MyComponent({}) {
const [count, setCount] = useState(0);
const [active, setActive] = useState(true);
useEffect(() => if (active) setTimeout(1000, () => setCount(count+1)), [count, active]);
...
can kind of be thought of as if it accepts an extra hidden set of parameters, kind of like this: function MyComponent({}, hook1 : useState, hook2 : useState, hook3 : useEffect) {
const [count, setCount, hook1Result] = hook1(0);
const [active, setActive, hook2Result] = hook2(true);
const hook3Result = hook3(() => if (active) setTimeout(1000, () => setCount(count+1)), [count, active]);
...
return [<div></div>, [hook1Result, hook2Result, hook3Result]];
}
In this version, those hook functions are side-effect free, pure functions that our caller provides us with, closed over the previous stored state of our component in the tree; and when we return the results along with our rendered output, our caller has the new state of our component together with our desired DOM.Instead of forcing us to write all that extra code, route those extra variables, and figure out the metadata mechanism whereby the caller of the render method figures out what hook functions to supply, the rules of hooks let you use JavaScript's imperative, procedural syntax to call hooks which are implemented using ugly side-effecting impure techniques, but in a way that pulls off something semantically identical to this pure functional flow.
It's a pretty neat trick, honestly.
Because web browsers exist, and they work in a nonfunctional way.
So to interact with one we need to do a little bit of imperative code.
And then react lets us figure out what to do when the web browser responds to that imperative code by letting us run a largely functional program.
Which is pretty neat considering we’re doing so in JavaScript.
The thing that we’re after here is not a badge that we can slap on our code saying ‘certified 100% functional’. We are trying to write code using patterns that help us reason about and structure what we want the computer to do, avoid bugs, and ship software.
If react managed to squeeze a surprisingly functional pattern into a JavaScript library to that end, can’t we just applaud the audacity and enjoy using it, without nitpicking that they didn’t actually reimplement the lambda calculus?
React is neither FP nor OOP. More on that here: https://medium.com/codex/can-we-all-just-admit-react-hooks-w...
React builds on functional reactive programming, something the FP world is known for[0]. I’m not saying it’s pure FP in the dogmatic sense, I just wanted to point out it’s closer to FP than the other paradigms.
Without the ability to interact with the outside world (side-effects), FP is not of much practical use. Not to say it’s bad or shouldn’t exist, just we need to add to it a bit to make it useful.
Why would we do that? Because the underlying ideas and principles provide a great foundation. Building on that to get FRP and algebraic effects, monads and so on, is very different to starting from OOP and brining FP ideas over.
React hooks are an interesting and tricky thing to put in a box, because they’re almost monads, almost thunks, almost algebraic effects. The interplay with fibers and components makes them hard to pin down, but they are much closer to functional than any other paradigm.
[0] https://wiki.haskell.org/Functional_Reactive_Programming
What we have with badly written React apps now (90% approx.) is a UI, that is not at all behaving like a typical website and very far from being like FP. We often cannot simply go back with the back button and expect to see the same page rendered. I mean we can, but the websites will fart in our face, if we do. We do not get the ability to simple send a link to someone and have them see the exact same page. It is a huge departure from anything FP.
Furthermore from the dev perspective components feel more mainstream OOP than FP, since they store internal state and update it, which indirectly causes things I described above.
Not sure where people see the FP. Not sure they really have done any FP language before or tried in projects to strictly adhere to FP principles, because that looks a lot differently than React components and mixing state, layout and styling in JSX/TSX.
They're almost classes (/objects). The implementation is what it'd look like if you wanted to make a weird half-assed OO... thing. Property and method declarations, getting attached (ultimately) to an object. But it's FIFO access/calling, rather than using named properties and methods w/ a lookup step. And the declaration syntax is bizarre and hard to read. And they're slower than regular JS OO (because they add an extra layer of JS on top of it).
useEffect is a side effect and if you use it you're gonna have a bad time.
Right.. but then you can't really use React for building serious UI.
But it’s a powerful escape hatch like rust’s `unsafe` block. Rust can do amazing things to ensure safety of your program, but there’s still times where a human can do it better.
By signposting that, keeping it to limited blocks, and wrapping it up in a safe function you get the capability to do hard things, while exposing a safer API.
useEffect is similar, it lets you write the imperative synchronisation logic. For example if you need to interact with a DOM element that only has an imperative API, someone needs to do the work somewhere to wrap it in a declarative one. You can do that in a component with a useEffect hook, and then expose a declarative API.
Because at some point your function call is manipulating registers on the CPU. If we go even further, the CPU needs to load its next instructions and then the data required.
Point being, we abstract and encapsulate complexity and provide a simpler API around it. It’s the only feasible way to manage the inherent complexity.
React builds up a virtual description of UI. react-dom and react native take that description and synchronise it to the UI layer.
If you need to do something extra, that they don’t handle like managing a map widget (ie mapbox) in React that only has an imperative API, it sure would be nice to do that yourself.
You can build a UI without useEffect. You can hide messy implementation details inside a well tested component, and contain where useEffect is being used.
And, you can understand how useEffect works so that you can use it without it having a negative effect on you.
and
"You can ... contain where useEffect is being used."
are not the same statement. Maybe I'm being pedantic, but when I read "build without X", I think there is some method that can be used to avoid X, not that I can hide away X.
All declarative code eventually runs something imperative.
And anything non-theoretical is going to have side effects somewhere.
Painting to a screen is a side effect.
Reading a file is a side effect.
Software gets messy when it intersects reality.
But, we can do quite a lot by staying in a theoretical realm. You can build a whole UI in the theoretical realm, where no side effects exist.
When you actually need to run it though, you drop out the the theoretical and into reality. Now there’s side effect’s everywhere.
So you contain them. Encapsulate those side effects into composable blocks. Test them extensively. Offload that work to someone who’s just focussed on managing them.
Updating the DOM is a side effect. Making a network request is a side effect.
Contain them, test them, and then you can stop thinking about them.
If someone else has done all the work to contain side effects, you can build a UI without them.
Why would fetching data depend on whatever is being rendered?
You know what data you need or not need before rendering anything, or you will be in a world of pain.
- Click the link to show top scorers
- Initiate fetching the data
- (The view may change to indicate you're loading the top scorers, if you want, that's a different matter).
- Receive the data, the view changes to display the data
This is opposed to:
- Click the link to show top scorers
- View changes to show top scorers component. A component loads, or mounts, or inits, or does something which triggers a data fetch
- Receive the data, the view changes to display the data
Your app "knows" you need the data when the user clicks the link, no need to involve the view components into this.
This gives you the ability of adding a lot of complexity to how you fetch the data that is best handled outside a UI component (like caching, reusing it in other components, error handling, retrying, throtling, whatever), while saving you from a whole class of problems where a misunderstood component lifecycle has consequences for your data. Frameworks like React or Angular are going to try to help you by caching outputs, reusing components, dropping updates for the same frame, prerendering, preloading or anything really. If you fetch data when a component does any of that, suddenly you have to care about it and fully understand it. The abstraction leaked. If you treat the component as close to a pure function as you can, things become much simpler.
This applies to relatively big, relatively complicated apps, controlled by a single entity, where it pays off to prioritize simplicity.
- how is the data fetch completion signalled to the component?
- how is the data piped into the component?
Would be happy to be pointed to some example code if you have.
Both have the same answer. When you fetch the data you put it in the state, and then you render your components based only on that state.
If we're talking React the state makes it into the component through the props (pure UI component) or, if using Redux or similar, some kind of hook that triggers a rerender when the store changes (useSelector? My React-Redux is a bit rusty).
The point is not to worry anymore about change detection and rendering, because the framework takes care of it by being either fast enough that it can run on every change, or smart enough that it can tell when it's not necessary to re-render (caching, change detection).
Of course, this setup with redux etc makes a lot of sense for bigger apps with lots of state coming from different sources. I don't have any specific example I can point to, but any redux implementation examples could help. I use NgRx with Angular, and the docs are OK at explaining the flow of data.