Skip to content

Instantly share code, notes, and snippets.

@ckkz-it
Last active February 17, 2023 11:13
Show Gist options
  • Save ckkz-it/2e353e7ca8e57450dedfcffadf1bce5a to your computer and use it in GitHub Desktop.
Save ckkz-it/2e353e7ca8e57450dedfcffadf1bce5a to your computer and use it in GitHub Desktop.
useFetch React Hook for MobX (v6+ or mobx-react-lite) with "real" project structure and some usage examples
import axios from 'axios';
export class SomeApi {
async getAll(): Promise<any[]> {
const { data } = await axios.get('/items'); // `fetch` could be used here
return data;
}
async update(id: number, itemData: any) {
const { data } = await axios.put(`/items/${id}`, itemData);
return data;
}
}
import React from 'react';
import { observer } from 'mobx-react-lite';
import { useStores } from '../hooks/use-stores';
import { useFetch } from '../hooks/use-fetch';
const Component1: React.FC = observer(() => {
const { someStore } = useStores();
const { error, fetched } = useFetch<void>({ fetchFn: someStore.fetchAll });
if (error) {
return <div>{error?.message}</div>;
}
if (!fetched) {
return <div>Loading...</div>;
}
return (
<div>
{someStore.items.map((item) => ( // or result.map(...) if your action function returns a value
<div key={item.id}>{item.title}</div>
))}
</div>
);
});
export default Component1;
import React from 'react';
import { observer } from 'mobx-react-lite';
import { useStores } from '../hooks/use-stores';
import { useFetch } from '../hooks/use-fetch';
const Component2: React.FC = observer(() => {
const { someStore } = useStores();
const { result, error, makeRequest, loading } = useFetch<any[]>({
fetchFn: someStore.fetchAll,
immediate: false,
});
if (error) {
return <div>{error?.message}</div>;
}
if (loading) {
return <div>Loading...</div>;
}
if (!fetched) {
return <button onClick={() => makeRequest()}>Fetch Items</button>
}
return (
<div>
{result && result.map((item) => ( // or store.items.map(...)
<div key={item.id}>{item.title}</div>
))}
</div>
);
});
export default Component2;
import React from 'react';
import { observer } from 'mobx-react-lite';
import { useStores } from '../hooks/use-stores';
import { useFetch } from '../hooks/use-fetch';
type Props = { item: any }
const Component3: React.FC<Props> = observer(({ item }) => {
const { someStore } = useStores();
const { error, makeRequest, loading } = useFetch<any[]>({
fetchFn: someStore.update,
immediate: false,
// fnArgs: [item.id, 'New Title'], // You can specify args here as well, then just call `makeRequest()`
// fnArgs: [item.id], // Or pass only some arguments (e.g. which you already know at this time)
});
const updateTitle = () => {
makeRequest(item.id, 'New Title'); // Pass args to your initial "fetch" function (`someStore.update` here)
// makeRequest(); // If `fnArgs` were already specified OR
// makeRequest('New Title'); // If first argument (`id` here) was already specified, but `data` were not
}
if (error) {
return <div>{error?.message}</div>;
}
if (loading) {
return <div>Loading...</div>;
}
if (!fetched) {
return <button onClick={() => updateTitle()}>Update Item's Title</button>
}
return (
<div>
{item.id} {item.title}
</div>
);
});
export default Component3;
import { useCallback, useEffect, useState } from 'react';
type useFetchState<T = any> = { result: T; error: any; loading: boolean; fetched: boolean };
type useFetchArgs<T> = {
fetchFn: (...args: any[]) => Promise<T>;
fnArgs?: any[];
immediate?: boolean;
};
const emptyArgs: any[] = [];
export const useFetch = <T>({ fetchFn, fnArgs = emptyArgs, immediate = true }: useFetchArgs<T>) => {
const [state, setState] = useState<useFetchState<T>>({
result: null,
error: null,
loading: false,
fetched: false,
});
const fetchRequest = useCallback(
async (...extraArgs: any[]) => {
setState((s) => ({ ...s, loading: true }));
try {
const data = await fetchFn(...fnArgs, ...extraArgs);
if (data !== undefined) {
setState((s) => ({ ...s, result: data }));
}
} catch (err) {
setState((s) => ({ ...s, error: err }));
} finally {
setState((s) => ({ ...s, fetched: true, loading: false }));
}
},
[fetchFn, fnArgs],
);
const makeRequest = (...extraArgs: any[]) => fetchRequest(...extraArgs);
useEffect(() => {
if (!state.fetched && immediate) {
fetchRequest();
}
}, [state.fetched, fetchRequest, immediate, fnArgs]);
return { ...state, makeRequest };
};
import React from 'react';
import { storesContext } from '../store';
export const useStores = () => React.useContext(storesContext);
import React from 'react';
import { SomeApi } from '../apis/some-api';
import { SomeStore } from './some-store';
const someApi = new SomeApi();
const someStore = new SomeStore(someApi);
export const storesContext = React.createContext({
someStore,
});
import { action, observable } from 'mobx';
import { SomeApi } from '../apis/some-api';
export class SomeStore {
@observable public items: any[] = null;
constructor(private api: SomeApi) {}
@action setDrawSources = (items: any[]) => {
this.setItems = items;
};
fetchAll = async () => {
const items = await this.api.getAll();
this.setItems(items);
return items; // can be omitted if you use `store.items` directly in component
};
update = async (id: number, data: any) => {
const updatedItem = await this.api.update(id, data);
const updatedItems = this.items.map((item) =>
item.id === id ? updatedItem : item,
);
this.setItems(updatedDrawSources);
}
}
@ckkz-it
Copy link
Author

ckkz-it commented Apr 8, 2020

It's called useFetch, but what it actually does is take a MobX (action?) function and run it with convenient error and loading props.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment