结合React useContext和一次性数据加载(React钩子)的最佳实践?

时间:2019-09-16 02:19:20

标签: reactjs react-hooks

我有一个由几个输入组件组成的表格。表单数据通过React上下文和React钩子useContext在这些兄弟组件之间共享和共享。

我正在为如何有选择地将数据异步加载到同一上下文中而苦恼。例如,如果浏览器URL为example.com/form,则表单可以使用FormContext提供的默认值加载(没问题)。但是,如果用户通过导航到example.com/form/:username/:form-id返回完成先前编辑过的表单,则应用程序应该使用这两个数据点来获取数据。为了覆盖默认的空表单初始值,大概必须在FormContext内发生这种情况。

  1. URL参数甚至可以用于React.createContext函数吗?
  2. 如果是,hooks are not to be used with in a conditional时如何处理可选的数据提取要求?
  3. 如何确保数据获取仅发生一次?
  4. 最后,该表单还将当前状态保存到本地存储中,以便在刷新时持久保存值。如果实现了数据获取,刷新应该加载异步数据还是本地存储?我倾向于使用本地存储,因为它更可能代表用户上次的体验。我对实施持开放态度。

FormContext

export const FormContext = React.createContext();
export const FormProvider = props => {
  const defaultFormValues = {
    firstName: "",
    lastName: "",
    whatever: "",
  };
  const [form, setForm] = useLocalStorage(
    "form",
    defaultFormValues
  );
  return (
    <FormContext.Provider value={{ form, setForm }}>
      {props.children}
    </FormContext.Provider>
  );
};

Reference for useLocalStorage

2 个答案:

答案 0 :(得分:1)

我认为您要寻找的答案是Redux,而不是库,而是工作流程。我确实发现很好奇,React没有对此提供更多指导。我不确定其他人在做什么,但这就是我想出的。

首先,我确保将来自useReducer的分派添加到上下文中。这是该接口:

export interface IContextWithDispatch<T> {
  context: T;
  dispatch: Dispatch<IAction>;
}

然后给出此上下文:

export interface IUserContext {
  username: string;
  email: string;
  password: string;
  isLoggedIn: boolean;
}

我可以这样做:

export const UserContext = createContext<IContextWithDispatch<IUserContext>>({
  context: initialUserContext,
  dispatch: () => {
    return initialUserContext;
  },
});

在顶层组件中,我记住上下文,因为我只想要一个实例。这就是我把它们放在一起的方式

import memoize from 'lodash/memoize';
import {
  IAction,
  IContextWithDispatch,
  initialUserContext,
  IUserContext,
} from './domain';

const getCtx = memoize(
  ([context, dispatch]: [IUserContext, React.Dispatch<IAction>]) =>
    ({ context, dispatch } as IContextWithDispatch<IUserContext>),
);
const UserProvider = ({ children }) => {
  const userContext = getCtx(useReducer(userReducer, initialUserContext)) as IContextWithDispatch<
      IUserContext
      >;
  useEffect(() => {
    // api call to fetch user info
  }, []);
  return <UserContext.Provider value={userContext}>{children}</UserContext.Provider>;
};

您的userReducer将响应所有dispatch的调用,并且可以进行API调用或调用其他服务来执行此操作等。reducer处理对context的所有更改。 一个简单的reducer可能看起来像这样:

export default (user, action) => {
  switch (action.type) {
    case 'SAVE_USER':
      return {
        ...user,
        isLoggedIn: true,
        data: action.payload,
      }
    case 'LOGOUT':
      return {
        ...user,
        isLoggedIn: false,
        data: {},
      }
    default:
      return user
  }
}

我现在可以在组件中执行此操作:

const { context, dispatch } = useContext<IContextWithDispatch<IUserContext>>(UserContext);

其中UserContext是从上面定义的export导入的。

在您的情况下,如果您的路线example.com/form/:username/:form-id没有它需要的数据,则可以dispatchaction并听取上下文以了解该操作的结果。您的reducer可以进行任何必要的api调用,并且您的组件不需要了解任何有关它的信息。

答案 1 :(得分:0)

设法完成了我想要的大部分工作。以下是说明最终产品基本概念的要点:https://gist.github.com/kwhitejr/df3082d2a56a00b7b75365110216b395

很高兴收到反馈!