# Dependency Inversion Principle (DIP)

Vamos con último de los principio SOLID que no es otro que el Principio de Inversión de Dependencias (Dependency Inversion Principle, DIP) que se corresponde con la **D** de SOLID.

Este principio nos viene a decir que nuestros componentes, nuestras clases, nuestras funciones deberían depender de abstracciones y no de implementaciones concretas. Una vez más esta definición nos puede sonar muy genérica por lo que como vamos a partir de un ejemplo:

```typescript
import useSWR from 'swr'

const fetcher = async (url: string) => {
  const res = await fetch(url)
  return res.json()
}

export const Todo = () => {
  const { data } = useSWR('http://jsonplaceholder.typicode.com/todos', fetcher)

  if (!data) {
    return <p>loading...</p>
  }

  return (
    <ul>
      {data.map(todo: any) => {
        return (
          <li key={todo.id}>
            <span>{todo.id}</span>
            <span>{todo.title}</span>
          </li>
        )
      }}
    </ul>
  )
}
```

El código del componente anterior estaría bien desde el punto de vista funcional porque efectivamente está haciendo lo que se espera que haga que no es otra cosa que traerse una lista de *todo* (tareas que se tienen que realizar) y renderizarlas en la página.

Ahora bien, si nos fijamos bien veremos que el componente en cuestión tiene una dependicia con `useSWR` que no es más que un custom hook que implementa de una forma concreta la conectividad con una API para traerse información el cual necesita de un *fetcher* que nosotros le tenemos que pasar y del cuál conocemos el código. Pero es más, es el componente el que tiene que saber la URL a la que tiene que acudir para traerse dicha información cosa que no parece del todo que esté bien.

¿Qué podemos hacer para arreglar esto? Pues deberíamos crear una abstracción que no dependiense para nada de saber la url de la API a la que se tiene que conctar para poder traerse la información de los *todo* ni tampoco de es necesario el *fetcher* para el correcto funcionamiento de dicha comunicación (aunque en última instancia el saber que vamos a necesitar un *fetcher* para podernos traer los datos es algo que sí que vamos a necesitar como veremos un poco más adelante).

¿Y cómo podemos extraer toda (abstraer) toda esta información del componente `Todo`? Pues gracias al uso de un custom hook.

### Custom hook

Teniendo en cuenta lo que acabamos de mencionar lo que vamos a hacer es crear el fichero `useData.ts` en el que vamos a crear nuestro custom hook que nos ayude a cumplir con el DIP. Dentro de este hook lo primero que haremos será importar la librería `swr` porque es la que vamos a usar para realizar la comunicación con la API:

```typescript
import swr from 'swr'
```

Ahora vamos a definir la interfaz que nos servirá para definir los parámetros que le pasaremos a nuestro hook y para ello es necesario que conozcamos muy a alto nivel como funciona `swr` puesto que es quién va a definir qué es lo que necesitamos. Así necesitaremos una `key` para establecer la clave dentro de la caché de resultados de la consulta a la API que gestiona `swr` donde se guardarán los resultados de nuestra consulta a la API (generalmente esta `key` será el segmento de la URL de la API a la que tenemos que lanzar nuestra consulta) y un *fetcher* que no es más que un callback que se ejecutará para obtener la información que se ha de guardar dentro de esa clave de la cache.

> **Nota:** para todos aquellos que estén más interesados en saber cómo funciona `swr` le recomendamos que se vayan a la [página oficial del hook](https://swr.vercel.app/es-ES)

Es más, como no tenemos ni idea del tipo de datos que se guardará en cada una de las claves de la caché que se puedan ir generando lo que haremos será hacer uso de un genérico de TypeScript en la definición para así que poder tipar mejor nuestros resultados.

En definitiva que definimos el siguiente tipo:

```typescript
interface UseDataArgs<T> {
  key: string
  fetcher: () => Promise<T>
}
```

¿Qué estamos definiendo con esto? Pues si lo pensamos detenidamente gracias a esta interface lo que estamos declarando es la abstracción que servirá para que le podamos comunicar a nuestro custom hook `useData` cuáles son los tipos de datos con los que va a tener que trabajar tratando de cumplir el DIP.

Es más llevando esta idea a la respuesta que nos proporcionará nuestro hook vamos a definir la interfaz en la que se declarará el tipo de datos con el que nos responderá la ejecución de nuesto hook que será la información que se ha obtenido, el posible error que se haya producido y toda aquella información adicional que nosotros consideremos necesaria para poder llevar a cabo nuestro proceso. Por ejemplo, nosotros vamos a hacer que además de los datos y el error esperamos recibir un *boolean* donde se nos diga si nuestros datos se están validados o no.

```typescript
interface Response<T> {
  data: T | undefined
  error: string | undefined
  isValidating: boolean
}
```

¿Qué logramos con todo esto? Pues en vez de que nuestro componente `Todo` tenga que saber que tiene que proporcionar unos datos concretos para una implementación concreta del código que se encargará de traer la información con la que se está trabajando lo que estamos haciendo es separarlo para poderlo cambiar en el caso de que sea necesario y así que `Todo` pueda hacer uso de una implementación totalmente diferente del proceso/librería para traerse la información de la API.

> **Nota:** no nos quedemos únicamente con el hecho de que podemos cambiar la implementación de la comunicación con la API puesto que si el DIP está bien implementado al componente `Todo` le tiene que dar igual dónde está la información de los *todo* que tiene que implenentar (podría estar, por ejemplo, en el localstorage en vez de la API) sino que simplemente lo que hará será pedir los *todo* que tiene que renderizar y mostrarlos.

Ahora ya podemos comenzar con la implementación de `useData` que definimos de la siguiente manera:

```typescript
export const useData = <T>({ key, fetcher }: UseDataArgs<T>): Response<T> => {}
```

y tras la definición del mismo vamos a podernos centrar en la implementación propia del código que se ejecutará dentro de nuestro custom hook usando `swr`:

```typescript
export const useData = <T>({ key, fetcher }: UseDataArgs<T>): Response<T> => {
  const { data, error, isValidating } = useSWR<T, string>(key, fetcher)
  return { data, error, isValidating }
}
```

El código completo del custom hook `useData` después de todos los pasos que acabamos de mencionar sería algo parecido a lo que podemos ver a continuación:

```typescript
import swr from 'swr'

interface UseDataArgs<T> {
  key: string
  fetcher: () => Promise<T>
}

interface Response<T> {
  data: T | undefined
  error: string | undefined
  isValidating: boolean
}

export const useData = <T>({ key, fetcher }: UseDataArgs<T>): Response<T> => {
  const { data, error, isValidating } = useSWR<T, string>(key, fetcher)
  return { data, error, isValidating }
}
```

Así pues hemos creado una abstacción porque aunque estamos usando `swr` para comunicarnos con la API de tal manera que los componentes que necesiten los *todo* (como es el caso del componente `Todo` del que hemos partido) van a poder utilizarlo sin que en ningún momento tengan la necesidad de saber cómo se va a realizar el fetch de los datos.

Volviendo por lo tanto al código de nuestro componente `Todo` lo que vamos a hacer es definir un nuevo tipo de datos que nos sirva para poder definir cómo son los datos que esperamos obtener en la respuesta de la API (es decir cuál es el tipo que está asociado a cada uno de los *todo*):

```typescript
type ResponseType {
  id: number
  title: string
}
```

> **Nota:** lo seguimos dejando lo más sencillo posible haciendo que para el componente `Todo` la respuesta que se espera recibir de la API sean objetos formados por el atributo `id` con el identificador del *todo* y `title` con el títulod el *todo*.

Con estos cambios vamos a ver cómo queda el código completo de nuestro componente para que pase a usar el hook `useData`:

```typescript
import { useData } from './useData.ts'

type ResponseType {
  id: number
  title: string
}

const fetcherFromApi = async (): Promise<ResponseType[]> => {
  const url = 'http://jsonplaceholder.typicode.com/todos'
  const res = await fetch(url)
  return res.json()
}

export const Todo = () => {
  const { data } = useData<ResponseType[]>({
    key: '/todos',
    fetcher: fetcherFromApi
  })

  if (!data) {
    return <p>loading...</p>
  }

  return (
    <ul>
      {data.map(todo: any) => {
        return (
          <li key={todo.id}>
            <span>{todo.id}</span>
            <span>{todo.title}</span>
          </li>
        )
      }}
    </ul>
  )
}
```

En el código anterior tenemos que fijarnos que ahora ya no tenemos la función `fetcher` que teníamos originalmente donde se esperaba recibir como parámetro la `url` de la API a la que teníamos que conectarnos para poder obtener la respuesta y en su lugar hemos definido la función `fetcherFromApi` y es esta función la que tiene como responsabilidad saber la API a la que se tiene que conectar para poder obtener la información de los *todo*.

### ¿Dónde está la clave de todo esto?

Pues nos tenemos que fijar en la definición de la interfaz `UseDataArgs<T>` porque es aquí donde le estamos inyectando a nuestro custom hook cómo vamos a realizar el fetching de los datos sin saber de dónde lo va a sacar (en nuestro ejemplo de una API) siendo lo importante de verdad desde el punto de vista de la abstracción que la respuesta que retorne este *fetcher* ha de ser una promisa del tipo genérico `T`.

En nuestro ejemplo la `T` que le estamos pasando a `useData` es un array de objetos `ResponseType` por lo que al final lo que tendremos en el atributo `data` del objeto con el que response el custom hook lo que tendríamos sería un array de objetos que están formados por un `id` y un `title` (la definición recogida en el tipo `ResponseType`).

> **Nota:** cada uno de los *fetcher* que vayamos creando se considera una buena práctica separarlo en su propio archivo afuera del componente donde se está utilizando para que pueda llegar a ser reutilizado en más sitios siempre que se diese ese caso.

### Otro `fetcher`

Veamos ahora cómo podemos crear otro *fetcher* y para ello lo que tenemos que aseguirar es qeue sea una función que cumpla con la interfaz (es decir, en nuestro caso que retorne un array de objetos `ResponseType`) en donde puede sacar la información de un objeto:

```typescript
const fetchFromObject = async (): Promise<ResponseType[]> => {
  return [
    { id: 1, title: 'Hello' },
    { id: 2, title: 'World' }
  ]
}
```

y ahora simplemente en el código del componente cuando llamamos al custom hook `useData` escribiríamos algo como:

```typescript
const { data } = useData<ResponseType[]>({
  key: '/todos',
  fetcher: fetchFromObject
})
```

O bien podríamos usar un *fetcher* que obtenga la información de los *todo* del localstorage como sigue:

```typescript
const fetchFromLocalStorage = async (): Promise<ResponseType[]> => {
  const todos = localStorage.getItems('todos')
  return todos ? JSON.parse(todos) : []
}
```

y nuevamente usarlo como sigue en el código del componente `Todo`:

```typescript
const { data } = useData<ResponseType[]>({
  key: '/todos',
  fetcher: fetchFromLocalStorage
})
```

Lo realmente interesante aquí es independientemente de cual sea el *fetcher* que se vaya a utilizar a la hora de llamar a `useData` en ningún momento vamos a tener que modificar ninguna otra parte del código y todo funcionaría correctamente.

> **Importante:** cuando estamos definiendo los diferentes tipos de *fetcher* en nuestro código los pasamos a usar con `useData` lo que estamos haciendo es inyectar la dependencia de este *fetcher* de forma que todo nuestro código es independiente del lugar del que nos extraemos los datos.

Es más podríamos ir una paso más adelante y hacer que el componente `Todo` no necesite ni tan siguiera saber nada del *fetcher* esperando recibirlo como una prop:

```typescript
export const Todo = ({ fetcher }) => {
  const { data } = useData<ResponseType[]>({
    key: '/todos',
    fetcher
  })
  // ..... resto del código .....
```

o incluso que este *fetcher* saliense del context de React haciendo algo como lo siguiente:

```typescript
export const Todo = ({ fetcher }) => {
  const { fetchTodoFromApi: fetcher } = useContext(ContextTodo)

  const { data } = useData<ResponseType[]>({
    key: '/todos',
    fetcher
  })
  // ..... resto del código .....
```

> **Nota:** obviamente el código anterior está muy simplicado porque lo que es importante aquí es que seamos capaces de entender la potencia de este principio no centrarnos en cómo funciona el context de React.

Es más, podemos ir un paso más allá y definir algo así como un context de React general para toda nuestra aplicación (todos los componentes que la forman tendrían acceso al mismo) el cual podríamos recuperar con un custom hook al que podríamos llamar `useGlobalContext` ahora bien, dentro de este contexto global podemos tener el atributo `fetchTodos` donde le asignáriamos el *fetcher* global a toda la aplicación de tal manera que en el caso de tener que cambiarlo solamente lo tuviésemos que hacer en un único sitio, facilitándonos mucho el mantenimiento de nuestro código:

```typescript
export const Todo = ({ fetcher }) => {
  const { fetchTodos: fetcher } = useGlobalContext()

  const { data } = useData<ResponseType[]>({
    key: '/todos',
    fetcher
  })
  // ..... resto del código .....
```

> **Nota:** de esta manera nosotros como desarrolladores de los componentes de la aplicación cuando estamos haciendo uso del *fetcher* no nos estamos enterando de dónde se obtiene la información (no sabemos si se va a una API, si se va al localstorage, si sale de las cookies, si es un valor mockeado, etc.) siendo esto lo que hace que la inyección de dependencias sea tan sumamente potente.
