Context Management Applications

Keywords: React Mobile

Context Management Applications

Using React makes it easy to track data streams through React components. When you see a component, you can see which props are being delivered, which makes it easy for your application to know what to do.

In some cases, you want to pass data through the component tree rather than props manually through the middle components at each level, which you can do directly in React using powerful "context".

Why not use Context?

Most applications do not need to use Context directly.

If you want your application to be stable, don't use Context. Because this is an experimental API, it may be removed in future React versions.

If you are not familiar with state management libraries such as Redux or MobX, don't use Context. For many practical applications, these state management libraries are a good choice to use with React to manage those component-related states. In many cases, using Redux can solve your problem, rather than using Context.

If you're not an experienced React developer, don't use Context. Using props and state to implement functions is a better way.

Despite these warnings, you still insist on using Context, so isolate Context into a small area and avoid using Context directly as much as possible so that applications can be upgraded more easily when the API changes.

How to use Context

Suppose you have a structure:

import React from 'react';
import ReactDOM from 'react-dom';

class MyButton extends React.Component {
    constructor(props) {
        super(props);
    }

    render() {
        const style = {backgroundColor: this.props.color};
        return <button style={style}>{this.props.children}</button>;
    }
}
class Message extends React.Component {
    render() {
        return (
            <div>
                {this.props.text}
                <MyButton color={this.props.color}>delete</MyButton>
            </div>
        )
    }
}
class MessageList extends React.Component {
    render() {
        const color = 'red';
        const children = this.props.messages.map(msg => <Message text={msg} color={color}/>);
        return <div>{children}</div>;
    }
}
const messages = ['zhangyato', 'Re: zhangyatao', 'Re:Re:zhangyatao'];
ReactDOM.render(
    <MessageList messages={messages}/>,
    document.getElementById('root')
);

In this example, we manually pass in a color props to set the styles of MyButton and Message components appropriately. With Context, we can let color automatically pass through the component tree:

import React from 'react';
import ReactDOM from 'react-dom';

class MyButton extends React.Component {
    constructor(props) {
        super(props);
    }

    render() {
        const style = {backgroundColor: this.context.color};
        return <button style={style}>{this.props.children}</button>;
    }
}
MyButton.contextTypes = {
    color: React.PropTypes.string.isRequired
};
class Message extends React.Component {
    render() {
        return (
            <div>
                {this.props.text}
                <MyButton>delete</MyButton>
            </div>
        )
    }
}
class MessageList extends React.Component {
    getChildContext() {
        return {color: 'red'};
    }

    render() {
        const children = this.props.messages.map(msg => <Message text={msg}/>);
        return <div>{children}</div>;
    }
}
MessageList.childContextTypes = {
    color: React.PropTypes.string.isRequired
};
const messages = ['zhangyato', 'Re: zhangyatao', 'Re:Re:zhangyatao'];
ReactDOM.render(
    <MessageList messages={messages}/>,
    document.getElementById('root')
);

By adding child ContextTypes and getChildContext to MessageList (Context provider), React automatically passes context information, which can be accessed by any component (Button) in the sub-component tree by defining contextTypes.

If contextTypes are not defined, then Context will be an empty object.

Coupling between parent and child components

Context also allows you to interact between parent and child components. For example, a well-known library working in this way is React Router V4:

const RouterExample = () => {
    <Router>
        <div>
            <ul>
                 <li><Link to="/">homepage</Link></li>
                 <li><Link to="/recyclebin">recycle bin</Link></li>
                 <li><Link to="/timeline">picture</Link></li>
            </ul>

            <hr />

            <Match exactly pattern="/" component={Home} />
            <Match pattern="/recyclebin" component={RecycleBin} />
            <Match pattern="/timeline" component={TimeLine} />

        </div>
    </Router>
}

By passing some information from the Router Example component, each Link and Match can be returned to the included Outer.

Before building components with API s like this, consider whether there are better alternatives. For example, you can pass the entire React component as props.

Using Context in the Life Cycle Approach

If contextTypes are defined in a component, the following lifecycle method will receive one more parameter, the Context object:

  • constructor(props, context)

  • componentWillReceiveProps(nextProps, nextContext)

  • shouldComponentUpdate(nextProps, nextState, nextContext)

  • componentWillUpdate(nextProps, nextState, nextContext)

  • componentDidUpdate(prevProps, prevState, prevContext)

Using Context in Stateless Functional Components

If contextTypes are defined as attributes of stateless functional components, stateless functional components can also reference Context. The following code shows the MyButton component written as a stateless function component.

function MyButton(props, context) {
     const children = props.children;
     return (
         <button style={{backgroundColor: context.color}}>
             {children}
         </button>
     );
}
MyButton.contextTypes = {
    color: React.PropTypes.string.isRequired
};

Update Context

Never do that!!!

React has an API to update Context, but it fundamentally destroys Context, so you shouldn't use it.

When the state or props change, the getChildContext function is called. To update the data in the Context, use this.setState() to trigger the state update in the component. This will also trigger a new Context, and the content of the Context changes will be received by the subcomponent.

import React from 'react';
import ReactDOM from 'react-dom';

class MediaType extends React.Component {
    render() {
        return <div>type is {this.context.type}</div>
    }
}
MediaType.contextTypes = {
    type: React.PropTypes.string
};
class MediaQuery extends React.Component {
    constructor(props) {
        super(props);
        this.state = {type: 'PC end'};
        this.checkMediaQuery = this.checkMediaQuery.bind(this);
    }

    getChildContext() {
        return {type: this.state.type}
    }

    checkMediaQuery() {
        let type = window.matchMedia('<code>zhangyatao</code>(max-width: 760px)').matches ? 'Mobile end' : 'PC end';
        this.setState({type: type});
    }

    componentDidMount() {
        window.addEventListener('resize', this.checkMediaQuery, false);
        this.checkMediaQuery();
    }

    render() {
        return <div>{this.props.children}</div>;
    }
}
MediaQuery.childContextTypes = {
    type: React.PropTypes.string
};
ReactDOM.render(
    <MediaQuery>
        <MediaType />
    </MediaQuery>,
    document.getElementById('root')
);

The problem is that the Context value is provided by component updates, and if the intermediate parent component returns false in shouldComponentUpdate(), the descendant component that uses the Context value will never be updated. Using Context is completely out of the control of components, so there is basically no way to update Context reliably. This article explains why this is a problem and how you should get rid of it.

Posted by stb74 on Mon, 17 Jun 2019 15:50:54 -0700