christianalfoni / impact

Reactive React
https://impact-react.dev
MIT License
113 stars 6 forks source link

Typescript and library issues #341

Open coredevel opened 1 month ago

coredevel commented 1 month ago

Hey Christian,

Just found some errors when trying to follow the docs to get a basic store setup:

Screenshot from 2024-10-15 17-16-29

I have the following deps installed:

    "@impact-react/signals": "^1.2.1",

The ts error is Cannot find module '@impact-react/signals' or its corresponding type declarations.ts(2307)

The project's tsconfig is standard next OOB and this issue doesn't arise with other packages being used.

{
  "compilerOptions": {
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "bundler",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "plugins": [
      {
        "name": "next"
      }
    ],
    "paths": {
      "@/*": ["./src/*"]
    }
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
  "exclude": ["node_modules"]
}

Also in the docs I see the following:

import { configureStore } from "@impact-react/store";
import { signal } from "./signal";

export const createStore = configureStore((propValue) => {
  const [value, setValue] = signal(propValue);

  return {
    get() {
      return value();
    },
    set(newPropValue) {
      setValue(newPropValue);
    },
  };
});

I'm assuming the import {signal} is supposed to come from impact-react/signals and not ./signals? Could you elaborate on this some more as I'd like to initialise a store with a prop value and watch this when it changes too. There are some other typescript issues but I'll come on to these later.

Thanks

coredevel commented 1 month ago

Ignore the type issue, I took a step back and on returning it seems to have resolved itself, very weird as I didn't touch anything and after earlier trying all kinds of workarounds / variations to no avail.

If you could elaborate on the second item though that would be appreciated, I was running into a TS issue when trying to create or configure a store with a fn that accepted prop params. I'll mock up a quick example and send through in a mo.

coredevel commented 1 month ago

OK another issue, this has been a painful experience trying to get this up and running today:

TypeError: (0 , react__WEBPACK_IMPORTED_MODULE_0__.createContext) is not a function
    at eval (webpack-internal:///(rsc)/./node_modules/@impact-react/store/dist/esm/index.js:176:74)
    at (rsc)/./node_modules/@impact-react/store/dist/esm/index.js (/app/.next/server/vendor-chunks/@impact-react.js:120:1)
    at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:43)
    at eval (webpack-internal:///(rsc)/./node_modules/@impact-react/signals/dist/esm/store.js:6:77)
    at (rsc)/./node_modules/@impact-react/signals/dist/esm/store.js (/app/.next/server/vendor-chunks/@impact-react.js:110:1)
    at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:43)
    at eval (webpack-internal:///(rsc)/./node_modules/@impact-react/signals/dist/esm/index.js:20:64)
    at (rsc)/./node_modules/@impact-react/signals/dist/esm/index.js (/app/.next/server/vendor-chunks/@impact-react.js:60:1)
    at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:43)
    at eval (webpack-internal:///(rsc)/./src/components/StrategyBuilder/stores/StrategyBuilderStore.tsx:5:79)
    at (rsc)/./src/components/StrategyBuilder/stores/StrategyBuilderStore.tsx (/app/.next/server/app/strategy-builder/page.js:689:1)
    at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:43)
    at eval (webpack-internal:///(rsc)/./src/app/strategy-builder/page.tsx:7:113)
    at (rsc)/./src/app/strategy-builder/page.tsx (/app/.next/server/app/strategy-builder/page.js:678:1)
    at Function.__webpack_require__ (/app/.next/server/webpack-runtime.js:33:43)
    at runNextTicks (node:internal/process/task_queues:60:5)
    at listOnTimeout (node:internal/timers:545:9)
    at process.processTimers (node:internal/timers:519:7)
    at async e9 (/app/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:35:396530)
    at async tb (/app/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:35:400044)
    at async tS (/app/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:35:400605)
    at async tS (/app/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:35:400736)
    at async tS (/app/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:35:400736)
    at async tR (/app/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:36:2320)
    at async /app/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:36:2989 {
  digest: '2719363999'
}

Very simple AppStore created and simply trying to wrap a page.tsx's default func return with the provider:



export default function Page() {
  return (
    <useAppStore.Provider>
      <h1>Text</h1>
    </useAppStore.Provider>
  )
}
coredevel commented 1 month ago

Tried the below but same issue, it can't resolve create context for some reason :/

import { createStore, signal } from '@impact-react/signals'

type Props = { initialCount: number }

function CounterStore(props: Props) {
  const [initialCount, setInitialCount] = signal(props.initialCount)
  return {
    get initialCount() {
      return initialCount()
    },
    set initialCount(value: number) {
      setInitialCount(value)
    },
  }
}
const useCounterStore = createStore(CounterStore)

function Counter() {
  const counterStore = useCounterStore()

  return <div>{counterStore.initialCount}</div>
}

export default function Page() {
  return (
    <useCounterStore.Provider initialCount={10}>
      <Counter />
    </useCounterStore.Provider>
  )
}
coredevel commented 1 month ago

OK I'm very tired and not thinking clearly, obviously the provider has to be inside a client component hence SSR throwing the ' (0 , react__WEBPACK_IMPORTED_MODULE_0__.createContext) is not a function' error.

Managed to resolve this and have a working functional counter demo now. Might be worth adding this to the docs for next projects with SSR (or generically to ensure everyone understands the context provider must be in a client component). So in a next proj using the App router, the below works:

./app/section/page.tsx:

import ClientWrapper from '@/components/Section/ClientWrapper'
import React from 'react'

function Page() {
  return <ClientWrapper />
}

export default Page

./components/Section/ClientWrapper.tsx:

'use client'

import { useCounterStore } from './stores/store'
import { useObserver } from '@impact-react/signals'
import React from 'react'

function Counter() {
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  using _ = useObserver()

  const { count, increment } = useCounterStore()

  return (
    <div>
      {count}
      <button onClick={increment}>Increment</button>
    </div>
  )
}

function ClientWrapper() {
  return (
    <useCounterStore.Provider>
      <Counter />
    </useCounterStore.Provider>
  )
}

export default ClientWrapper

./components/Section/stores/store.ts:

import { createStore, signal } from '@impact-react/signals'

type Props = { initialCount?: number }

function CounterStore(props: Props) {
  const initialCount =
    (props.initialCount as number | undefined | typeof NaN) ?? 0

  const [count, setCount] = signal<number>(initialCount)

  return {
    get count() {
      return count()
    },
    increment() {
      setCount((value) => (value += 1))
    },
  }
}

export const useCounterStore = createStore(CounterStore)

Note: I've had to use using _ = useObserver() as disabling SWC and switching to babel massively degrades compilation times and also seems to break async server action functions, so if using next with impact this is the only (easy) option for now unfortunately.

coredevel commented 1 month ago

Looking at the 'Developing with Impact' docs and reading the section on store composition, i.e. with 'namespaces', while makes sense needs some additional info as to what the createDashboardStore, createProfileStore() fn defs actually look like, i.e. are they standard functions, or wrapped in calls to createStore as a normal store?

import { createStore } from "@impact-react/*";
import { createDashboardStore } from "./dashboard";
import { createProfileStore } from "./profile";

function AppStore() {
  const dashboard = createDashboardStore();
  const profile = createProfileStore();

  return {
    dashboard,
    profile,
  };
}

export const useAppStore = createStore(AppStore);

Using a previous version of impact I was defining these child stores as follows:

type Props = {
  navbarOpened: Signal<boolean>;
};

export function SidebarStore(props: Props) {
  const width = signal(DEFAULT_SIDEBAR_WIDTH);
  const isResizing = signal(false);
  const isTabsPanelHidden = signal(false);
  const navbarOpened = props.navbarOpened;
  ...

And including in the section's parent store as:

type Props = {
  navbarOpened: Signal<boolean>;
};

function StrategyBuilderStore(props: Props) {
  const sidebar = SidebarStore({ navbarOpened: props.navbarOpened });
  ...

(Ignore the prop drilling, though it was handy at the time, as I had some residual code where navbarOpened was part of a navbarContext)

christianalfoni commented 1 month ago

Hi @coredevel and thanks so much for taking me through this 😄

Which version of Next JS are you using? One of the demo examples in the repo is a Next JS app and it should work without considering server or client rendering, should work for either 🤔

I am trying to recruit a colleague to build the SWC plugin, or I will try to throw it at ChatGPT and see what comes out 😂