Home > Back-end >  React Context is not working as expected: Unable to change the value of shared variable
React Context is not working as expected: Unable to change the value of shared variable

Time:02-24

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, useState } from "react";

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


export const ControlContext = createContext<MyContext>({
    clicked: false,
    changeClicked: () => {}
});


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

Model.tsx

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

export default function Model (props:any) { 
    const group = useRef<THREE.Mesh>(null!)
    
    const {clicked, changeClicked } = useContext(ControlContext);
    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={(event) => {
                changeClicked;
                console.log(clicked)
            }}
        >
            <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

Please refer this image if needed

CodePudding user response:

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

CodePudding user response:

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]);
  • Related