DEV Community

Cover image for React vs Signals: 10 Years Later

React vs Signals: 10 Years Later

Ryan Carniato on March 01, 2023

How does the old Winston Churchill quote go? Those who fail to learn from history are doomed to repeat it Although a more ironic addendum might...
Collapse
 
dan_abramov profile image
Dan Abramov • Edited

I'm not sure I understood this article. I think we're talking about different things. The responses you wrote don't correspond to what I meant to say. I'll try to clarify.

The problem that React "fixes" is inconsistent UI between initialization and updates. Before React, you would typically write some logic for initialization of some UI, and write a separate piece of logic that would update that UI to match the latest state. Like you did with the imperative manual button example. Of course, the problem with this is that they get out of sync.

The key insight is that the user doesn't care if the component has just appeared or if it updated. The end result is supposed to be the same. So we want to somehow get to the "end result" based on the current information. For React, this means re-running your component function and figuring out how to update the UI to match that. For Solid, this means re-running the things embedded in your template.

So far so good—but not quite.

The interesting question to me is where do you place the rendering logic. The kind of logic that governs what your component displays. Your example omits any realistic logic, so let's consider this React component:

// Working version in React
function VideoList({ videos, emptyHeading }) {
  const count = videos.length;
  let heading = emptyHeading;
  if (count > 0) {
    const noun = count > 1 ? 'Videos' : 'Video';
    heading = count + ' ' + noun;
  }
  return <h1>{heading}</h1>
}
Enter fullscreen mode Exit fullscreen mode

The big idea in React is that you should be able to write code like this.

You should be able to write rendering logic that takes some data, makes some decisions, formats some inputs, and then embeds them into the template. And that logic should not describe initialization only — it should run on updates too!

The user doesn't care whether it's initialization or update. The user just wants to see the latest state — whatever it is — reflected on the screen. That's what React "fixed": it made rendering logic reactive by default. Whenever you write rendering logic in React, you can be confident that it won't get out of sync with the data you pass.

Solid follows a different strategy. Only "holes" in the template are reactive. As a result, code like this doesn't work in Solid (even if I access stuff via props.something):

// Broken version in Solid
function VideoList(props) {
  const count = props.videos.length; // never updates in Solid 
  let heading = props.emptyHeading; // never updates in Solid
  if (count > 0) {
    const noun = count > 1 ? "Videos" : "Video";
    heading = count + " " + noun;
  }
  return <h1>{heading()}</h1>
}
Enter fullscreen mode Exit fullscreen mode

Yes, I get the point, it's not supposed to work in Solid because things execute once. Since the linter warns about this code, it's fair to say that this is not a valid example for me to complain about.

What is the canonical way to structure this code instead? From my understanding (I might be wrong), the idiomatic Solid code would be:

// Working version in Solid
function VideoList(props) {
  const count = () => props.videos.length;
  const heading = () => {
    if (count() > 0) {
      const noun = count() > 1 ? "Videos" : "Video";
      return count() + " " + noun;
    } else {
      return emptyHeading;
    }
  }
  return <h1>{heading()}</h1>
}
Enter fullscreen mode Exit fullscreen mode

This reads pretty nicely, but note that with Solid, I've had to restructure the code around each value instead of relying on the control flow of the outer function.

Suppose I wanted to add more rendering logic into count > 0 condition (for example, to determine some other variable). In React, I would put it inside the if statement I already have:

// Working version in React
function VideoList({ videos, emptyHeading }) {
  const count = videos.length;
  let heading = emptyHeading;
  let somethingElse = 42; // can add something here
  if (count > 0) {
    const noun = count > 1 ? 'Videos' : 'Video';
    heading = count + ' ' + noun;
    somethingElse = someOtherStuff(); // can add something here too
  }
  return (
    <>
      <h1>{heading}</h1>
      <h2>{somethingElse}</h2>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

In Solid, I don't know how to do this other than to duplicate the if statement again inside a new const somethingElse = () => { /* my logic here */ } binding:

// Working version in Solid
function VideoList(props) {
  const count = () => props.videos.length;
  const heading = () => {
    if (count() > 0) { // can't add the logic here :(
      const noun = count() > 1 ? "Videos" : "Video";
      return count() + " " + noun;
    } else {
      return emptyHeading;
    }
  }
  const somethingElse = () => {
    if (count() > 0) { // let's put this here i guess
      return someOtherStuff();
    } else {
      return 42;
    }
  });
  return (
    <>
      <h1>{heading()}</h1>
      <h2>{somethingElse()}</h2>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

I get that there are ways to compress this specific example to be shorter, but my point is that you can't rely on top-level control flow for any of your rendering logic. You have to group things by individual values. (Or you could return multiple things from heading and rename it to model or something.) Another solution in Solid is using DSL like <When>. The downside of that is now you can't easily move the computation back into JS when you want to read its result inside an event handler — for example, to create similar branching logic.

That is the point I was trying to make.

In Solid, only your template (and things explicitly referenced from it) re-executes. So putting rendering logic into the top-level component body is a mistake. You can't use top-level control flow or read values at the top level because that would undo the "fix": your initialization logic would diverge from your update logic. The linter flags it.

In React, all your rendering logic is your "template". This lets you use if statements and control flow without regrouping your code around every value you render. This also ensures that the user always sees fresh values. That's what I meant by React not "missing" updates. React doesn't let you write rendering logic that leaves initialization and updates out of sync.

The benefit of the Solid approach is that you can avoid re-executing parts of that logic because you've structured the code around the values (rather than around the control flow). Similar to how you have to restructure your code around the values when you optimize it with useMemo. But for us, this isn't the desirable end state.

With the compiler, the goal is to be able to write code without regrouping it:

// Working version in React (again)
function VideoList({ videos, emptyHeading }) {
  const count = videos.length;
  let heading = emptyHeading;
  let somethingElse = 42;
  if (count > 0) {
    const noun = count > 1 ? 'Videos' : 'Video';
    heading = count + ' ' + noun;
    somethingElse = someOtherStuff();
  }
  return (
    <>
      <h1>{heading}</h1>
      <h2>{somethingElse}</h2>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

And then the compiler would figure out the "groups" so that it has comparable performance to useMemo or something even more fine-grained. That's the hard part, but hopefully this helps explain the vision.

Coming back to your final code example. Remove useMemo, and the React code still works. Would the Knockout example still work if you did not mark this as pureComputed()? This seems a bit similar to how Solid examples require you to wrap things.

The beauty of React is that making things "computed" is an optimization, not a requirement. An optimization we can eventually put under the hood. React does not require you to write rendering logic inside-out just to get things to update. In React, everything is reactive by default. That's what I was trying to convey — perhaps poorly.

Apologies if something is incorrect in my post. It's a bit hard to write these responses impromptu. But I figured I should since my tweets were pulled here. :) Appreciate the conversation.

Collapse
 
ryansolid profile image
Ryan Carniato • Edited

I appreciate you taking the time to write such a thorough response. I misunderstood your grief with setup + reactivity approach I thought it was due to immutability. I never would associate it with update correctness as once you write things in a derived way you don't tend to have those problems.

You've definitely making me think of scenarios where derived data might take more thought to write. I don't think anyone would want code written like that in the example if avoidable. And when it does happens it happens.

The crux of it is that in some cases it is easier to group around control flow rather than data flow. Data flow is a must for memoization which is why this same refactor occurs today in React if you want to use useMemo. You have the same dilema of where to put count > 0 because you can't conditionally wrap Hooks. In Solid you always write as if the data is derived so you don't refactor to optimize.

I'd probably write this and not worry about it:

const formatHeading = (n) => `${n} Video${n > 1 ? "s" : ""}`;

function VideoList(props) {
  const count = () => props.videos.length;
  const heading = () =>
     count() > 0 ? formatHeading(count()) : props.emptyHeading;
  const somethingElse = () => count() > 0 ? someOtherStuff() : 42;

  return (
    <>
      <h1>{heading()}</h1>
      <h2>{somethingElse()}</h2>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

The refactor story is add a one liner for somethingElse and move on. Or extract out count() > 0 if you really want to.

The thing is any of the logic (other than early return, although I could show what an early return would map to if you like) you could just hoist into a function or memo. So you could just take your React code and now it's in the pure space. It's like a React component inside a Solid one.

As I said our community is full of trolls. You should recognize the wrapped code in this memo from your useInterval article.

Back to the example if you were being lazy you could do this as well but I don't think this is where you'd want to end up:

function VideoList(props) {
  const state = createMemo(() => {
    // the react component body
    const count = props.videos.length;
    let heading = props.emptyHeading;
    let somethingElse = 42; // can add something here
    if (count > 0) {
      const noun = count > 1 ? 'Videos' : 'Video';
      heading = count + ' ' + noun;
      somethingElse = someOtherStuff(); // can add something here too
    }
    return { heading, somethingElse }
  });

  return (
    <>
      <h1>{state().heading}</h1>
      <h2>{state().somethingElse}</h2>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

I think the goal of the compiler is really cool. I'm excited to see you guys get there and honestly I'm inspired to go further down this path myself. I wrote about it a while ago but you guys make me want to look at again.

EDIT: The more I look at this though isn't the data derived writing just clearer than the control flow version anyway. It's definitely more declarative feeling and more refactorable. Like let me port my Solid version to one without closures.

const formatHeading = (n) => `${n} Video${n > 1 ? "s" : ""}`;

function VideoList({ videos, emptyHeading}) {
  const count = videos.length;
  const heading = count > 0 ? formatHeading(count) : emptyHeading;
  const somethingElse = count > 0 ? someOtherStuff : 42;

  return (
    <>
      <h1>{heading}</h1>
      <h2>{somethingElse}</h2>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

This makes me wonder.. if you are going to be smart on re-execution with React Forget are you going to forbade things like console.logs or other side effects in the main component body to prevent people from observing that the code doesn't exactly run top down? The compiler sounds good but I imagine traceability gets even harder.

Collapse
 
dan_abramov profile image
Dan Abramov • Edited

Yeah, so with your final example we're essentially back to React-land.

I.e. if my entire function body is there, it always re-executes. But I thought Solid was supposed to help me be more granular! :) So we don't get the benefits of Solid here and also the syntax is more clunky (an extra indent level). Especially if aside from heading we're gonna need a bunch of other things. My JSX can't just access those values from the closure scope. Weren't we inside of a function?

If I try to make it granular again by splitting it up, then as you said it starts to look a lot like React with useMemo's. You still have to think about how to regroup things (and whether to split them up or unify them again) each time you change something. I guess it's par for the course in traditional reactive approaches, but after using React this feels like a step back. Why does everything need to be wrapped? Why can't we use the top level function body for its purpose?

That's what we're hoping to solve. Write plain logic, write it at the top level, and let the compiler figure out how to group it. Is this doable? We don't know yet whether our approach is completely solid, but we hope to share an update soon.

Thread Thread
 
dan_abramov profile image
Dan Abramov

Regarding Hooks, note React doesn't place restrictions on your rendering logic. The restriction is on calling React built-in functions. Although in principle, if we were compiling code, we could remove this restriction, I don't know if it makes sense to. Say you want to useState inside an if. What is this supposed to mean? Does it mean this state gets reset whenever the condition flips back to false? Or does it mean that the state persists? It's a bit similar to saying you want to conditionally define a method on a class. Even if you could, it's just not clear what you actually mean by that. The rules of Hooks are restrictive but in most cases I'd say they help do the right thing and have clear semantics (the lifetime is always tied to the surrounding component). And importantly, they don't tell you how to write your code — everything after can have arbitrary control flow.

Thread Thread
 
ryansolid profile image
Ryan Carniato • Edited

Right. My last example was just showing the bail out so to speak. This is how a lot React ports look like at first. I just wanted to show you could do things both ways. You return all the fields you need and you sacrifice some granular execution so it works. You could also nest Memos and not lose granularity too.

And yes once you go to useMemo breaking it out we are in the same boat. But what I'm getting at is if you start by writing things as if they are useMemo (whether they memo or not) I'm not sure how much you are bothered by this. I suppose there might be some duplication. I don't think it fundamentally impacts correctness if you are thinking in data. It definitely pushes you towards writing data as being derived at which point being a function or not is sort of whatever.

And like there are other things that you aren't worried about. Because like things like useCallback etc don't exist. Things like memoizing functions are much less common. Like communication between Effects and stable references ...memoizing components, these are all not concepts. Instead you have a model where you feel like you control and know exactly what updates. I'm sure we could pick out more slightly more awkward scenarios for each but to what end.

I don't really agree this is a clear step backwards. But my React experience on the job is much more limited than yours. I wrote and supported a React Native app for 3 years, and only have about 1 year experience doing React on the web(same company), doing a full rewrite of an private social media application (like Instagram for schools). I am not unfamiliar with developing React apps.

When my team moved from Knockout to React Hooks they were really confused. They did a lot of things wrong. They thought they were guarding executions and were getting re-renders. They figured it out but the overall sentiment was, Hooks weren't what I was expecting. They are fine. I thought we'd get some big win here, but maybe we should have just given Solid a try (this was 4 years ago so I didn't recommend we use Solid yet). So to me it is very much a matter of perspective.


Aside I have no issue with Hooks I think their design makes sense. I've never felt Hook rules were restrictive other than not being able to nest them within themselves. I think stale closures are confusing as complexity grows, ie.. if you need to useRef for something other than a DOM node you've hit a point that goes beyond where most comfort is. I only mentioned the rules from the perspective that our useMemo examples would be nearly identical. Unless we are doing some fancy stuff.

Thread Thread
 
dan_abramov profile image
Dan Abramov

Hooks weren't what I was expecting.

Yeah that’s pretty interesting. I haven’t thought about it from this perspective. To me Hooks are very functional in the classical sense. They’re a universe apart from Knockout style, so I was surprised by your mention of trading away the pure model. Hooks do not trade it away, they’re the clearest representation of that model. But I can see now how superficially they might look like some Knockout-style code. That might explain why people sometimes really struggle with them. Guess that’s similar to Solid code looking deceptively Reacty — when it’s really not. That’s a useful insight.

Thread Thread
 
trusktr profile image
Joe Pea • Edited

The examples above are very very simple. They are complete beginner examples, and don't really show where things get either a lot more complex, or way simpler. When you really use both React and Solid, then you'll see which is simpler as app requirements grow.

Here's just one simple example with pure Solid that I challenge anyone to write in pure React without importing additional libraries and with the same simplicity:

import {createSignal, createEffect} from `solid-js`

const [n, setN] = createSignal(0)

setInterval(() => setN(n() + 1), 500)

function One() {
  return <div>value in One: {n()}</div>
}

function Two() {
  return <div>value in Two: {n() * 2}</div>
}

// DOM!
document.body.append(<One />)
document.body.append(<Two />)
Enter fullscreen mode Exit fullscreen mode

Solid playground example

Example on CodePen with no build tools:

Thread Thread
 
karl_okeeffe profile image
Karl O'Keeffe

This was an interesting challenge, as I could see lots of ways of building this in React depending on which parts of the above code were considered critical.

The most natural way I would write it is:

const One = ({count}) => {
  return <div>value in One: {count}</div>
}

const Two = ({count}) => {
  return <div>value in Two: {count * 2}</div>
}

const App = () => {
  const [count, setCount] = useState(0);
  useEffect(() => {
    setInterval(() => {
      setCount(count => count + 1);
    }, 500)
  }, []);

  return(
    <>
      <One count={count} />
      <Two count={count} />
    </>
  );
}

ReactDOM.render(<App />, document.getElementById("app"));
Enter fullscreen mode Exit fullscreen mode

codepen.io/karlokeeffe/pen/vYzxPEX

The big difference with the above Solid code is that this moves the state handling into a top level React component so that React will re-render our components when the state changes.

We also need to wrap the setInterval call in a useEffect in order to kick off the interval from a React component.

Thread Thread
 
tomsherman profile image
Tom Sherman • Edited

You don't technically even need the top level App component or the state...

const One = ({count}) => {
  return <div>value in One: {count}</div>
}

const Two = ({count}) => {
  return <div>value in Two: {count * 2}</div>
}

const root = React.createRoot(document.getElementById("app"));
let count = 0;

setInterval(() => {
  count++;
  root.render(
    <>
      <One count={count} />
      <Two count={count} />
    </>,
  );
}, 500);
Enter fullscreen mode Exit fullscreen mode
Thread Thread
 
akmjenkins profile image
Adam

This thread is crazy.

Impure solidjs components being rebuilt using pure react components.

Let's turn this into a different challenge: write the code and the tests for each of these in solid and react

Thread Thread
 
ninjin profile image
Jin • Edited
const [n, setN] = createSignal(0)
setInterval(() => setN(n() + 1), 500)
Enter fullscreen mode Exit fullscreen mode

This code looks very simple and concise. But this is incorrect code, since the timer running will never stop. It's a time bomb. If you write this code correctly with the timer reset when removing the component, then the code will turn out to be not so concise at all. And I am silent about the fact that this code ticks not 2 times per second, but at unpredictable intervals of time, which introduces a progressive systematic error. To avoid this, you need to measure the time that has elapsed since the timer started.

In $moll there is a special store ticking with a given accuracy for this. Example:

const start = Date.now()

const { elapsed, run } = $mol_wire_let({
    elapsed: ()=> $mol_state_time.now( 1e3 ) - start,
    run: ()=> console.log( 'Elapsed: ', elapsed() / 1e3 ),
})

run()
setTimeout( ()=> run.atom.destructor(), 1e4 )
Enter fullscreen mode Exit fullscreen mode

Collapse
 
jdjdndndn profile image
nice

Don’t mind. I’ve worked at Meta and I can count on one hand the number of times I saw people using let in component rendering code (not inside a callback). I always request a refactor during code reviews when I see it. Dan was just using contrived examples to make his point.

Collapse
 
thdxr profile image
Dax Raad • Edited

FWIW even if I was using React and someone submitted that code, I'd tell them to restructure to make it look more like the Solid verison

It was extremely hard for me to understand what the nested if statement mutating variables outside of it was doing - it's the type of thing I always try to avoid unless I'm forced into - which is pretty rare.

So you can view Solid forcing you to regroup things as a problem - or you can view it as a feature that teaches you how to write more readable code. And if you added a memo const hasMany = createMemo(() => videos.length > 0) you'd be making it more readable and performant at the same time

The trade-off being expressed here is in React's model all styles of programming make sense given the render function approach. And in the Solid world a more specific style is required (one that I think is better in the majority of situations)

Collapse
 
dan_abramov profile image
Dan Abramov

Both React and Solid limit what people can do, and to the extent that we like other things about both respective designs, we’ll probably both find a way to rationalize it. I do find it difficult to believe that someone might start designing with "I want features like local variables or conditional local mutation not to work". Yes, it's tolerable and arguments could be made about how that's better code anyway (unless it's one of those cases where it's actually super annoying because you have to restructure a bunch of stuff and can't use the tools already built into the language). But one could of course make a similar argument about Hooks being conditional, or gotchas related to Effects. And we'd say, well, yeah this is confusing, but it forces you to express the intent more clearly, or say to confront some edge cases early. And so, with some practice, it leads to better code too. The thing about better is it's pretty subjective. That's the fun of it, I guess.

Thread Thread
 
jeremyong profile image
Jeremy Ong

I think what is swaying me so far is not necessarily the ergonomics, but efficiency. Intuitively, React to me feels like an "immediate mode" UI, where each time a node in the tree needs to re-render, all children process their hooks to determine what effects and derivations need to reprocess. Solid's approach on the other hand resembles "retained" UI architectures, where a component function describes data dependencies and returns a node to add to a tree. In fact, the usage of the VDOM in React makes sense in this light, since most immediate rendering architectures also rely on shadow state for efficiency. Both approaches are valid, to be sure, with different sets of tradeoffs, but as mentioned, I gravitate a bit more to the Solid approach to doing things. My personal bias is likely my experience as a graphics programmer operating in a primarily C++ world though.

Collapse
 
ninjin profile image
Jin • Edited

React approach: let's write imperative code, calling it pure functions, which are actually not pure at all, since we will use hooks to store state and emulate object decomposition, without mentioning the word "class", and by default we will have a bunch of re-renderers for every sneeze, launching all these imperative completeness calculations to form a new tree of objects, which we will compare deeply with the previous version to find out what needs to change one text node in the corner of the screen, and let developers from all over the world struggle with these re-renders by placing even more hooks, providing them with manually placed dependencies, and wonder why re-renders still happen when they don't need to, but now they don't happen, the code needs to.

The Solid approach: Let's just write reactive invariants and not think about dependencies, re-renders, optimizations, but still get an effective update of the state graph, but for each of the 100 state changes per animation frame, we will synchronously update the entire application in general, even if some states are not needed for rendering now, and the results of intermediate the renderer will not be able to physically see the renderers.

Well, I don't even know who to choose.. Don't you want to think about a declarative description of the composition of components and data flows between them, without manual state managing?

$my_hello $mol_list rows /
    <= Greeting_input $mol_textarea
        hint @ \Greeting
        value? <=> greeting? \Hi, Dan
    <= Greeting_output $mol_text
        text <= greeting
Enter fullscreen mode Exit fullscreen mode

This is the full application code (see in the beta of sandbox which build UI through reflect components declarations), self - sufficient, but controlled, with l10n, detailed style/behavior/composition customizations, error boundaries everywhere, suspense, automatic waiting indicators, a lot of extension points/slots/props, inversion of control, tested dependencies, virtual rendering etc. In 6 lines of code! Yes, there is a two-way data flow. And no, it doesn't cause any problems at all, since the state is stored only in one place and is used through delegates, and not copied to a bunch of props and states.

Here I was telling how to fix most of the problems of React and JSX, if I was suddenly forced to develop on this, but no one heard me. Fortunately, I don't need to mimic HTML in order to use and customize the components as my heart desires.

Guys, developers from all over the world are listening to you. Why don't you move the industry somewhere in this direction, instead competing in how to write trivial logic in the most non-trivial way and condemning them to write dozens of times more code in real applications?

Collapse
 
Sloan, the sloth mascot
Comment deleted
 
ninjin profile image
Jin

Your answer sounds like the words of a fool. Is that on purpose?
Here you can compare different syntaxes.

Collapse
 
fsidev profile image
SF

The more imperative code we write, the more jobs we create) Hey, why don't you take a walk down the road to nowhere, buddy))

Thread Thread
 
ninjin profile image
Jin

Jobs will not disappear anywhere, but work tasks will become much more interesting. Instead of spending 3 months screwing the spinner (the real story from GitLab), you could at the same time completely rewrite the output of the commit code so that it does not slow down.

Collapse
 
aquaductape profile image
Caleb Taylor • Edited

I fell in love with React because you write beautiful, simple, clear, declarative code. However once your webapp grows you start using memos, bringing state as close to children as possible, useCallback, aka restructuring your code, I wish I didn't have to resort to that.

Collapse
 
beeplin profile image
Beep LIN

I wonder if I am using some different kind of React than Dan is :)))

"The user doesn't care whether it's initialization or update. " --So why am I keeping thinking of "should I need useCallback here?"

"That's what I meant by React not "missing" updates. React doesn't let you write rendering logic that leaves initialization and updates out of sync. " --So I have to fill the annoying dep arrays again and again to achieve this.

;(((

Collapse
 
jiyinyiyong profile image
题叶

"referential transparency" that functional programming languages respect.

Collapse
 
brucou profile image
brucou • Edited

I feel like a lot of people do not really accurately get this signals thing at a conceptual level. Let me allow myself to make an attempt at explaining how I see things in case others may find it helpful. I am going to use a basic DSL with only three constructs:

  1. event -> reaction (reaction)
  2. variable <- expression (assignment)
  3. variable = expression (abstraction)

Taking your simple counter example, in the end, the specs we are implementing are those:

route change -> (render counter at 0, count <- 0)
button click -> (count <- count + 1, render counter at count)
Enter fullscreen mode Exit fullscreen mode

In this simple example, there is no need to use syntax 3 but you could imagine something where doubleCount = 2 * count for instance.

Here:

  • render counter at 0 is document.createElement... etc.
  • render counter at count is button.textContent = count, etc

The specifications for a reactive system, category to which web apps belong, are the set of events that the app must react to, mapped to the corresponding reactions. As such syntax 1 is necessary. Because applications are stateful in general (i.e., the same event can trigger different reactions), syntax 2. is necessary to express the state of the application on which the reaction depend. Syntax 3 is optional (thanks to referential equality you could inline the variable everywhere) but is nice to have. I call it abstraction because the details (the expression) are abstracted behind the variable name. This is essentially the same as lambda abstraction.

These are the specs of the counter and they are independent from any implementation or framework or else.

Moving to the implementation realm, you need to find ways to implement event binding, do assignments, and perform effects (here just rendering). You can do that manually like a boss, or you can do through a library, or you can do it through a framework. As long as somehow you arrive to implement render count with sth of equivalent effect to button.textContent = count, your implementation will be correct. Whether your framework/library/brain arrives at that conclusion through diffing vDOM nodes, or through reactive proxies, or because you wrote it directly this way, it is all the same. Whether you used mutable state, proxies, or went to the moon and back to reach that conclusion is not relevant.

Except that it is. But not to the functional requirements of the application. It is relevant because the accidental complexity attached to the solution that you apply impacts the non-functional requirements (performance, efficiency, maintainability, etc.) and constraints ( time, people skills, budget etc.) of your project. Architects will recognize this pattern of thinking as they commonly understand an application as a set of those three things (functional reqs, non-functional reqs, and constraints).

VanillaJS works, but it can get tiring and error prone on large application to create and update all these nodes by hand. And that's just the first write. Maintain and evolve that is also going be a challenge. You can still write large apps in JS, but you are going to need solid design and modularization skills (or/and a good UI component library). It is not all impossible and once you have your patterns straight, it is refreshingly simple. (btw, example of vanillaJS app from Adobe leonardocolor.io/#)

Nonetheless, you may also realize that writing the code for the rendering work is repetitive and you may prefer instead to let the computer figure out for you the DOM updates to perform. vDOM diffing is a way to do that. Template languages (.vue, .svelte, etc.) are another way. Template literals yet another one and there are still more ways, as we know.

All those solve the problem of you having to write a ton of DOM update codes manually. As we discussed accidental complexity, while they solve the repetitiveness and boilerplate problem, they may also create others. So Angular 1 default change detection was to check everything for change after the occurrence of each event. That works but it can get really slow, as we observed. React's default is to rerender everything (I mean the whole component being rendered) and then do a diff with its previous version. That absolutely works but has also some inefficiencies attached to it. Template languages allow mapping easily a source of change to a DOM update operation. But then those languages are not good at expressing arbitrarily changing templates.

There would be more to say about the modularization story of each pattern. I want better to emphasize that the bad memories that folks have of two-way data binding may relate to two-way data binding being done wrong. Two-way data binding wants to implement sth like x = y with semantics like (x updates -> update y, y updates-> update x). There is an obvious circularity here that has caused problems in many implementations. Unidirectional dataflow avoid the issues of having to implement the 2-way pattern without falling into the traps by removing the circularity from the get-go. With that said, Vue and Svelte both have two-way data binding that works like a charm.

Oh I almost forgot to explain signals:

1. event    -> reaction         (reaction)
2. variable <- expression    (assignment)
3. variable = expression     (abstraction)
Enter fullscreen mode Exit fullscreen mode
  1. is createEffect (in Solid)
  2. is, well the setter/getter parts (setCount, count() in Solid)
  3. is for derived state (createMemo in Solid)
Collapse
 
mfp22 profile image
Mike Pearson • Edited

This might irritate React developers, but it's obvious you're right. The benefit of React was completely about sane code structure, and in the end that structure became identical to reactivity under certain rules. So... How about just reactivity with those rules? But I guess we'll watch as the industry debates whether React was 90% right or 100% right. Kind of boring tbh.

But it seems to me like the emphasis on unidirectionality is about making it easy to answer the question, "Why did this value change?" This is why I have stopped caring about read/write segregation as an RxJS user. In actually fully reactive programming, the answer is that the value changed because the stream it directly referenced emitted a new value. There's nothing to " track down". In CycleJS this extends all the way up to event sources. But even in a Flux-like architecture where user events result in initial callback functions but those functions only contain a single imperative command (dispatch or next for RxJS), it's also not hard at all to find the source of changes. It's only when you mix reactivity with MVC that things become extremely chaotic.

By MVC I mean MV-Controller, where controllers are functions that control state as side-effects. So basically 90% of React components. So do we secretly love MVC? I don't, but devs with strong imperative habits may prefer an "imperative feel" for updating state. But it's not clear to me that it's actually easier. When updating a counter, why should the first instinct be to write a callback function to imperatively update count state that lives elsewhere? Why isn't it to update the count state to react to the click event?

If you listen carefully to the original Flux presentation, the point of unidirectionality was to avoid infinite loops, but more importantly colocate control logic and the state it's defining. Declarative code. Reactive code.

So if you have actual full reactivity, unidirectionality is a strange and redundant goal.

Collapse
 
ryansolid profile image
Ryan Carniato

Agreed. Signals systems even are generally a DAG. In Hindsight, I probably should have put that in the article. Maybe it isn't too late.

Collapse
 
redbar0n profile image
Magne

This reminds me of React's relation to MVC. stackoverflow.com/a/65969849/380607

Here's the gist of the headlines from that answer:

React is not considered to be MVC. Since Facebook inaccurately contrasted Flux with MVC.

Flux is not in direct opposition to MVC per se, but in opposition to mutability and two-way data-binding, something which MVC does not specify (but which people typically allowed when using MVC).

Stateless MVC also didn't fit on the client-side.

But React is MVC, in some sense. Components are ViewControllers, typically. Components as Views, Hooks as Controllers.

Alternatively, you could treat Components as pure Views (as originally intended?), and have a separate Model-Controller:
github.com/statelyai/xstate/discus...

Collapse
 
mfp22 profile image
Mike Pearson

Sounds like you're copying my own writing back to me but with strange twists
Image description

Controllers "control" things, so it's the event handlers with imperative state-controlling statements that are the controllers.

Thread Thread
 
redbar0n profile image
Magne

Indeed, and the event handlers could be modeled in XState, like in my gist. It’s basically vertically sliced MVC-widgets, instead of having MVC as a horizontal layer based on technology.

Collapse
 
drsensor profile image
૮༼⚆︿⚆༽つ

Just imagining the future, I think the next step of signal would be Atomic Signal where it can propagate effects across worker/thread 🤔 (akin to Go channel + reactive variable)

Collapse
 
davedbase profile image
David Di Biase

Hah! I started working on this. The question ultimately is: why would it be valuable in JS? The cost of sending between worker/thread in the browser isn't worth it.

Collapse
 
drsensor profile image
૮༼⚆︿⚆༽つ • Edited

Not if you use SharedArrayBuffer and model every instance of reactive variable as StructOfArray. The crazy part is on when to use the mutex guard 😂

Atomic Signal is very valuable on apps/games that utilize OffscreenCanvas. Writing both heavy rendering and logic off-main-thread is a deal breaker.

Thread Thread
 
vincentvandijck profile image
bigmistqke

Yes! I have been thinking about this a lot lately... I feel it should be possible with .notify and .wait.

Good inspiration: javascript.plainenglish.io/buildin... they use a SharedArrayBuffer with atomics only for updating indices to update a float arraybuffer.

Thread Thread
 
vincentvandijck profile image
bigmistqke

Oo waw github.com/Bnaya/objectbuffer this project is so so crazy. A proxy implementation of an object w (shared)arraybuffer under the hood. Serializes the schema somehow in the arraybuffer... Wild stuff.

Someone please slap reactivity on top of this plz.

Thread Thread
 
vincentvandijck profile image
bigmistqke
Thread Thread
 
drsensor profile image
૮༼⚆︿⚆༽つ

Oo waw github.com/Bnaya/objectbuffer this project is so so crazy. A proxy implementation of an object w (shared)arraybuffer under the hood. Serializes the schema somehow in the arraybuffer... Wild stuff.

Wait, Struct of Array backed by virtual memory (SharedArrayBuffer)!? That's wild!

Thread Thread
 
drsensor profile image
૮༼⚆︿⚆༽つ • Edited

I think I've done something similar but it just something on a whim for creative coding. (not yet using futex like Aomics.waitAsync and Atomics.notify)

fosstodon.org/@drsensor/1093552594...

Collapse
 
peerreynders profile image
peerreynders

Is called fine-grained reactivity after all and crossing the worker boundaries for fine-grained values is likely not optimal.

However something for more course-grained messages that can feed into fine-grained reactive graphs should work.

Is postMessage slow? (Surma, 2019)

Thread Thread
 
davedbase profile image
David Di Biase

Thanks for sharing this! It reinvigorates my interest in exploring the idea in future :)

Thread Thread
 
peerreynders profile image
peerreynders

If you're interested I have a collection of links here

Thread Thread
 
davedbase profile image
David Di Biase

Thanks!!!

Collapse
 
peerreynders profile image
peerreynders • Edited

but I cannot believe I am reading these words from someone who seems to have a deep understanding of this space:

Your judgement is largely informed by your perception of the impact that React had on the Web as a whole.

But the quote talks about:

  • the React team
  • UIs in general

…outside of the high-management-maturity organisations in which they were born … tight latency budgeting, dedicated performance teams, hawkish management reviews, ship gates to prevent regressions, and end-to-end measurements of critical user journeys. They understood the only way to scale JS-driven frontends are massive investments in controlling complexity…

The Market for Lemons (2023)

i.e. the React team developed a solution inside of Facebook/Meta that the organization could sustain while addressing concerns that were unique to that particular organization.

And whether or not it was deliberate from the very beginning or just a happy accident from July 2013 onwards the emergence of React Native set certain architectural decisions in stone which the web team had to accept as permanent constraints from that point on and work around.

React wasn't about the web but React was about React/Native.


Now the React Community is an entirely separate component.

Unrelated but I think this has strong parallels:

Object-oriented development is good at providing a human oriented representation of the problem in the source code, but bad at providing a machine representation of the solution. It is bad at providing a framework for creating an optimal solution…

Mapping the problem (Data-Oriented Design, 2008)

They like working with React (and the cornucopia of support packages) so much that's it's become their golden hammer—even when it's not an optimal solution to their problem or the operating environment (the web). And the effort to acquire the necessary skills with that ecosystem is an investment they seek to protect and maintain (Fire and Motion).

The problem is an industry rife with faulty thinking that assumes (a) popular technology is popular because it’s good…

The Great Gaslighting of the JavaScript Era (2023)

Collapse
 
inwerpsel profile image
Pieter Vincent • Edited

Fun fact: take a hook that uses a subscription (e.g. by directly using useSyncExternalStore), and use it as a component...

You now have a signal, written in React, with 0 external dependencies.

function useFoo() {
  return useSyncExternalStore( /* ... */);
}

function Normal() {
  const foo = useFoo();

  return foo === 'bar' ? BarComponent : OtherComponent;
}

// Temporary uppercase name to make JSX work.
const FooSignal = useFoo;
// Yes, this only needs to happen once for the whole app.
const $foo = <FooSignal />;

function WithSignals() {
  return <div>
    // ...
    <h2>{$foo}</h2>
    <p> Some other stuff that last rendered at {performance.now()}</p>
  </div>;
}
Enter fullscreen mode Exit fullscreen mode

This works with any argumentless hook that properly manages global state.

working codesandbox

Collapse
 
ivan7237d profile image
Ivan Novikov

Such an interesting discussion with Dan...

The React approach seems very attractive in a world where there is no async or all async is abstracted away with libraries like React Query, but my real-life experience has not been like this. With async in the picture, even if there is a React compiler, I can't say if it would beat the simplicity of executing top-level code once.

Collapse
 
seanblonien profile image
Sean Blonien

Totally agree React approach makes sense when you abstract away all async. So then it begs the question, when we do have awesome libraries like TanStack Query which do perfectly solve this problem, don't the annoying problems of React go away and React remains just as attractive? For me, the quirks of React are solved with that 1 library, and the simplicity of values just being values (and not functions) is just as attractive still. My real life experience is if you're not using TanStack Query for all your async code, you're not taking advantage of it enough and you could refactor to use use such hooks.

Collapse
 
lili21 profile image
li.li

I think Suspense would do that.

Collapse
 
kashanahmad profile image
Kashan Ahmad

Ngl Dan looks like a SolidPhobic in this discussion, just saying...

Collapse
 
magicspon profile image
Dave Stockley • Edited

I get the feeling that react is so far up it's own arse that it can't admit that mistakes have been made. I say that as someone who loves using react. But me oh my, solid looks soo much nicer. Once the solid eco system has grown a bit, I will 100% be switching over.

 
peerreynders profile image
peerreynders • Edited

There is a wide selection of Application Holotypes.

On top of that the web supports a broad range of interactivity:

So the apparent focus on SPAs can come across as a bit myopic.

An eighteen wheeler can be useful in some situations but that doesn't mean everyone needs one in their driveway.

Thoughtworks Technology Radar (2022 Oct)—Hold: SPA by default

Collapse
 
gaargh profile image
External • Edited

Dear @ryansolid,

How do you prevent transient "state" updates from being shown on the UI without a VDOM?

KR<

Collapse
 
ryansolid profile image
Ryan Carniato

The answer is scheduling. Even with a reactive system like Signals you schedule side effects. These systems are called "push/pull". As we render and update we push a dirty notification, this tells us what has possibly changed but we don't run any side effects until we know that its dependencies have changed. In the frame of the update we collect what could update and then afterwards we run those all together pulling out the latest values. Since we automatically track dependencies on each run we can trace the dependencies of the previous run to to know if they have in fact changed and then run the effect if necessary.

In so we get the benefit of both push based systems of not over executing things that are unrelated to the source of change, and pull based systems of not executing things that aren't currently in use (asked for).

Collapse
 
gaargh profile image
External

OK! Can you explain how this is implemented in Solid? I fail to see what's the purpose of a batch function if you provide a scheduler with such properties.

Thread Thread
 
gaargh profile image
External

@dan_abramov Your take?

Thread Thread
 
ryansolid profile image
Ryan Carniato

It was because Solid's current version batches synchronously which means we need to have the ability to wrap it so it knows to run at the end. Reactive execution (like reacting to a signals change) does this automatically but we don't currently wrap async entry points like events. This is a very rarely used API and even VDOM libraries like Inferno have the same immediate behavior in event handlers. We will autobatch into a microtask queue in these cases in the future (which is what Vue does), but it was a something I did early on imitating some of the fastest libraries.

Collapse
 
factordiceomar profile image
OmarC

So… what exactly is a “Signal”?

Collapse
 
ryansolid profile image
Ryan Carniato
Collapse
 
kasvith profile image
Kasun Vithanage

Nice write up as always Ryan

Collapse
 
xania profile image
Ibrahim ben Salah

Love the positive note at the end!

Collapse
 
akmjenkins profile image
Adam • Edited

I think the principles that both frameworks espouse matter vastly more than either syntax. Everybody's standing on the shoulders of the giants before them.

I actually find it ridiculous and sad that the author of SolidJS wrote a flamewar article, and also that thought leadership of React engaged so deeply with it (and leading up to it, from looking at the tweets).

The best code is the simplest code. React code can be simple. SolidJS code can be simple, but neither of them can be simple in the same way, because they take fundamentally different approaches. That's fine.

They can also both be monstrously complicated (we all know, we've all been in the legacy project that's been stitched together and somehow works against all sensible rules of the framework). Neither framework can completely forbid devs from doing stupid things (though both do a great job of helping out where they can).

Give it a rest.

Collapse
 
peerreynders profile image
peerreynders

We are competing with native apps.

Perhaps you are not familiar with this perspective: The App is a Technology Tiller (2014)

Apps are a popular solution because they are a familiar solution but that doesn't meant they are the optimal solution.

Collapse
 
iainsimmons profile image
Iain Simmons

Great article, and an excellent follow up to your other one about the history of signals (you mean to say you didn't invent signals?!! 😉).

I sometimes wonder, especially after watching that React documentary, if Jordan Walke also took inspiration from earlier forms of reactivity, but that we just hear less and less of his voice and original vision for React as time goes on.

At the cost of making things even spicier, sometimes the React evangelism reminds me of religions claiming their God is somehow the one and only true God and their beliefs the only way to worship, when in fact we're all worshipping the same God in our own way.

And just like any new religion or a resurgence of an old one in a place with one dominant religion, those in power and the majority can feel like their beliefs are threatened.

Anyways, while part of me worries about things being said, it's kind of reassuring that signals and fine-grained reactivity are getting more attention, because it means they do in fact deserve attention. It's no longer something people can just ignore or dismiss.

Collapse
 
noah_shipley_d1fbde42c25b profile image
Noah Shipley • Edited

Here's a mobx version. Note that if the heading result doesn't change (even though the video list count DID change) we still won't rerender unless we have an actual new computed value:

type VideoListProps = {
    videos: Array<any>
    emptyHeading: string
}
class VideoListStore {
    constructor(public props: VideoListProps) {}

    get count() { return this.props.videos.length}
    @computed get heading() {
        const {count, props: {emptyHeading}} = this
        return count > 0 ? `${count} ${count > 1 ? 'Videos' : 'Video'}`
            : emptyHeading
    }

    @observable somethingElse = 42
}
const VideoList = observer<MyProps>((props) => {
    const {heading, somethingElse} = useLocalStore(() => new VideoListStore(props))

    return (
        <>
            <h1>{heading}</h1>
            <h2>{somethingElse}</h2>
        </>
    )
})
Enter fullscreen mode Exit fullscreen mode
Collapse
 
lovetingyuan profile image
tingyuan

vuejs version:

<template>
  <h1>{{ heading }}</h1>
</template>

<script setup lang="ts">
import { computed } from 'vue'
const props = defineProps<{
  videos: {}[]
  emptyHeading: string
}>()
const heading = computed(() => {
  const count = props.videos.length
  if (count > 0) {
    const noun = count > 1 ? 'Videos' : 'Video'
    return count + ' ' + noun
  }
  return props.emptyHeading
})
</script>
Enter fullscreen mode Exit fullscreen mode
Collapse
 
lovetingyuan profile image
tingyuan

The variable "heading" is derived (calculated) and logically should have been so from the beginning. This allows developers to have an intuitive understanding of the data source (all related logic is aggregated within one method). Compared to writing it directly in the outer layer, wrapping it in a function has its own advantages, and of course, the most important is its natural responsiveness without any additional cognitive burden.

Collapse
 
chadsteele profile image
Chad Steele

SolidJS rocks... React, like Java, .NET, etc. is suffocating under it's own obesity. Yes, there's still flat earthers and React devotees. I'll never understand why people will choose dogma over liberty, but it happens over and over again.
sigh.
If you haven't already, try solid. If you run a company, switch. Now. It'll save you $

Collapse
 
beeplin profile image
Beep LIN

Image description

Image description

This confusion is mainly caused by the compile-time tricks, saving developer from writing () => repeatly. But personally I prefer more verbose while more "reasonable" way, that is, the original js way, with less compiler tricks:

function OneWrong(props) {
  const doubled = props.count * 2;
  return <div>Count: {() => doubled}</div> // wrong position for `() =>`
}

function OneRight(props) {
  const doubled = () => props.count * 2; // right position for `() =>`
  return <div>Count: {doubled}</div>
}

function Two(props) {
  return <div>Count: {() => props.count * 2}</div>
}
Enter fullscreen mode Exit fullscreen mode

So every javascript developer knows what's happening here. Basically all reactive holes in JSX should be lazy-evaluted functions.

Collapse
 
shriji profile image
Shriji

Image description

Collapse
 
0okba3 profile image
Ook Baar • Edited

I'm quite new at web development, (less than 2 two years, no job experience yet); I come from an audio-visual design background among other things I've done, and I choose to continue my career path by expanding it to the interactivity that internet gives. And yes, it's powerful.

So at some point people who knows told me that React was the real thing. The big community support, and everything you already know by memory. So I began to study React. First thing I noticed: third party boilerplates: create react app, it was like everything was settle down to work with that because that Webpack thing was a pain if you wanted to configure it by yourself. I got it. A couple of month later I noticed that when I needed to use some packages it just didn't work, and my fellows learners had errors everywhere, of course we have Vite already to save us... Then I was told I needed to learn Class components because, while that was not the actual way React recommends to write code, there may be many companies that still use Class components etc. So I learnt Class Components, and Functional Components, and the Hooks stuff. But it seemed that the applications could run wild anyway if they got bigger and bigger, and I was told that for those cases Redux was useful. So while I was learning Redux and React-Redux suddenly things didn't worked. Again. Because it seemed there was a better way to write Redux now and it was Redux Toolkit and well, I went through Redux Toolkit. Some functionalities look kind of odd in relation with some extremely important principles that should never be broken because really important reasons support them -and best practices too-, like: don't mutate the state. Don't ever use the .push method (I heard that like several thousand times in a few months), but there was the push method again because of some thing that I don't really know, but somehow I could tell that it was abstracting everything at a level that someone who comes from zero probably won't actually ever know what's going on inside those functions and etc. It was cool anyway, declarative code is cool. Then, or in the meanwhile - it doesn't matter anymore- there was something wrong about rendering your app from the client side, so suddenly I was reading everywhere the server side rendering stuff. And Next.js. Well, as I was moving ahead (because when I began to learn web development there was JQuery -dying-, React (rules), Vue.js and Angular (there were probably others but you know what I mean), so as I was saying while I was learning React it seemed developers found the need to build some other frameworks too, Svelte (I was told is really good), there was something called Preact, and I didn't try it but it's name is funny (I can't wait for some other one, some Proto-React-Post-Preact and Wherever.js framework constructed over it, it will be really fun, yeah...).

So, signals are called? Yes, it sounds like something awesome that no real web developer who takes his job seriously should avoid. I'm sure Signals rules.

Well, now I realized that all that javascript that seemed so so scary the first days, makes me fell really really sure nowadays. It's weird, because I thought that the point of React was to make JavaScript easier to write. But I'm probably wrong because, as I said, I'm quite new at web development.

Collapse
 
sentinelaeux profile image
sentinelae

Look at all the delusional people here, blinded by their own weaknesses, trying to use hype and corporate propaganda as a guide to their decisions. Dan Abramov is an authority with golden handcuff$, forced by himself to believe in the lie$ he was told. The very premise he uses to justify React is completely false and broken from the start. React is a problem sold as solution to dispute web dev market share, no more than this. Every attempt to fix the insanity that it brings is a failure from the start, as it is not really meant to solve anything. Whatever React does is to bring more confusion, more reinvention-of-the-wheel, more inefficiencies, more infinite pointless discussions to solve trivial problems that are not real problems outside React, and worse experiences overall. The whole "reactive" concept is pure propaganda, as it was never better than vanilla JS. The other frameworks are just as useless as React, they don't solve anything new, have the same problems, and keep shoving whatever the propaganda suceeds to sell to naive devs. If you really want to suceed in web dev, just realize how awful React is, then learn proper JS, CSS and HTML well, and you'll be able to take over the world. React/JSX/reactivity is complete bullshit.

Collapse
 
ionshard profile image
Victor Ling • Edited

This discussion is super interesting to me because I didn't learn React. I started out using Reagent which is a Clojurescript wrapper around React. Then when I eventually started using React itself I found it lacking. Now reading this discussion on Solid it looks like an attempt to do what Reagent did from the start but a little more wrong.

To start Reagent has the same concept as signals through Reactive Atoms (or ratoms for short) which you can dereference to get the current value and when you derefernce an ratom, the component you are rendering gets subscribed to that ratom for updates and will re-render anytime the value in the ratom changes.

However, one thing I think Reagent does right that both React and Solid seem to do wrong is in Reagent you can define a component as just a function that returns an element and this becomes the component's render function. This makes it work just like React currently, it gets called each time the component updates and rerenders the component from the output.

You can ALSO instead of defining a component straight from a render function. You can create a component from a function that returns a render function. This makes Reagent work the way that Solid seems to be proposing but the interface between initial render and subsequent renders is clear as day because it's using native language features you can clearly see the delineation between what is called when. You can do any initialization that should only run once in the outer function and then with a closure you can use that information inside the render function.

To use Dan Abramov's example in a mock Reagent way it would be thusly:

function VideoList({ videos, emptyHeading }) {
  let somethingElse = 42; // can add something here

  return function() {
    const count = videos.length; // Assuming accessing videos here creates an update subscription the way Solid does
    let heading = emptyHeading;

    if (count > 0) {
      const noun = count > 1 ? 'Videos' : 'Video';
      heading = count + ' ' + noun;
      somethingElse = someOtherStuff(); // can add something here too
    }
    return (
      <>
        <h1>{heading}</h1>
        <h2>{somethingElse}</h2>
      </>
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

Any access of data in the outer function only runs on initialization, any data in the inner function creates a subscription to re-run the inner render function only when the data changes. Instead of it needing a linter so you can catch where this invisible "this won't re-run" boundary is, it's clear as day via a function closure.

UPDATE: Looking into SolidJS more, it looks like the second example is exactly what it does but via "compiler magic" so I assume that is where the confusion seems to stem from. You need to know where the compiler magic happens rather than being explicit.

Collapse
 
kashanahmad profile image
Kashan Ahmad

This, I've recently gotten into the world of animations and GSAP and it's been hell to use React. Now I need to make myself think twice - When to re-trigger an animation and when to stop react from re-triggering it cuz it wants to.

Collapse
 
sylwesterdigital profile image
Sylwester Mielniczuk

As far as I know Facebook React is not web standard (W3C) and never will be. People devoted brain to this web nightmare are total lunatics.

Collapse
 
gabrielhangor profile image
Gabriel Khabibulin

Just use Vue ffs

Collapse
 
jakubkeller profile image
Jakub Keller

Unrelated.

Collapse
 
devvsakib profile image
Sakib Ahmed

++

Collapse
 
dawoodkarim profile image
dawoodkarim

Thank you for the tips!