Bill
Bill

Reputation: 5150

React: TypeScript: Set and Get Global Context with useReducer

I have this route that has a login and logout button, the error in the browser is 'Property 'dispatch' does not exist on type '{}'.'

(home.tsx)

    import React, { useContext, useEffect, useRef, useState } from 'react';
    import { Dispatch, Global } from '../components/context';
    import { LOG_IN, LOG_OUT} from '../components/reducer';

    const Home: React.FC = () => {
      const { global } = useContext(Global);
      const { dispatch } = useContext(Dispatch);

      const login = () => {
        dispatch({ type: LOG_IN});
      };

      const logout = () => {
        dispatch({ type: LOG_OUT});
      };
      return (
      <div>
        {global.loggedIn && <div>You are logged in</div>}
        {!global.loggedIn && <div>You are logged out</div>}
        <br/>
        <button onClick={login}>Log In</button>
        <button onClick={logout}>Log Out</button>
      </div>
      );
    };

    export { Home };


I think I have the context wrong

(context.tsx)

    import { createContext } from 'react';
    import { InitialState } from './reducer';

    const Dispatch = createContext({}); // <--- I'm missing somthing here??

    const Global = createContext({
      global: InitialState,
    });

    export { Dispatch, Global };


The reducer feels right

(reducer.tsx)

    const LOG_IN = 'LOG_IN';
    const LOG_OUT = 'LOG_OUT';

    interface StateInterface {
      loggedIn: boolean;
    }
    const InitialState: StateInterface = {
      loggedIn: true,
    };

    interface ActionInterface {
      type: string;
    }
    const Reducer = (state: StateInterface, action: ActionInterface) => {
      switch (action.type) {
        case 'LOG_IN':
          return {
            ...state,
            loggedIn: true,
          };
        case 'LOG_OUT':
          return {
            ...state,
            loggedIn: false,
          };
        default:
          return state;
      }
    };
    export { Reducer, InitialState, LOG_IN, LOG_OUT };


And the route I believe is right

(Router.tsx)

    import React, { useReducer } from 'react';
    import { BrowserRouter, Route, Switch } from 'react-router-dom';
    import { Dispatch, Global } from './components/context';
    import { Layout } from './components/layout';
    import { InitialState, Reducer } from './components/reducer';
    import { Home } from './routes/home';
    import { NotFound } from './routes/notFound';

    const Router: React.FC = () => {
      const [global, dispatch] = useReducer(Reducer, InitialState);
      return (
        <Dispatch.Provider value={{ dispatch }}>
          <Global.Provider value={{ global }}>
            <BrowserRouter>
              <Route
                render={({ location }) => (
                  <Layout location={location}>
                    <Switch location={location}>
                      <Route exact path='/' component={Home} />
                      <Route component={NotFound} />
                    </Switch>
                  </Layout>
                )}
              />
            </BrowserRouter>
          </Global.Provider>
        </Dispatch.Provider>
      );
    };

    export { Router };

Upvotes: 1

Views: 973

Answers (1)

Alvaro
Alvaro

Reputation: 9672

Set as initial value for the Context the properties that will later be used. Otherwise any Component that uses the Context, on the first render, will get undefined properties.

So either set some kind of value for the property that the Provider will fill later, or check in each Component that uses the Context if the property exists (not recommended).

import { Dispatch as ReactDispatch } from "react";

// We assign the same type the property is going to have later
const Dispatch = createContext<{ dispatch: ReactDispatch<ActionInterface> }>({
    dispatch: () => {}
});

Upvotes: 1

Related Questions