3

I have a React Context file with a stateful Provider to manage cookie preferences. It all works as expected but I'm having 1 issue with Typescript. My file looks like this:

import Cookies from 'js-cookie';
import React, { Component, ReactNode } from 'react';

interface Props {
  children: ReactNode | ReactNode[];
  functionalCookiesOn: boolean;
  performanceCookiesOn: boolean;
};

interface State {
  functionalCookiesOn: boolean;
  performanceCookiesOn: boolean;
};

// Initialise context storage
const CookiesContext = React.createContext({});
const { Consumer: CookiesConsumer } = CookiesContext;

class CookiesProvider extends Component<Props, State> {
  constructor(props: Props) {
    super(props);

    const {
      functionalCookiesOn,
      performanceCookiesOn,
    } = this.props;

    this.state = {
      functionalCookiesOn,
      performanceCookiesOn,
    };
  }

  componentDidUpdate(_prevProps: Props, prevState: State) {
    const {
      functionalCookiesOn,
      performanceCookiesOn,
    } = this.state;

    // Set cookie to store functional cookies setting
    if (functionalCookiesOn !== prevState.functionalCookiesOn) {
      Cookies.set('functionalCookiesOn', functionalCookiesOn.toString());
    }

    // Set cookie to store performance cookies setting
    if (performanceCookiesOn !== prevState.performanceCookiesOn) {
      Cookies.set('performanceCookiesOn', performanceCookiesOn.toString());
    }
  }

  toggleAllCookies = () => {
    // Store reversed state for functional and performance cookies
    this.setState((prevState: State) => ({
      functionalCookiesOn: !prevState.functionalCookiesOn,
      performanceCookiesOn: !prevState.performanceCookiesOn,
    }));
  }

  render() {
    const { children } = this.props;

    const {
      functionalCookiesOn,
      performanceCookiesOn,
    } = this.state;

    const value = {
      functionalCookiesOn,
      performanceCookiesOn,
      toggleAllCookies: this.toggleAllCookies,
    };

    return (
      <CookiesContext.Provider value={value}>
        {children}
      </CookiesContext.Provider>
    );
  }
}

export default CookiesContext;
export { CookiesConsumer, CookiesProvider };

When I use this in another function component it looks like this:

const AnotherComponent = () => {
  const {
    functionalCookiesOn,
    performanceCookiesOn,
    toggleAllCookies,
  } = useContext(CookiesContext);

  return (
    ...
  );
}

This throws errors such as:

Property 'functionalCookiesOn' does not exist on type '{}'.

This seems to me to do with the following line in the original file:

const CookiesContext = React.createContext({});

Because I initialise the context with an empty object (because at that stage it's got no values).

What's the correct way to initialise this or apply types to avoid this error?

1
  • Yes, you need to provide initial values. Otherwise typescript will throw the error Commented Jun 7, 2019 at 15:00

2 Answers 2

7

I think you can provide a type to your call to createContext

const CookiesContext = React.createContext<Partial<Props>>({});

<Partial> allows you to create the context without default values.

Sign up to request clarification or add additional context in comments.

3 Comments

Ah right. I think this is nearly there, although I don't think the Props or State interface are correct to use since I also have the toggleAllCookies method passed into the Provider. Should I create a new interface for this purpose?
this page is a good guide: fettblog.eu/typescript-react/context
Ah yeah perfect, their example creates a new interface for it. Great, thanks!
1

Try this out;-

import Cookies from 'js-cookie';
import React, { Component, ReactNode } from 'react';

interface Props {
  children: ReactNode | ReactNode[];
  functionalCookiesOn: boolean;
  performanceCookiesOn: boolean;
};

interface State {
  functionalCookiesOn: boolean;
  performanceCookiesOn: boolean;
};

// Initialise context storage
const CookiesContext = React.createContext({
    functionalCookiesOn: false,
    performanceCookiesOn: false,
    toggleAllCookies: () => null
});
const { Consumer: CookiesConsumer } = CookiesContext;

class CookiesProvider extends Component<Props, State> {
  constructor(props: Props) {
    super(props);

    const {
      functionalCookiesOn,
      performanceCookiesOn,
    } = this.props;

    this.state = {
      functionalCookiesOn,
      performanceCookiesOn,
    };
  }

  componentDidUpdate(_prevProps: Props, prevState: State) {
    const {
      functionalCookiesOn,
      performanceCookiesOn,
    } = this.state;

    // Set cookie to store functional cookies setting
    if (functionalCookiesOn !== prevState.functionalCookiesOn) {
      Cookies.set('functionalCookiesOn', functionalCookiesOn.toString());
    }

    // Set cookie to store performance cookies setting
    if (performanceCookiesOn !== prevState.performanceCookiesOn) {
      Cookies.set('performanceCookiesOn', performanceCookiesOn.toString());
    }
  }

  toggleAllCookies = () => {
    // Store reversed state for functional and performance cookies
    this.setState((prevState: State) => ({
      functionalCookiesOn: !prevState.functionalCookiesOn,
      performanceCookiesOn: !prevState.performanceCookiesOn,
    }));
  }

  render() {
    const { children } = this.props;

    const {
      functionalCookiesOn,
      performanceCookiesOn,
    } = this.state;

    const value = {
      functionalCookiesOn,
      performanceCookiesOn,
      toggleAllCookies: this.toggleAllCookies,
    };

    return (
      <CookiesContext.Provider value={value}>
        {children}
      </CookiesContext.Provider>
    );
  }
}

export default CookiesContext;
export { CookiesConsumer, CookiesProvider };

This should do the trick and typescript will start intellisense.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.