Abstracting with react hooks on LSD

3. The final one, useBussinessLogic hook

Hooks are free of cost. i.e., you can make them really easily, and the only cost there is, is the cost of abstraction.

3.1 A basic useTodos hook

Now our components, they don't always just interact with the local state, most times they will be interacting with state on the server and managing async operations. And that's where the lines get blurry. So how about we put our hands in the magic pocket and try seeing if we have something that will help us?

Let's take an example of a basic to-do app, you'd be having a list of to-dos calling the APIs for getting it all that fun stuff, so let's extract it in a hook.

const useTodos = () => {
    const todos = useTodosStore()
    const [isLoading, setIsLoading] = useState(false)
    const [error, setError] = useState(null)

    const fetchTodos = useCallback(async () => {
        setIsLoading(true)

        try {
            const { data: todos } = await axios.get("/api/todos")
            setTodos(todos)
            setError(null)
        } catch(e) {
            setError(e)
        }

        setIsLoading(false)
    })

    useEffect(() => {
        fetchTodos()
    }, [fetchTodos])

    return {
        todos,
        fetch: fetchTodos,
        isLoading: false,
        error: null
    }
}

If we need to change something, we can just change this small function, and it works everywhere as long as it returns the same object. Now we can just use this with one line of code wherever we want.

const App = () => {
    const { todos, isLoading, error } = useTodos()

    // other stuff
}

3.2 Mutating to-dos

Now, let's say we want to toggle the state of a to-do. What do we do? We just put or hands in the custom hooks doremon pocket and bring out useToggleTodo

const useToggleTodos = () => {
    const [isLoading, setIsLoading] = useState(false)
    const [error, setError] = useState(null)

    const toggleTodo = useCallback(async todoId => {
        setIsLoading(true)

        try {
            const { data } = await axios.get(`/api/todos/${todoId}/toggle`)
            setError(null)
            setIsLoading(false)
            return data
        } catch(e) {
            setError(e)
        }

        setIsLoading(false)
    })

    return [toggleTodo, { isLoading, error }]
}

But wait, we also need to update things in our store and oh, what about having multiple useTodos. Do we have a global store or are all instances updated separately? What about race condition? And caching?

3.3 Doing it all right

Remember, our custom hooks can use other hooks too, so let's bring in useQuery from react-query

import { useQuery } from "react-query"

const fetchTodos = () => axios.get('/api/todos').then(res => res.data())

const useTodos() => {
    const { data: todos, isLoading, error } = useQuery('todos', fetchTodos)

    return { todos, isLoading, error }
}

And in our useToggleTodo we can use the useMutation from react-query so that our to-dos query is re-fetched whenever we toggle a to-do

import { useMutation } from "react-query"

const getToggleTodoById = todoId => axios.get(`/api/todos/${todoId}/toggle`)

const useToggleTodo = () => {
    return useMutation(getToggleTodoById, { refetchQueries: ["todos"] })
}

Look how we moved to using vanilla axios to react-query in seconds and didn't have to change more than a couple of lines. And now we have these nice hooks for our components to hooks into.

And my friends, that how we use hooks and manage like a pro (or from what all I know at least). Now you can go show off your new shiny gadgets to your friends if you have any.

17