简体   繁体   English

在属性更新后更新用户上下文 - AWS Amplify

[英]Update User Context After Attribute Update - AWS Amplify

I have a requirement that a first time user is presented with a popup that they need to agree to after logging in. I have created a custom attribute in Cognito that is marked "Yes" until the user clicks the agree button.我有一个要求,首次用户在登录后会看到一个他们需要同意的弹出窗口。我在 Cognito 中创建了一个自定义属性,该属性标记为“是”,直到用户单击同意按钮。 All of that logic works except when you refresh the page the user is presented with the popup again despite agreeing and the attribute being changed in Cognito.所有这些逻辑都有效,除非当您刷新页面时,尽管同意并在 Cognito 中更改了属性,但仍会再次向用户显示弹出窗口。

I am using the React context api with useContext hook.我正在使用带有 useContext 钩子的 React 上下文 api。 I noticed in React tools that the context is not getting updated and this may be the problem.我在 React 工具中注意到上下文没有更新,这可能是问题所在。

AuthContext.js验证上下文.js

import React from 'react';

export const AuthContext = React.createContext();

export const AuthProvider = AuthContext.Provider;

App.js应用程序.js

import React from 'react';
import { withRouter } from 'react-router-dom';
import Header from './components/Header';
import Routes from './Routes';
import useAmplifyAuth from './libs/useAmplifyAuth';
import { AuthProvider } from './context/AuthContext';
import InitialLoginModal from './components/InitialLoginModal';

function App() {
  const {
    state: { user },
    handleSignout
  } = useAmplifyAuth();

  return (
    <>
      <AuthProvider value={{ user, handleSignout }}>
        <>
          <Header />
          <Routes />
          <InitialLoginModal />
        </>
      </AuthProvider>
    </>
  );
}

export default withRouter(App);

InitialLoginModal.js InitialLoginModal.js

import React, { useContext, useState, useEffect } from 'react';
import { Modal, Button, Image } from 'react-bootstrap';
import { Auth } from 'aws-amplify';
import imgLogo from '../img/logo.jpg';
import { AuthContext } from '../context/AuthContext';

const InitialLoginModal = () => {
  const { user, handleSignout } = useContext(AuthContext);

  const [showModal, setShowModal] = useState(false);
  const [initialLogin, setInitialLogin] = useState('');

  const noAccept = () => {
    setShowModal(false);
    handleSignout();
  };

  useEffect(() => {
    if (user) {
      console.log(user.attributes['custom:initiallogin']);
      if (user.attributes['custom:initiallogin'] === 'Yes') {
        setShowModal(true);
      }
    }
  }, [user]);

  const accept = () => {
    updateInitialLogin();
    setShowModal(false);
  };

  const updateInitialLogin = async () => {
    await Auth.updateUserAttributes(user, { 'custom:initiallogin': 'No' });
    setInitialLogin('No');
    setShowModal(false);
  };

  return (
    <>
      {/* Initial login modal */}
      <Modal
        show={showModal}
        onHide={noAccept}
        dialogClassName="modal-70w modal-item"
        aria-labelledby="Initial Login Modal"
      >
        <Modal.Header closeButton>
          <Modal.Title>
            <Image
              src={imgLogo}
              alt="Logo"
              fluid
              className="modal-image-center"
            />
          </Modal.Title>
        </Modal.Header>
        <Modal.Body>
          <p>
            Text that I must agree to.
          </p>
        </Modal.Body>
        <Modal.Footer>
          <Button onClick={accept}>Next</Button>
        </Modal.Footer>
      </Modal>
    </>
  );
};

export default InitialLoginModal;

useAmplifyAuth.js使用AmplifyAuth.js

import { useReducer, useState, useEffect } from 'react';
import { Auth, Hub } from 'aws-amplify';

const amplifyAuthReducer = (state, action) => {
  switch (action.type) {
    case 'FETCH_USER_DATA_INIT':
      return {
        ...state,
        isLoading: true,
        isError: false
      };
    case 'FETCH_USER_DATA_SUCCESS':
      return {
        ...state,
        isLoading: false,
        isError: false,
        user: action.payload.user
      };
    case 'FETCH_USER_DATA_FAILURE':
      return { ...state, isLoading: false, isError: true };
    case 'RESET_USER_DATA':
      return { ...state, user: null };
    default:
      throw new Error();
  }
};

const useAmplifyAuth = () => {
  const initialState = {
    isLoading: true,
    isError: false,
    user: null
  };
  const [state, dispatch] = useReducer(amplifyAuthReducer, initialState);
  const [triggerFetch, setTriggerFetch] = useState(false);
  useEffect(() => {
    let isMounted = true;
    const fetchUserData = async () => {
      if (isMounted) {
        dispatch({ type: 'FETCH_USER_DATA_INIT' });
      }
      try {
        if (isMounted) {
          const data = await Auth.currentAuthenticatedUser();
          if (data) {
            dispatch({
              type: 'FETCH_USER_DATA_SUCCESS',
              payload: { user: data }
            });
          }
        }
      } catch (error) {
        if (isMounted) {
          dispatch({ type: 'FETCH_USER_DATA_FAILURE' });
        }
      }
    };
    const HubListener = () => {
      Hub.listen('auth', data => {
        const { payload } = data;
        onAuthEvent(payload);
      });
    };
    const onAuthEvent = payload => {
      switch (payload.event) {
        case 'signIn':
          if (isMounted) {
            setTriggerFetch(true);
            console.log('signed in');
          }
          break;
        default:
          return;
      }
    };
    HubListener();
    fetchUserData();
    return () => {
      Hub.remove('auth');
      isMounted = false;
    };
  }, [triggerFetch]);
  const handleSignout = async () => {
    try {
      console.log('signed out');
      await Auth.signOut();
      setTriggerFetch(false);
      dispatch({ type: 'RESET_USER_DATA' });
    } catch (error) {
      console.error('Error signing out user ', error);
    }
  };
  return { state, handleSignout };
};
export default useAmplifyAuth;

In the end, I just need the user to be able to agree to the terms, have their custom attribute be updated, and not be presented with the modal again after that.最后,我只需要用户能够同意这些条款,更新他们的自定义属性,并且在此之后不再显示模态。 Any help would be appreciated.任何帮助,将不胜感激。 Thanks.谢谢。

  • New InitialLoginModal.js after @vencovsky suggestion @vencovsky 建议后的新 InitialLoginModal.js
import React, { useContext, useEffect, useState } from 'react';
import { Modal, Button, Image } from 'react-bootstrap';
import { Auth } from 'aws-amplify';
import imgLogo from '../img/logo.jpg';
import { AuthContext } from '../context/AuthContext';

const InitialLoginModal = () => {
  const {
    user,
    handleSignout,
    shouldShowModal,
    setShouldShowModal
  } = useContext(AuthContext);

  const [showModal, setShowModal] = useState(shouldShowModal);
  // const [initialLogin, setInitialLogin] = useState('');

  const noAccept = () => {
    setShowModal(false);
    handleSignout();
  };

  useEffect(() => {
    if (user) {
      if (user.attributes['custom:initiallogin'] === 'Yes') {
        setShowModal(true);
      }
    }
  }, [user, setShouldShowModal]);

  const accept = () => {
    updateInitialLogin();
    // setShouldShowModal(false);
  };

  const updateInitialLogin = async () => {
    if (user) {
      await Auth.updateUserAttributes(user, { 'custom:initiallogin': 'No' });
      setShowModal(false);
    }
  };

  return (
    <>
      {/* Initial login modal */}
      <Modal
        show={showModal}
        onHide={noAccept}
        dialogClassName="modal-70w modal-item"
        aria-labelledby="Initial Login Modal"
      >
        <Modal.Header closeButton>
          <Modal.Title>
            <Image
              src={imgLogo}
              alt="Logo"
              fluid
              className="modal-image-center"
            />
          </Modal.Title>
        </Modal.Header>
        <Modal.Body>
          <p>
            Info here
          </p>
        </Modal.Body>
        <Modal.Footer>
          <Button onClick={accept}>Next</Button>
        </Modal.Footer>
      </Modal>
    </>
  );
};

export default InitialLoginModal;

I just ran into this issue myself, and I believe that this is the line that needs updating from:我自己刚刚遇到了这个问题,我相信这是需要更新的行:

const data = await Auth.currentAuthenticatedUser();

to:到:

const data = await Auth.currentAuthenticatedUser({bypassCache: true});

This will retrieve the updated attributes directly from Cognito so that the user is not presented with the modal when the attribute is set to custom:initiallogin = 'No'.这将直接从 Cognito 检索更新的属性,以便在属性设置为 custom:initiallogin = 'No' 时不会向用户显示模态。

Not sure if this is correct, but you should have a state in your context that decides if it should show the modal or not.不确定这是否正确,但您应该在您的上下文中有一个状态来决定它是否应该显示模态。

function App() {
  const [shouldShowModal, setShouldShowModal] = useState(true) // you can choose if you want true or false
  const {
    state: { user },
    handleSignout
  } = useAmplifyAuth();

  return (
    <>
      <AuthProvider value={{ user, handleSignout, shouldShowModal, setShouldShowModal }}>
        <>
          <Header />
          <Routes />
          <InitialLoginModal />
        </>
      </AuthProvider>
    </>
  );
}

And the default value of the showModal should be from the context.showModal的默认值应该来自上下文。

const InitialLoginModal = () => {
  const { user, handleSignout, shouldShowModal, setShouldShowModal } = useContext(AuthContext);

  const [showModal, setShowModal] = useState(shouldShowModal );

  ...

}

And then you can do some more things with setShouldShowModal so when you get the authentication, you choose if it should show or not.然后你可以用setShouldShowModal做更多的事情,所以当你获得身份验证时,你可以选择它是否应该显示。

在需要访问数据的应用客户端的 Cognito AWS 界面中选择“oauth scopes”下的“profile”。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM