New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
improve/extend async state for useAsync and related hooks #88
Comments
I'd say that there's no need for I've written my own const getData = (id: string) => {
return getDataByIdViaAPI(id);
}
const MyComponent: React.FunctionComponent<Props> = (props) => {
const { isLoading, isResult, isError, result, error, load } = useAsync(getData);
useEffect(() => {
load(props.id);
}, []);
return (
...
);
} It has additional And it doesn't run your promise automatically. That's why there is the Maybe one should then add an And maybe an Here's my implementation. There may be some optimizations left 😄 import { useState } from 'react';
export const useAsync = <T, ARGS extends any[]>(promise: (...args: ARGS) => Promise<T>) => {
const [state, setState] = useState({
isLoading: false,
isResult: false,
isError: false,
result: undefined as undefined | T,
error: undefined
});
const load = async (...args: ARGS) => {
setState({ isLoading: true, isResult: false, isError: false, result: undefined, error: undefined });
try {
const result = await promise(...args);
setState({ isLoading: false, isResult: true, isError: false, result: result, error: undefined });
} catch(error) {
setState({ isLoading: false, isResult: false, isError: true, result: undefined, error: error });
}
};
return {...state, load: load};
}; |
@benneq what do you think about replacing let state: 'peding' | 'error' | 'resolved'; |
That should be fine, too. I'm already a bit annoyed because there are so many props 😆 If you want to keep the Promise naming scheme, I'd say it should be called
What do you think about |
let state: 'idle' | 'pending' | 'rejected' | 'fulfilled'; 😄 |
Sounds reasonable! 👍 And what about the |
That's interesting, but that's a breaking change. Maybe could be a flag to have |
Yes, there should be some option / flag to enable/disable this. My version has
|
const useApi = (api_fn, variables) => {
const [state, set_state] = useState({ loading: !!variables, error: null, res: null });
const api = async (...variables) => {
set_state({ loading: true, error: null, res: state.res });
try {
const res = await api_fn(...variables);
set_state({ loading: false, error: null, res });
} catch (error) {
set_state({ loading: false, error, res: state.res });
throw error;
}
};
// if has variables, it loads at start even the variables is just an empty array []
useEffect(_ => !!variables && api(...variables), [...(variables || [])]);
return [api, state, set_state];
};
const data = new Array(100).fill(0).map(_ => Math.random());
const rest_api = n => new Promise(res => setTimeout(res, 1000)).then(_ => data.filter(d => d > n));
const MyComponent = props => {
const [n, set_n] = useState(0);
// useApi(rest_api, [n]) --- has variables, so it loads at start. If you just want an async api, useApi(rest_api)
const [search, search_state, set_search_state] = useApi(rest_api, [n]);
return (
<div>
<div>
<label>
number: <input value={n} onChange={e => set_n(parseFloat(e.target.value) || 0)} />
</label>
<button onClick={_ => search(n)}>research</button>
</div>
{search_state.loading && "loading..."}
{search_state.error && <Dialog content={search_state.error} onClose={_ => {
// you can manully set the state to close the error dialog, or use set_state as Apollo.Query.updateQuery
set_search_state({ ...search_state, error: null })
}} />}
<ul>
{(search_state.res || []).map(n => (
<li key={n}>{n}</li>
))}
</ul>
</div>
);
}; |
@xialvjun So the only real difference is the I'm not sure if this a common use case. But there might be a simple workaround: const [search, search_state, _] = useApi(rest_api, [n]);
const [showError, setShowError] = useState(false);
useEffect(() => {
if(search_state.error) {
setShowError(true);
}
}, [search_state.error]);
return (
// ...
{showError && <Dialog content={search_state.error} onClose={_ => setShowError(false)} />
// ...
) |
I found another possible issue that must be solved somehow: Concurrent requests. For example if you have some autocomplete / typeahead search box, that is powered by API.
My implementation currently doesn't respect this case 😞 But I have two ideas:
Another (maybe useful?!) option would be to provide another "option", that prohibits additional load calls, while it's already loading. But this could also be solved by simply using something like this:
|
And I also found a use case for I have a todo list, which uses Though it should be possible to call something like This could be worked-around by using a separate list for But... Additionally you can
EDIT: workaround isn't that easy, because I've got to trigger a state change within the component after running
|
When calling |
I'd say: It's a feature! 😄 Depending on your UX Styleguide, you can do some cool stuff with that. Of course you have to take care of it in your component. For example:
All I see there is: Having a lot of possibilities! In my current test project, there's a text box, where you can enter your search term (using your const { isLoading, result, load } = useAsync(getTasks);
const [filter, setFilter] = useState({});
useEffect(() => load(filter), [filter]);
const [searchTerm, setSearchTerm] = useState('');
useDebounce(() => setFilter({ ...filter, searchTerm: searchTerm }), 300, [searchTerm]);
return (
<>
<input value={searchTerm} onChange={e => setSearchTerm(e.target.value)} />
// display results / loading indicator
</>
) The only problem is when you have inconsistent slow internet connection: Then it's possible that you execute |
Another use case: Load more / infinite scroll. How would you handle this? I haven't tried it yet... but, is it possible to "concat" using hooks? Pseudocode / untested / fantasy: const { result, load } = useAsync(...);
const [combinedResults, setCombinedResults] = useState([]);
useEffect(() => {
if(result) { // will only trigger if we get new results ... hopefully?
setCombinedResults(...combinedResults, ...result);
}
}, [result]);
const loadNextPage = () => {
load(currentPage + 1);
}; And here we have a use case, where parallel / concurrent What we (means: you! 🤣 ) should keep in mind: Hooks are very easily composable. In other words: If it's possible to create a custom |
Hey guys, I used So I came up with the following re-implementation: https://gist.github.com/dfee/0686746c4b37ad45ba88b20a66a60432 and here's some example usage (I'm passing const [state, utils] = useAsync<WrappedFormUtils>(auth.signInWithEmail, {
onFinally: ({ context }) => {
if (context !== undefined) {
context.resetFields(["password"]);
}
},
});
const onSubmit: SignInFormProps["onSubmit"] = (e, form) => {
utils.setArgs([form.getFieldsValue()]);
utils.setContext(form);
}; I'm sharing this here because I think my solution is effective, but might be improved by the community, and perhaps merged in to replace the current |
useAsync
, useAsyncFn
and useAsyncRetry
useAsync
, useAsyncFn
and useAsyncRetry
So I have tackled this problem. I am not sure whether it is feasible to extend the useAsync hook or provide another one that has an API to [state, setState]. Below is the difference in the API that useAsync hook exposes using a trivial todo example:
const {
loading,
error,
value
} = useAsync(async () => {
const result = await apiClient.get('/todos');
return result;
}, []);
// todos cannot be modified locally
const {
loading,
error,
data: [todos, setTodos]
} = useAsync(async () => {
const result = await apiClient.get('/todos');
return result;
}, []);
// This is will work with the local state of todos
setTodos(todos.filter(todo => todo.type !== 'done')) I can do a pull request but I haven't really read through the contributing guidelines and Typescript can be a problem since I have not used it before. Let me know what you think. |
just like
Query Mutation in react-apollo
:And now we have
useAsync
, but it can only act asQuery
.We should have an
useApi
to act asMutation
.The text was updated successfully, but these errors were encountered: