preactjs / signals

Manage state with style in every framework
https://preactjs.com/blog/introducing-signals/
MIT License
3.78k stars 93 forks source link

signals/react: React components rendered as child routes with React Router don't update when using `signal.value` #273

Open cafreeman opened 1 year ago

cafreeman commented 1 year ago

Hi there,

I've recently been experimenting with using Signals in React and have discovered a strange behavior when using it in concert with React Router. It seems that when you render a component in a React Router <Outlet> (as a child route), there's something about the interaction between the Outlet and signals that causes the component itself to not actually update.

However, this can be fixed by only referring the signal itself (e.g. <div>{signal}</div> as opposed to <div>{signal.value}</div>. If you do that, the component suddenly behaves as expected.

I've created a repo with a small reproduction here: https://github.com/cafreeman/signals-react-weirdness

Please let me know if there's any other information I can provide.

Thanks!

cafreeman commented 1 year ago

Update: this appears to only happen when using the automatic JSX runtime. If I instead use the classic runtime, the component behaves as expected.

This might also be related to https://github.com/preactjs/signals/issues/269

jesseagleboy commented 1 year ago

I'm happy to see someone else posted about this. I'm using signals within React Router and either the HMR will crash my page or within a sandbox demo I did, just importing signals into a file will give an error. I've tested importing signals in React non-React-Router projects and they work just fine. I have no clue what Signals does to the React component that alters how React Router handles the routing.

image

Live Testing

cedeber commented 1 year ago

Switching to the JSX "classic" mode didn't help for me.

theverything commented 1 year ago

react-router throws an error if @preact/signals-react is imported to the same project. This is because the preact lib wraps components in a Proxy. When react-router calls this code https://github.com/remix-run/react-router/blob/main/packages/react-router/lib/components.tsx#L572 it errors because the Router type doesn't match the Proxy type.

harshithr commented 1 year ago

hello, even i am facing same issue as above, -- Uncaught Error: [Route] is not a component --

So is there any workaround to resolve this issue?

DmnChzl commented 1 year ago

Hi 👋 I had this same issue with version 1.2.X of @preact/signals-react:

Uncaught Error: [Route] is not a <Route> component. All component children of <Routes> must be a <Route> or <React.Fragment>
    at invariant (history.ts:480:11)
    at components.tsx:593:5
    at react.development.js:1195:17
    at react.development.js:1158:17
    at mapIntoArray (react.development.js:1049:23)
    at mapIntoArray (react.development.js:1099:23)
    at mapChildren (react.development.js:1157:3)
    at Object.forEachChildren [as forEach] (react.development.js:1194:3)
    at createRoutesFromChildren (components.tsx:575:18)
    at Routes (components.tsx:413:20)

But, it seems fixed in version 1.3.X. No more problem using signals, computed or even effect despite the use of a routing (react-router-dom@6.11.0). My code:

import { computed, effect, signal } from "@preact/signals-react";
import ReactDOM from "react-dom/client";
import { BrowserRouter, Link, Navigate, Route, Routes } from "react-router-dom";

const counter = signal(0);
const isEven = computed(() => counter.value % 2 === 0);

effect(() => {
  console.log(isEven.value ? "Even" : "Odd");
});

const increment = () => (counter.value += 1);
const decrement = () => (counter.value -= 1);

function Result() {
  return (
    <div style={{ display: "flex", flexDirection: "column" }}>
      <span>Value: {counter.value}</span>
      <Link to="/btn-grp">Go To ButtonGroup</Link>
    </div>
  );
}

function ButtonGroup() {
  return (
    <div style={{ display: "flex", flexDirection: "column" }}>
      <div style={{ display: "flex", flexDirection: "row" }}>
        <button onClick={decrement}>-1</button>
        <button onClick={() => (counter.value = 0)}>Reset</button>
        <button onClick={increment}>+1</button>
      </div>
      <Link to="/result">Go To Result</Link>
    </div>
  );
}

function App() {
  return (
    <Routes>
      <Route path="/" element={<Navigate to="/result" />} />
      <Route path="/result" element={<Result />} />
      <Route path="/btn-grp" element={<ButtonGroup />} />
    </Routes>
  );
}

const root = ReactDOM.createRoot(document.getElementById("root"));

root.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>
);

Can anyone else confirm?

aspizu commented 7 months ago

This might be related to the issue I am having, I too am using React router. If i use a mySignal.value created using the signal() function, exported from a module directly inside my component then my component will re-render on signal updates. But, if i call a function which eventually uses mySignal.value, it won't update on changes. It only works if the signal value was directly used.

XantreDev commented 7 months ago

@JoviDeCroock Probably it's not actual too, probably it's related with fact only some of high order component have been patched in previous implementation