skip to Main Content

So, im trying to import my existing React app with JSX to TSX, but for some reason the AppContext doesnt work like it did was on my JSX:

App.JSX :

export interface ContextType {
  cursor: {x: number, y: number},
}

export const AppContext = createContext<ContextType | null>(null);

export default function App() {
  const [ cursor, setCursor ] = useState<ContextType['cursor']>({ x: -100, y: -100 }); // Store the cursor x and y position

...

  return(
    <div id="app" onMouseMove={ updateCursor }>
      <AppContext.Provider value={{ cursor }}>
      ...

Cursor.JSX :

import { AppContext, ContextType } from "../../App;

...

export default function Cursor() {
    const { cursor } = useContext(AppContext);

So it did say, Property ‘cursor’ does not exist on type ‘ContextType | null’.

3

Answers


  1. It is because the property cursor does not exist on the type null (the initial context value set by your code when you called createContext), even though it exists in ContextType.

    One way to fix it is to change:

    export const AppContext = createContext<ContextType | null>(null);

    to

    export const AppContext = createContext<ContextType>({ x: 0, y: 0 });

    The above will make the context always have a ContextType-compliant value

    Alternatively, you can still useContext(AppContext), but you will need to check whether that value is null first. Probably something along the lines:

    const context = useContext(AppContext);
    
    if (!context) return null; // this really depends on the logic of your component, I'm early returning here just to demonstrate
    
    const { x, y } = context;
    
    Login or Signup to reply.
  2. The problem is the definition of the type in the createContext, because you also allow null as value, which is not an object with a property cursor.

    I would define the context with a valid default fallback, e.g.

     export const AppContext = createContext<ContextType>({x:0,y:0});
    

    You’ll probably don’t call useContext outside of the ContextProvider, so the value will never be used. But doing it like this, it’s guaranteed, that a valid context object is returned.

    As an alternative define a special useAppContext method to access the context. This also improves readability.

    const useAppContext:ContextType = ()=>{
     const ctx = useContext(AppContext)
     if(!ctx) {
       throw new Error("useAppContext must be used within AppContextProvider")
     }
     return ctx
    }
    
    Login or Signup to reply.
  3. A clean and scalable way to do this is to separate this logic in a separate file and define what information will be passed by your application.

    For example, I will leave an example of a code that I implemented in an authentication context that I believe could help you. In it, I also use Typescript.

    File: /contexts/auth.tsx

    interface AuthContextData {
        signed: boolean;
        user: User | null;
        loading: boolean;
        ...
    }
    
    const AuthContext = createContext<AuthContextData>({} as AuthContextData);
    
    export const AuthProvider = ({ children }: { children: ReactNode }) => {
        const [user, setUser] = useState<User | null>(null);
        const [loading, setLoading] = useState(true);
    
        return (
            <AuthContext.Provider value={{ signed: Boolean(user), user, loading }}>
                {children}
            </AuthContext.Provider>
        )
    }
    
    export default AuthContext;
    

    File: /App.tsx

    import { AuthProvider } from "./contexts/auth";
    
    import Routes from './routes';
    
    function App() {
    
        return (
            <AuthProvider>
                <Routes />
            </AuthProvider>
          )
    }
    

    So, you can access the information inside something that is wrapped by the AuthProvider as follows:

    File: /routes/index.tsx

    import { useContext } from 'react';
    import AuthContext from "../contexts/auth";
    
    import UnauthenticatedRoutes from './unauthenticated.routes';
    import AppRoutes from '../routes/app.routes';
    
    import Loading from '../components/Loading';
    
    const Routes = () => {
        const { signed, loading } = useContext(AuthContext);
    
        if (loading) {
            return <Loading />;
        }
    
        return signed ? <AppRoutes /> : <UnauthenticatedRoutes />;
    
    };
    
    export default Routes;
    

    Try applying this to your logic and it will certainly work, as well as improving the quality of your code.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search