signals icon indicating copy to clipboard operation
signals copied to clipboard

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

Open cafreeman opened this issue 2 years ago • 8 comments

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 avatar Nov 10 '22 19:11 cafreeman

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

cafreeman avatar Nov 14 '22 20:11 cafreeman

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

jesseagleboy avatar Nov 16 '22 17:11 jesseagleboy

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

cedeber avatar Nov 22 '22 07:11 cedeber

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.

theverything avatar Feb 11 '23 03:02 theverything

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

So is there any workaround to resolve this issue?

harshithr avatar Feb 25 '23 17:02 harshithr

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 ([email protected]). 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?

DmnChzl avatar May 24 '23 07:05 DmnChzl

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.

aspizu avatar Apr 03 '24 11:04 aspizu

@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

XantreDev avatar Apr 03 '24 20:04 XantreDev