web-dev-qa-db-ja.com

Apollo Client + React Routerを使用して、ユーザーステータスに基づいてプライベートルートとリダイレクトを実装する方法は?

Reactルーター4をルーティングに使用し、Apolloクライアントをデータのフェッチとキャッシュに使用しています。次の基準に基づいてPrivateRouteとリダイレクトソリューションを実装する必要があります。

  1. ユーザーに表示を許可するページは、サーバーからフェッチしたり、キャッシュから読み取ったりできるユーザーステータスに基づいています。ユーザーステータスは基本的に、ユーザーが目標到達プロセスのどこにいるかを理解するために使用する一連のフラグです。フラグの例:isLoggedInisOnboardedisWaitlistedなど。

  2. ユーザーのステータスでページへの表示が許可されていない場合でも、ページのレンダリングを開始することはできません。たとえば、isWaitlistedでない場合、順番待ちリストページは表示されません。ユーザーが誤ってこれらのページにアクセスした場合は、自分のステータスに適したページにリダイレクトする必要があります。

  3. リダイレクトも動的である必要があります。たとえば、isLoggedInになる前にユーザープロファイルを表示しようとしたとします。次に、ログインページにリダイレクトする必要があります。ただし、あなたがisLoggedInであるがisOnboardedでない場合でも、プロフィールを表示することは望ましくありません。そのため、オンボーディングページにリダイレクトします。

  4. これはすべて、ルートレベルで行う必要があります。ページ自体は、これらの権限とリダイレクトを認識しないようにしておく必要があります。

結論として、ユーザーステータスデータを与えられたライブラリが必要です。

  • ユーザーが特定のページにアクセスできるかどうかを計算する
  • 動的にリダイレクトする必要がある場所を計算する
  • ページをレンダリングする前にこれらを実行してください
  • ルートレベルでこれらを行う

私はすでに汎用ライブラリに取り組んでいますが、現在は欠点があります。この問題にどのように取り組むべきか、そしてこの目標を達成するための確立されたパターンがあるかどうかについて意見を求めています。

これが私の現在のアプローチです。 getRedirectPathが必要とするデータがOnboardingPage componentにあるため、これは機能していません。

また、リダイレクトパスの計算に必要な小道具を挿入できるHOCでPrivateRouteをラップすることはできません。これは、スイッチReact Routerコンポーネントの子として使用できないためです。ルートではなくなります。

<PrivateRoute
  exact
  path="/onboarding"
  isRender={(props) => {
    return props.userStatus.isLoggedIn && props.userStatus.isWaitlistApproved;
  }}
  getRedirectPath={(props) => {
    if (!props.userStatus.isLoggedIn) return '/login';
    if (!props.userStatus.isWaitlistApproved) return '/waitlist';
  }}
  component={OnboardingPage}
/>
12
AnApprentice

一般的方法

すべてのページでこのロジックを処理するHOCを作成します。

// privateRoute is a function...
const privateRoute = ({
  // ...that takes optional boolean parameters...
  requireLoggedIn = false,
  requireOnboarded = false,
  requireWaitlisted = false
// ...and returns a function that takes a component...
} = {}) => WrappedComponent => {
  class Private extends Component {
    componentDidMount() {
      // redirect logic
    }

    render() {
      if (
        (requireLoggedIn && /* user isn't logged in */) ||
        (requireOnboarded && /* user isn't onboarded */) ||
        (requireWaitlisted && /* user isn't waitlisted */) 
      ) {
        return null
      }

      return (
        <WrappedComponent {...this.props} />
      )
    }
  }

  Private.displayName = `Private(${
    WrappedComponent.displayName ||
    WrappedComponent.name ||
    'Component'
  })`

  // ...and returns a new component wrapping the parameter component
  return hoistNonReactStatics(Private, WrappedComponent)
}

export default privateRoute

次に、ルートをエクスポートする方法を変更するだけです。

export default privateRoute({ requireLoggedIn: true })(MyRoute);

そして、react-routerで今日行うのと同じ方法でそのルートを使用できます。

<Route path="/" component={MyPrivateRoute} />

リダイレクトロジック

この部分をどのように設定するかは、いくつかの要因によって異なります。

  1. ユーザーがログインしているか、オンボーディングされているか、順番待ちリストに登録されているかなどを判断する方法。
  2. リダイレクト先のwhereを担当するコンポーネント。

ユーザーステータスの処理

Apolloを使用しているので、おそらくgraphqlを使用してHOCでそのデータを取得することをお勧めします。

return hoistNonReactStatics(
  graphql(gql`
    query ...
  `)(Private),
  WrappedComponent
)

次に、Privateコンポーネントを変更して、これらの小道具を取得できます。

class Private extends Component {
  componentDidMount() {
    const {
      userStatus: {
        isLoggedIn,
        isOnboarded,
        isWaitlisted
      }
    } = this.props

    if (requireLoggedIn && !isLoggedIn) {
      // redirect somewhere
    } else if (requireOnboarded && !isOnboarded) {
      // redirect somewhere else
    } else if (requireWaitlisted && !isWaitlisted) {
      // redirect to yet another location
    }
  }

  render() {
    const {
      userStatus: {
        isLoggedIn,
        isOnboarded,
        isWaitlisted
      },
      ...passThroughProps
    } = this.props

    if (
      (requireLoggedIn && !isLoggedIn) ||
      (requireOnboarded && !isOnboarded) ||
      (requireWaitlisted && !isWaitlisted) 
    ) {
      return null
    }

    return (
      <WrappedComponent {...passThroughProps} />
    )
  }
}

リダイレクトする場所

これを処理できる場所はいくつかあります。

簡単な方法:ルートは静的です

ユーザーがログインしていない場合は、常に/login?return=${currentRoute}にルーティングする必要があります。

この場合、これらのルートをcomponentDidMountにハードコーディングするだけです。完了。

コンポーネントが責任を負います

MyRouteコンポーネントでパスを決定する場合は、privateRoute関数にいくつかのパラメーターを追加し、MyRouteをエクスポートするときにそれらを渡すことができます。

const privateRoute = ({
  requireLogedIn = false,
  pathIfNotLoggedIn = '/a/sensible/default',
  // ...
}) // ...

次に、デフォルトパスを上書きする場合は、エクスポートを次のように変更します。

export default privateRoute({ 
  requireLoggedIn: true, 
  pathIfNotLoggedIn: '/a/specific/page'
})(MyRoute)

ルートは責任があります

ルーティングからパスを渡せるようにする場合は、Privateでこれらの小道具を受け取る必要があります。

class Private extends Component {
  componentDidMount() {
    const {
      userStatus: {
        isLoggedIn,
        isOnboarded,
        isWaitlisted
      },
      pathIfNotLoggedIn,
      pathIfNotOnboarded,
      pathIfNotWaitlisted
    } = this.props

    if (requireLoggedIn && !isLoggedIn) {
      // redirect to `pathIfNotLoggedIn`
    } else if (requireOnboarded && !isOnboarded) {
      // redirect to `pathIfNotOnboarded`
    } else if (requireWaitlisted && !isWaitlisted) {
      // redirect to `pathIfNotWaitlisted`
    }
  }

  render() {
    const {
      userStatus: {
        isLoggedIn,
        isOnboarded,
        isWaitlisted
      },
      // we don't care about these for rendering, but we don't want to pass them to WrappedComponent
      pathIfNotLoggedIn,
      pathIfNotOnboarded,
      pathIfNotWaitlisted,
      ...passThroughProps
    } = this.props

    if (
      (requireLoggedIn && !isLoggedIn) ||
      (requireOnboarded && !isOnboarded) ||
      (requireWaitlisted && !isWaitlisted) 
    ) {
      return null
    }

    return (
      <WrappedComponent {...passThroughProps} />
    )
  }
}

Private.propTypes = {
  pathIfNotLoggedIn: PropTypes.string
}

Private.defaultProps = {
  pathIfNotLoggedIn: '/a/sensible/default'
}

次に、ルートを次のように書き換えることができます。

<Route path="/" render={props => <MyPrivateComponent {...props} pathIfNotLoggedIn="/a/specific/path" />} />

オプション2と3を組み合わせる

(これは私が使用したいアプローチです)

コンポーネントとルートに責任者を選択させることもできます。コンポーネントに決定させるために行ったように、パスにprivateRouteパラメータを追加する必要があります。次に、ルートが責任を負っていたときに行ったように、これらの値をdefaultPropsとして使用します。

これにより、柔軟に決定することができます。小道具としてルートを渡すことは、コンポーネントからHOCに渡すことよりも優先されることに注意してください。

今すべて一緒に

これは、HOCの最終的な見解として、上記のすべての概念を組み合わせたスニペットです。

const privateRoute = ({
  requireLoggedIn = false,
  requireOnboarded = false,
  requireWaitlisted = false,
  pathIfNotLoggedIn = '/login',
  pathIfNotOnboarded = '/onboarding',
  pathIfNotWaitlisted = '/waitlist'
} = {}) => WrappedComponent => {
  class Private extends Component {
    componentDidMount() {
      const {
        userStatus: {
          isLoggedIn,
          isOnboarded,
          isWaitlisted
        },
        pathIfNotLoggedIn,
        pathIfNotOnboarded,
        pathIfNotWaitlisted
      } = this.props

      if (requireLoggedIn && !isLoggedIn) {
        // redirect to `pathIfNotLoggedIn`
      } else if (requireOnboarded && !isOnboarded) {
        // redirect to `pathIfNotOnboarded`
      } else if (requireWaitlisted && !isWaitlisted) {
        // redirect to `pathIfNotWaitlisted`
      }
    }

    render() {
      const {
        userStatus: {
          isLoggedIn,
          isOnboarded,
          isWaitlisted
        },
        pathIfNotLoggedIn,
        pathIfNotOnboarded,
        pathIfNotWaitlisted,
        ...passThroughProps
      } = this.props

      if (
        (requireLoggedIn && !isLoggedIn) ||
        (requireOnboarded && !isOnboarded) ||
        (requireWaitlisted && !isWaitlisted) 
      ) {
        return null
      }
    
      return (
        <WrappedComponent {...passThroughProps} />
      )
    }
  }

  Private.propTypes = {
    pathIfNotLoggedIn: PropTypes.string,
    pathIfNotOnboarded: PropTypes.string,
    pathIfNotWaitlisted: PropTypes.string
  }

  Private.defaultProps = {
    pathIfNotLoggedIn,
    pathIfNotOnboarded,
    pathIfNotWaitlisted
  }
  
  Private.displayName = `Private(${
    WrappedComponent.displayName ||
    WrappedComponent.name ||
    'Component'
  })`

  return hoistNonReactStatics(
    graphql(gql`
      query ...
    `)(Private),
    WrappedComponent
  )
}

export default privateRoute

公式ドキュメント で提案されているように hoist-non-react-statics を使用しています。

9
Luke

ロジックを少し下げる必要があると思います。何かのようなもの:

<Route path="/onboarding" render={renderProps=>
   <CheckAuthorization authorized={OnBoardingPage} renderProps={renderProps} />
}/>
1
Dennie de Lange

私は個人的に次のようなプライベートルートを構築するために使用します:

_const renderMergedProps = (component, ...rest) => {
  const finalProps = Object.assign({}, ...rest);
  return React.createElement(component, finalProps);
};

const PrivateRoute = ({
  component, redirectTo, path, ...rest
}) => (
  <Route
    {...rest}
    render={routeProps =>
      (loggedIn() ? (
        renderMergedProps(component, routeProps, rest)
      ) : (
        <Redirect to={redirectTo} from={path} />
      ))
    }
  />
);
_

この場合、loggedIn()は、ユーザーがログに記録された場合にtrueを返す単純な関数です(ユーザーセッションの処理方法によって異なります)。このように、各プライベートルートを作成できます。

次に、canスイッチで使用します:

_<Switch>
    <Route path="/login" name="Login" component={Login} />
    <PrivateRoute
       path="/"
       name="Home"
       component={App}
       redirectTo="/login"
     />
</Switch>
_

このPrivateRouteからのすべてのサブルートは、最初にユーザーがログインしているかどうかを確認する必要があります。

最後のステップは、必要なステータスに従ってルートをネストすることです。

1
Dyo

'react-graphql'HOCなしでApolloClientを使用する必要があります。
1。 ApolloClientのインスタンスを取得します
2。ファイアクエリ
3。クエリがデータレンダリングの読み込みを返す間。
4。データに基づいてルートを確認および承認します。
5。適切なコンポーネントを返すか、リダイレクトします。

これは、次の方法で実行できます。

import Loadable from 'react-loadable'
import client from '...your ApolloClient instance...'

const queryPromise = client.query({
        query: Storequery,
        variables: {
            name: context.params.sellername
        }
    })
const CheckedComponent = Loadable({
  loading: LoadingComponent,
  loader: () => new Promise((resolve)=>{
       queryPromise.then(response=>{
         /*
           check response data and resolve appropriate component.
           if matching error return redirect. */
           if(response.data.userStatus.isLoggedIn){
            resolve(ComponentToBeRendered)
           }else{
             resolve(<Redirect to={somePath}/>)
           }
       })
   }),
}) 
<Route path="/onboarding" component={CheckedComponent} />

関連するAPIリファレンス: https://www.apollographql.com/docs/react/reference/index.html

0
Chromonav

Apollo reactクライアントを使用している場合は、Query@apollo/componentsをインポートして、プライベートルートでそのように使用することもできます。

    <Query query={fetchUserInfoQuery(moreUserInfo)}>
      {({ loading, error, data: userInfo = {} }: any) => {
        const isNotAuthenticated = !loading && (isEmpty(userInfo) || !userInfo.whoAmI);

        if (isNotAuthenticated || error) {
          return <Redirect to={RoutesPaths.Login} />;
        }
        const { whoAmI } = userInfo;

        return <Component user={whoAmI} {...renderProps} />;
      }}
    </Query>

ここで、isEmptyは、指定されたオブジェクトが空かどうかをチェックしているだけです。

const isEmpty = (object: any) => object && Object.keys(object).length === 0
0
Daler