1. ホーム
  2. reactjs

[解決済み] render関数の外側でReact Contextにアクセスする

2022-10-14 12:12:42

質問

Reduxの代わりに新しいReact Context APIを使って新しいアプリを開発しているのですが、以前は Redux で、例えばユーザーのリストを取得する必要があるときは、単純に componentDidMount でアクションを呼び出すだけですが、React Context ではアクションはレンダー関数の中にある Consumer の中にあります。つまり、レンダー関数が呼び出されるたびに、ユーザーリストを取得するためにアクションが呼び出されます。

では、どうすれば一度だけ私のアクションを呼び出すことができるでしょうか。 componentDidMount のように一度だけ呼び出すことはできますか?

例として、このコードを見てください。

をラップしているとします。 Providers を一つのコンポーネントにまとめているとします。

import React from 'react';

import UserProvider from './UserProvider';
import PostProvider from './PostProvider';

export default class Provider extends React.Component {
  render(){
    return(
      <UserProvider>
        <PostProvider>
          {this.props.children}
        </PostProvider>
      </UserProvider>
    )
  }
}

そして、このProviderコンポーネントを、以下のように、すべてのアプリを包むように配置します。

import React from 'react';
import Provider from './providers/Provider';
import { Router } from './Router';

export default class App extends React.Component {
  render() {
    const Component = Router();
    return(
      <Provider>
        <Component />
      </Provider>
    )
  }
}

さて、例えば私のユーザービューでは、次のようになります。

import React from 'react';
import UserContext from '../contexts/UserContext';

export default class Users extends React.Component {
  render(){
    return(
      <UserContext.Consumer>
        {({getUsers, users}) => {
          getUsers();
          return(
            <h1>Users</h1>
            <ul>
              {users.map(user) => (
                <li>{user.name}</li>
              )}
            </ul>
          )
        }}
      </UserContext.Consumer>
    )
  }
}

私が欲しいのはこれです。

import React from 'react';
import UserContext from '../contexts/UserContext';

export default class Users extends React.Component {
  componentDidMount(){
    this.props.getUsers();
  }

  render(){
    return(
      <UserContext.Consumer>
        {({users}) => {
          getUsers();
          return(
            <h1>Users</h1>
            <ul>
              {users.map(user) => (
                <li>{user.name}</li>
              )}
            </ul>
          )
        }}
      </UserContext.Consumer>
    )
  }
}

しかし、もちろん、上の例では getUsers がUsers view propsに存在しないからです。もしこれが可能なら、正しい方法は何でしょうか?

どのように解決するのですか?

EDITです。 でのreact-hooksの導入により v16.8.0 を利用することで、関数型コンポーネントでコンテキストを利用することができます。 useContext フック

const Users = () => {
    const contextValue = useContext(UserContext);
    // rest logic here
}

EDITです。 バージョンから 16.6.0 になりました。ライフサイクルメソッドでコンテキストを利用するには this.context のように

class Users extends React.Component {
  componentDidMount() {
    let value = this.context;
    /* perform a side-effect at mount using the value of UserContext */
  }
  componentDidUpdate() {
    let value = this.context;
    /* ... */
  }
  componentWillUnmount() {
    let value = this.context;
    /* ... */
  }
  render() {
    let value = this.context;
    /* render something based on the value of UserContext */
  }
}
Users.contextType = UserContext; // This part is important to access context values


バージョン16.6.0以前は、次のような方法で行うことができました。

ライフサイクルメソッドで Context を使用するには、次のようにコンポーネントを記述します。

class Users extends React.Component {
  componentDidMount(){
    this.props.getUsers();
  }

  render(){
    const { users } = this.props;
    return(

            <h1>Users</h1>
            <ul>
              {users.map(user) => (
                <li>{user.name}</li>
              )}
            </ul>
    )
  }
}
export default props => ( <UserContext.Consumer>
        {({users, getUsers}) => {
           return <Users {...props} users={users} getUsers={getUsers} />
        }}
      </UserContext.Consumer>
)

一般的には、アプリで1つのコンテキストを維持し、それを再利用するために、上記のログインをHOCでパッケージ化することは理にかなっています。次のように書くことができます。

import UserContext from 'path/to/UserContext';

const withUserContext = Component => {
  return props => {
    return (
      <UserContext.Consumer>
        {({users, getUsers}) => {
          return <Component {...props} users={users} getUsers={getUsers} />;
        }}
      </UserContext.Consumer>
    );
  };
};

といった具合に使うことができます。

export default withUserContext(User);