在渲染之前加载 state/data?

Load state/data before render?

我已经实现了我的应用程序的身份验证部分(使用 MERN 堆栈构建)。登录操作验证登录数据,然后加载用户数据,然后将路由推送到 /dashboard。在仪表板页面上,我有一个简单的 Welcome to the dashboard, {email}! 但是我收到一条错误消息,告诉我它不能 return 空数据。我还在导航栏中有用户的名字和姓氏以及他们的电子邮件,这也会产生 returning 空数据的错误。我有一个 useEffect 可以在我的 App.js 中加载用户数据,但我仍然收到空错误。

有没有办法在渲染之前加载数据?

Index.js

ReactDOM.render(
  <Provider store={store}>
    <PersistGate loading={null} persistor={persistor}>
      <ConnectedRouter history={history}>
        <App />
      </ConnectedRouter>
    </PersistGate>
  </Provider>,
  document.getElementById('root')
);

App.js

const App = () => {
  const [loading, setLoading] = useState(true);
  const dispatch = useDispatch();

  useEffect(() => {
    // check for token in LS
    if (localStorage.token) {
      setAuthToken(localStorage.token);
    }
    dispatch(attemptGetUser())
      .then(() => setLoading(false))
      .catch(() => setLoading(false));

    // Logout user from all tabs if they logout in another tab
    window.addEventListener('storage', () => {
      if (!localStorage.token) dispatch({ type: LOGOUT });
    });

    // eslint-disable-next-line
  }, []);

  return loading ? (
    <Loading cover="page" />
  ) : (
    <div className="App">
      <Switch>
        <Route path="/" component={Views} />
      </Switch>
    </div>
  );
};

redux/thunks/Auth.js

export const attemptLogin = (formData) => async (dispatch) => {
  await postLogin(formData)
    .then((res) => {
      dispatch(login(res.data));
      dispatch(push('/dashboard'));
    })
    .then(() => {
      dispatch(attemptGetUser());
    })
    .catch((error) => {
      const errors = error.response.data.message;
      dispatch(setAlert('Uh-oh!', errors, 'error'));
    });
};

redux/thunks/User.js

export const attemptGetUser = () => async (dispatch) => {
  await getUser()
    .then((res) => {
      dispatch(setUser(res.data));
    })
    .catch((error) => {
      const errors = error.response.data.message;
      console.log(errors);
      dispatch(setAlert('Uh-oh!', errors, 'danger'));
    });
};

views/app-views/dashboard

const Dashboard = () => {
  const { email } = useSelector((state) => state.user.user);
  return (
    <div>
      Welcome to the dashboard,
      <strong>{email}</strong>!
    </div>
  );
};

export default Dashboard;

components/layout-components/NavProfile.js

export const NavProfile = () => {
  const { firstName, lastName, email } = useSelector(
    (state) => state.user.user
  );

  const dispatch = useDispatch();

  const onLogout = () => {
    dispatch(attemptLogout());
  };

  const profileImg = '/img/avatars/thumb-1.jpg';
  const profileMenu = (
    <div className="nav-profile nav-dropdown">
      <div className="nav-profile-header">
        <div className="d-flex">
          <Avatar size={45} src={profileImg} />
          <div className="pl-3">
            <h4 className="mb-0">{firstName} {lastName}</h4>
            <span className="text-muted">{email}</span>
          </div>
        </div>
      </div>
      <div className="nav-profile-body">
        <Menu>
          {menuItem.map((el, i) => {
            return (
              <Menu.Item key={i}>
                <a href={el.path}>
                  <Icon className="mr-3" type={el.icon} />
                  <span className="font-weight-normal">{el.title}</span>
                </a>
              </Menu.Item>
            );
          })}
          <Menu.Item key={menuItem.legth + 1} onClick={onLogout}>
            <span>
              <LogoutOutlined className="mr-3" />
              <span className="font-weight-normal">Logout</span>
            </span>
          </Menu.Item>
        </Menu>
      </div>
    </div>
  );
  return (
    <Dropdown placement="bottomRight" overlay={profileMenu} trigger={['click']}>
      <Menu className="d-flex align-item-center" mode="horizontal">
        <Menu.Item>
          <Avatar src={profileImg} />
        </Menu.Item>
      </Menu>
    </Dropdown>
  );
};

export default NavProfile;

看来您可以通过更改 Redux 存储初始状态来解决此问题。

以您的 Dashboard 组件为例:

const Dashboard = () => {
  const { email } = useSelector((state) => state.user.user);
  return (
    <div>
      Welcome to the dashboard,
      <strong>{email}</strong>!
    </div>
  );
};

它期望在您的 Redux 存储的用户状态切片中有一个带有电子邮件字符串的用户对象。作为noted in their documentation

您可以更新 createStore 调用以包含 redux 存储的初始值,例如 createStore({'user': {'user': {'email': ''}}});

所以错误告诉你,在你的 redux 状态下,state.user.user 是未定义的,这就是为什么你不能解构 firstNamelastNameemail值。

如果在您的商店中 state.user.user 至少是一个已定义的空对象 ({}),则应解决 null 错误的访问。

const userReducer = (state = { user: {} }, action) => {
  ...
}

这仍然可能使您的 UI 呈现“未定义”,因此在组件代码中您需要提供默认值,即

const { firstName = '', lastName = '', email = '' } = useSelector(
  (state) => state.user.user
);

另一种方法是在您的用户减速器切片中拥有完全合格的初始状态。

const initialState = {
  user: {
    firstName: '',
    lastName: '',
    email: '',
  },
};

const userReducer = (state = initialState, action) => {
  ...
}