Last active
February 17, 2023 11:13
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }; | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react'; | |
import { storesContext } from '../store'; | |
export const useStores = () => React.useContext(storesContext); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
It's called
useFetch
, but what it actually does is take a MobX (action?) function and run it with convenienterror
andloading
props.