Solución:
Este es un caso interesante que el useReducer
los ejemplos no tocan. No creo que el reductor sea el lugar adecuado para cargar de forma asincrónica. Viniendo de una mentalidad de Redux, normalmente cargaría los datos en otro lugar, ya sea en un procesador, un observable (por ejemplo, redux-observable), o simplemente en un evento del ciclo de vida como componentDidMount
. Con el nuevo useReducer
podríamos usar el componentDidMount
enfoque usando useEffect
. Su efecto puede ser algo como el siguiente:
function ProfileContextProvider(props) {
let [profile, profileR] = React.useReducer(reducer, initialState);
useEffect(() => {
reloadProfile().then((profileData) => {
profileR({
type: "profileReady",
payload: profileData
});
});
}, []); // The empty array causes this effect to only run on mount
return (
<ProfileContext.Provider value={{ profile, profileR }}>
{props.children}
</ProfileContext.Provider>
);
}
Además, ejemplo de trabajo aquí: https://codesandbox.io/s/r4ml2x864m.
Si necesita pasar un apoyo o un estado a su reloadProfile
función, puede hacerlo ajustando el segundo argumento a useEffect
(la matriz vacía en el ejemplo) para que se ejecute solo cuando sea necesario. Debería verificar el valor anterior o implementar algún tipo de caché para evitar buscar cuando sea innecesario.
Actualización: recargar desde el niño
Si desea poder recargar desde un componente secundario, hay un par de formas de hacerlo. La primera opción es pasar una devolución de llamada al componente secundario que activará el envío. Esto se puede hacer a través del proveedor de contexto o un componente prop. Dado que ya está utilizando el proveedor de contexto, aquí hay un ejemplo de ese método:
function ProfileContextProvider(props) {
let [profile, profileR] = React.useReducer(reducer, initialState);
const onReloadNeeded = useCallback(async () => {
const profileData = await reloadProfile();
profileR({
type: "profileReady",
payload: profileData
});
}, []); // The empty array causes this callback to only be created once per component instance
useEffect(() => {
onReloadNeeded();
}, []); // The empty array causes this effect to only run on mount
return (
<ProfileContext.Provider value={{ onReloadNeeded, profile }}>
{props.children}
</ProfileContext.Provider>
);
}
Si tu De Verdad desea utilizar la función de envío en lugar de una devolución de llamada explícita, puede hacerlo envolviendo el envío en una función de orden superior que maneje las acciones especiales que habrían sido manejadas por el middleware en el mundo Redux. He aquí un ejemplo de eso. Note que en lugar de pasar profileR
directamente al proveedor de contexto, pasamos el personalizado que actúa como un middleware, interceptando acciones especiales que al reductor no le importan.
function ProfileContextProvider(props) {
let [profile, profileR] = React.useReducer(reducer, initialState);
const customDispatch= useCallback(async (action) => {
switch (action.type) {
case "reload": {
const profileData = await reloadProfile();
profileR({
type: "profileReady",
payload: profileData
});
break;
}
default:
// Not a special case, dispatch the action
profileR(action);
}
}, []); // The empty array causes this callback to only be created once per component instance
return (
<ProfileContext.Provider value={{ profile, profileR: customDispatch }}>
{props.children}
</ProfileContext.Provider>
);
}
Escribí una explicación muy detallada del problema y las posibles soluciones. Dan Abramov sugirió la Solución 3.
Nota: Los ejemplos en la esencia proporcionan ejemplos con operaciones de archivos, pero el mismo enfoque podría implementarse para la búsqueda de datos.
https://gist.github.com/astoilkov/013c513e33fe95fa8846348038d8fe42
Envolví el método de envío con una capa para resolver el problema de la acción asincrónica.
Aquí está el estado inicial. El loading
clave para registrar el estado de carga actual de la aplicación, es conveniente cuando desea mostrar la página de carga cuando la aplicación está obteniendo datos del servidor.
{
value: 0,
loading: false
}
Hay cuatro tipos de acciones.
function reducer(state, action) {
switch (action.type) {
case "click_async":
case "click_sync":
return { ...state, value: action.payload };
case "loading_start":
return { ...state, loading: true };
case "loading_end":
return { ...state, loading: false };
default:
throw new Error();
}
}
function isPromise(obj) {
return (
!!obj &&
(typeof obj === "object" || typeof obj === "function") &&
typeof obj.then === "function"
);
}
function wrapperDispatch(dispatch) {
return function(action) {
if (isPromise(action.payload)) {
dispatch({ type: "loading_start" });
action.payload.then(v => {
dispatch({ type: action.type, payload: v });
dispatch({ type: "loading_end" });
});
} else {
dispatch(action);
}
};
}
Supongamos que existe un método asincrónico
async function asyncFetch(p) {
return new Promise(resolve => {
setTimeout(() => {
resolve(p);
}, 1000);
});
}
wrapperDispatch(dispatch)({
type: "click_async",
payload: asyncFetch(new Date().getTime())
});
El código de ejemplo completo está aquí: