2

I made a context to share the value of the variable "clicked" throughout my nextjs pages, it seems to give no errors but as you can see the variable's value remains FALSE even after the click event. It does not change to TRUE. This is my first time working with context, what am I doing wrong? I'm using typescript PS: After the onClick event the log's number shoots up by 3 or 4, is it being executed more than once, but how?

controlsContext.tsx

import { createContext, FC, useState } from "react";

export interface MyContext {
    clicked: boolean;
    changeClicked?: () => void;
}

const defaultState = {
  clicked: false,
}

const ControlContext = createContext<MyContext>(defaultState);


export const ControlProvider: FC = ({ children }) => {
    const [clicked, setClicked] =  useState(defaultState.clicked);
    const changeClicked = () => setClicked(!clicked);
  return (
    <ControlContext.Provider
      value={{
          clicked,
          changeClicked,
      }}
    >
      {children}
    </ControlContext.Provider>
  );
};

export default ControlContext;

Model.tsx

import ControlContext from "../contexts/controlsContext";

export default function Model (props:any) { 
    const group = useRef<THREE.Mesh>(null!)
    const {clicked, changeClicked } = useContext(ControlContext);

    const handleClick = (e: MouseEvent) => {
        //e.preventDefault();
        changeClicked();
        console.log(clicked);
    }


    useEffect(() => {
        console.log(clicked);
      }, [clicked]);
    useFrame((state, delta) => (group.current.rotation.y += 0.01));
    const model = useGLTF("/scene.gltf ");
    return (
        <>
        
         <TransformControls enabled={clicked}>
         
        <mesh 
            ref={group} 
            {...props}
            scale={clicked ? 0.5 : 0.2}
            onClick={handleClick}
        >
            <primitive object={model.scene}/>
        </mesh>
        </TransformControls>
        
        </>
    )
}

_app.tsx

import {ControlProvider} from '../contexts/controlsContext';


function MyApp({ Component, pageProps }: AppProps) {
  return (
    <ControlProvider>
      <Component {...pageProps} 
      />
    </ControlProvider>
  )
}

export default MyApp

This is the error I get

sbunn
  • 33
  • 1
  • 6
  • in your attached image, you still have `changeClicked` instead of `changeClicked()`... – andy mccullough Feb 24 '22 at 11:52
  • @andymccullough My bad, I forgot to update the image, please refer to the latest one now – sbunn Feb 24 '22 at 12:37
  • you also have `;;` at the end of the line `const changeClicked ...` – andy mccullough Feb 24 '22 at 14:47
  • @andymccullough I've refactored my code, please check it once above. Now when a click event happens, such an error pops up – sbunn Feb 24 '22 at 18:24
  • It seems at this point we just need more context and more complete code to see what and where these components are rendered and how they relate. Can you create a *running* codesandbox so we can inspect and debug it without playing 20 questions? For example, what is `Component` in `MyApp`? Is the `Modal` rendered within the context provider? Etc... – Drew Reese Feb 24 '22 at 18:29
  • https://codesandbox.io/s/sleepy-zhukovsky-8lkmn1?file=/pages/index.tsx @DrewReese Here, please do have a look, I'll be really grateful for any help! – sbunn Feb 24 '22 at 18:47

2 Answers2

0

A few things -

setClicked((prev) => !prev);

instead of

setClicked(!clicked);

As it ensures it's not using stale state. Then you are also doing -

changeClicked

But it should be -

changeClicked();

Lastly, you cannot console.log(clicked) straight after calling the set state function, it will be updated in the next render

andy mccullough
  • 9,070
  • 6
  • 32
  • 55
  • Thank you for the quick reply! However when I change changeClicked to changeClicked(), I get an error `` onClick={(event) => { changeClicked(); }} `` The error - **Cannot invoke an object which is possibly 'undefined'.ts(2722)** – sbunn Feb 24 '22 at 05:18
  • Your interface has changeClicked as optional, so either make it not optional or check it exists before calling it – andy mccullough Feb 24 '22 at 05:24
  • I made the necessary changes, however changeClicked is not triggered anytime. The value of the variable "clicked" remains false :( – sbunn Feb 24 '22 at 05:54
0

Issues

  1. You are not actually invoking the changeClicked callback.
  2. React state updates are asynchronously processed, so you can't log the state being updated in the same callback scope as the enqueued update, it will only ever log the state value from the current render cycle, not what it will be in a subsequent render cycle.
  3. You've listed the changeClicked callback as optional, so Typescript will warn you if you don't use a null-check before calling changeClicked.

Solution

const { clicked, changeClicked } = useContext(ControlContext);

...

<mesh 
  ...
  onClick={(event) => {
    changeClicked && changeClicked();
  }}
>
  ...
</mesh>

...

Or declare the changeClicked as required in call normally. You are already providing changeClicked as part of the default context value, and you don't conditionally include in in the provider, so there's no need for it to be optional.

export interface MyContext {
  clicked: boolean,
  changeClicked: () => void
}

...

const { clicked, changeClicked } = useContext(ControlContext);

...

<mesh 
  ...
  onClick={(event) => {
    changeClicked();
  }}
>
  ...
</mesh>

...

Use an useEffect hook in to log any state updates.

const { clicked, changeClicked } = useContext(ControlContext);

useEffect(() => {
  console.log(clicked);
}, [clicked]);

Update

After working with you and your sandbox it needed a few tweaks.

  1. Wrapping the index.tsx JSX code with the ControlProvider provider component so there was a valid context value being provided to the app. The UI here had to be refactored into a React component so it could itself also consume the context value.
  2. It seems there was some issue with the HTML canvas element, or the mesh element that was preventing the Modal component from maintaining a "solid" connection with the React context. It wasn't overtly clear what the issue was here, but passing the context values directly to the Modal component as props resolved the issue with the changeClicked callback becoming undefined.
Drew Reese
  • 165,259
  • 14
  • 153
  • 181
  • Thank you for the reply! I made the necessary changes and there's no error. But it still doesn't work, whenever I click the mesh, the event isn't triggered. As a proof, I tried your useEffect solution to log the changes but it only triggers once after the component is mounted, it doesn't trigger after that, meaning that the value of "clicked" isn't being changed – sbunn Feb 24 '22 at 05:52
  • @sbunn Does `mesh` handle click events? Does something simple like ` console.log("mesh clicked")}> ....` result in the console log? Think you could create a pared down, *running* codesandbox demo that reproduces this issue that we could inspect and debug live? – Drew Reese Feb 24 '22 at 05:55
  • Yes, `mesh` handles click events. I have shared a code sandbox with you, but there are some errors regarding react popping up in that :( – sbunn Feb 24 '22 at 07:34
  • @sbunn Sorry, am I missing where you've shared a code sandbox link? – Drew Reese Feb 24 '22 at 18:02
  • I've refactored my code, please check it once above. Now when a click event happens, such an error pops up – sbunn Feb 24 '22 at 18:24
  • @sbunn Welcome, glad to assist. Cheers and good luck! – Drew Reese Feb 24 '22 at 20:00