cristian
cristian

Reputation: 1009

React context (this.context) is undefined at constructor call time

I am trying to write a class component that uses values from this.context to derive a state object. I have a method getStyle that returns such an object. At constructor time, I am calling this.getStyle to start off with an already up-to-date state, and speed up the initial rendering.

However, it seems that, at constructor call time, this.context is undefined, and it remains such until render call time.

Am I doing something wrong in my code?

I don't recall any details related to such issue in React's (new) context API docs. this.context is available at componentDidMount time, however, this would require a setState, which will cause an extra component re-render which I would like to avoid.

Below is the code that I'm trying to use:

import React from "react";
import { View, Text } from "react-native";


const defaultTheme = {
    light: { backgroundColor: "white" },
    dark: { backgroundColor: "black" }
};

const customTheme = {
    light: { backgroundColor: "#EEE" },
    dark: { backgroundColor: "#111" },
};

const MyContext = React.createContext(defaultTheme);


class Container extends React.PureComponent {
    static contextType = MyContext;

    constructor(props) {
        super(props);

        this.state = this.getStyle();   // TypeError: undefined is not an object (evaluating '_this.context[_this.props.colorScheme || "light"]')
        // this.state = {}; // use this to try the componentDidMount alternative
    }

    componentDidMount = () => {
        const style = this.getStyle();
        this.setState(style);
    }

    getStyle = () => {
        // this.props.colorScheme = "light" | "dark" | null | undefined
        return this.context[this.props.colorScheme || "light"];
    }

    componentDidUpdate = (prevProps, prevState, snapshot) => {
        if (prevProps.colorScheme !== this.props.colorScheme) {
            const style = this.getStyle();
            this.setState(style);
        }
    }

    render = () => {
        return <View style={this.state}>
            {this.props.children}
        </View>;
    }
}


export default function App() {
    return <MyContext.Provider value={customTheme}>
        <Container>
            <Text>Hello, world!</Text>
        </Container>
    </MyContext.Provider>
}

Upvotes: 2

Views: 1416

Answers (3)

cristian
cristian

Reputation: 1009

There is another solution other than the ones posted above: context wrapper component, passing context as props.
You create a Wrapper component that in its render function uses the context value, and passes it on to the component where you need those props at constructor time:

class TheActualComponent extends React.PureComponent {
    constructor(props) {
        super(props);
        console.log(props.context); // Now you've got'em
    }

    // ...
}

// SFC wrapper
function WrapperComponent(props) {
    const contextProps = React.useContext(MyContext);

    return <TheActualComponent {...props} context={contextProps}>
        {props.children}
    </TheActualComponent>;
}

// class component wrapper
class WrapperClassComponent extends React.PureComponent {
    static contextType = MyContext;
    render() {
        return <TheActualComponent {...this.props} context={this.context}>
            {this.props.children}
        </TheActualComponent>;
    }
}

Upvotes: 0

James Crain
James Crain

Reputation: 11

For anyone stumbling across this using React 18+, the only way I could get this to work was to access this.conext inside of the render() method in a class component.

Upvotes: 0

Huu Tho Tran Nguyen
Huu Tho Tran Nguyen

Reputation: 21

Try passing context to constructor:

constructor(props, context) {
    // your code here
}

This solution might be deprecated according to this but you can try.

Upvotes: 2

Related Questions