solid-js

Props don't seem to be reactive when used in an event handler


I am having issues with the value of a Solid-JS property not being up-to-date when it is used in an event handler. I have built the following example to illustrate:

import { Component, createContext, useContext, JSX, Context } from 'solid-js'
import { createStore, Store, SetStoreFunction } from 'solid-js/store'

const [state, setState] = createStore({ count: 0 })


interface ContextProps {
  state: Store<typeof state>,
  setState: SetStoreFunction<typeof state>
}

const StoreContext = createContext<ContextProps>()
const useStoreContext = () => useContext(StoreContext)

interface StoreProviderPropsType {
  children: JSX.Element
}

export const StoreProvider = (props: StoreProviderPropsType) => {
  return (
    <StoreContext.Provider value={{ state, setState }}>
      {props.children}
    </StoreContext.Provider>
  )
}


const TestComp: Component<{ count: number }> = (props) => {
  return (
    <button onClick={() => window.alert(props.count)}>Show</button>
  )
}

const App: Component = () => {
  return (
    <StoreProvider>
      <div>
        <TestComp count={useStoreContext()?.state.count!} />
        <button onClick={() => setState('count', state.count + 1)}>Increment</button>
      </div>
    </StoreProvider>
  )
}

export default App

When this code runs, clicking the 'Show' button always displays the value 'undefined' even after the state is updated by clicking the 'Increment' button. I have found a workaround to this by defining a local reactive variable using createMemo but it seems a bit unnecessary ... Couldn't the JSX compiler take care of this? Or is there something I am missing?


Solution

  • The UI shows the correct value however the event handler returns undefined.

    const TestComp: Component<{ count: number }> = (props) => {
      const handleClick = () => {
        console.log(props.count);
      };
      return (
        <button onClick={handleClick}>Show {props.count}</button>
      )
    }
    

    It could be a bug but I don't believe so because you are not properly using the context API since you are passing the context object as prop:

     <TestComp count={useStoreContext()?.state.count!} />
    

    The whole point of the Context API is eliminating the prop drilling, in other words, passing props every step of the way through the component tree.

    You should access the context value from the component that needs the value:

    const TestComp: Component<{}> = (props) => {
      const ctx = useStoreContext();
    
      const handleClick = () => {
        console.log(ctx?.state.count);
      };
    
      return (
        <button onClick={handleClick}>Show {ctx?.state.count}</button>
      )
    }
    

    The issue could be related to the Solid's execution mechanism. The context API relies on computation objects to resolve. The ownership tree. The function useContext returns the correct value when it is called inside an computation, that is an effect, a memo, or any other value that can subscribe to a signal. By passing it as a prop value, you are accessing the value when it is not resolve to value yet.

    Please check this answer for how context API works: https://stackoverflow.com/a/74492899/7134134