livewire 에서 api LengthAwarePaginator 데이터를 받아서 바로 사용하는 방법

Updated on

livewire 를 프론트엔드로 작업 하고 api endpoint 들은 백엔드 laravel 에서 분리 작업하는 경우들이 있다.

프론트엔드 livewire 에서는 최대한 api endpoint 에 있는데로 사용하길 원하는데, 이때 이 방법을 사용해서 하면 된다.

예전에 https://ggami.net/posts/234 해당 포스팅에 내용을 적긴 했었는데, 이 버전은 livewire v2 였었고, 지금은 livewire v3 이다. 생각보다 너무 많은 시간이 흘렀고, 많은 변경점들이 있었다.

아무튼 정리해보자면,

$items = $query->paginate(perPage: $perPage, columns: ['*'], page: $page, total: $total);

return LocalResource::collection($items);
return new LocalResource($items);

백엔드에서는 기존과 동일한 방식으로 pagination 을 만들어서 내려주면 된다.

use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Pagination\Paginator;

final class HelperUtil
{
    public static function makeLengthAwarePaginator(array|null $lengthAwarePaginatorArray, string $pageName = 'page'): LengthAwarePaginator|null
    {
        if (empty($lengthAwarePaginatorArray)) {
            return null;
        }

        $meta = $lengthAwarePaginatorArray['meta'] ?? [];
        $perPage = $lengthAwarePaginatorArray['per_page'] ?? $meta['per_page'] ?? 15;
        $total = $lengthAwarePaginatorArray['total'] ?? $meta['total'] ?? 0;
        $items = collect($lengthAwarePaginatorArray['data'] ?? []);

        $page = Paginator::resolveCurrentPage(pageName: $pageName, default: 1);
        $path = Paginator::resolveCurrentPath();

        $paginator = new LengthAwarePaginator(
            items: $items->forPage(1, $perPage),
            total: $total,
            perPage: $perPage,
            currentPage: $page,
        );

        if (!blank($path)) {
            $paginator = $paginator->setPath($path);
        }

        return $paginator;
    }
}

프론트엔드에서는 이렇게 makeLengthAwarePaginator 함수를 만들어서 사용해야한다.

예제를 더 작성하자면,

protected function requestGetLocalData(): LengthAwarePaginator|null
{
		try {
				$data = $this->client->getLocalData([
						...$this->filter,
						'page' => $this->page,
				]);
		} catch (ClientException $e) {
				if ($e->getCode() === 422) {
						$responseError = json_decode($e->getResponse()->getBody()->getContents(), true);
						$this->setErrorBag(new MessageBag($responseError['errors'] ?? []));
						return null;
				}
				throw $e;
		} catch (Exception $e) {
				throw $e;
		}

		return HelperUtil::makeLengthAwarePaginator($data, 'p');
}

이렇게 api request 에서 받아온 paginator response 를 그대로 makeLengthAwarePaginator 에 넣어주면 된다.

public function render()
{
		$data = $this->requestGetLocalData();

		return view('livewire.local-data', [
				'itemPaginator' => $data,
		]);
}

그리고 그렇게 받아온 LengthAwarePaginator 를 render 로 넘겨준다.

@foreach ($itemPaginator ?? [] as $index => $item)
<tr>
  <td>{{ $item['id'] }}</td>
</tr>
@endforeach
<div class="mt-4">
		{{ $itemPaginator?->links('vendor.livewire.tailwind') }}
</div>

프론트엔드 blade.php 에서는 이렇게 작성해주면 된다.

그런데 여기서 livewire v3 로 업데이트 되면서 변경된 점들이 있는데, 이런 부분까지 모두 작업을 해줘야만 한다.

use WithPagination {
		setPage as withPaginationSetPage;
}

#[Url(as: 'p', except: 1)]
public $page = 1;

public array $filter = [
		'search' => '',
		'order' => 'id.desc',
];

public function setPage(int $page): void
{
		$this->withPaginationSetPage(page: $page, pageName: 'p');
		$this->page = $page;
}

setPage를 추가해줘야만한다. ($this->page = $page 도 넣어줘야한다…)

그런데 여기까지 작업을 했는데, {{ $itemPaginator?->links('vendor.livewire.tailwind') }} 여기에 문제가 있었다.

Previous Next 버튼을 클릭했을때 문제가 있었다.

wire:click="previousPage('{{ $paginator->getPageName() }}')"
wire:click="nextPage('{{ $paginator->getPageName() }}')"

왜냐하면, $items->forPage(1, $perPage), 여기 때문인건데, page를 1로 고정해야지만 데이터를 정상적으로 출력해줄수가 있다. ($page 를 넣으면 안됨)

그러다보니 next는 2가 되고 previous는 1이 된다.

그래서 이 문제를 해결하려면, tailwind.blade.php 파일을 수정해주면 된다.

wire:click="gotoPage({{ $paginator->currentPage() - 1 }}, '{{ $paginator->getPageName() }}')"
wire:click="gotoPage({{ $paginator->currentPage() + 1 }}, '{{ $paginator->getPageName() }}')"

이렇게 수정을 해주면, api request 에서 paginate 를 그대로 사용해서 livewire frontend 에서도 사용할 수 있다.

참고로 ‘p’ 는 pageName 이다. queryString에 맞게 설정해주면 된다.

생각보다 이렇게 작업해서 하는게 편리하다.

이유는 언제든지 livewire 에서 vue 든 react 로 넘어갈 수 있기 때문이다.

백엔드와 프론트(livewire) 를 분리해서 작업하는게 생각보다 시간은 오래 걸리지만, 유지보수 측면이나 미래를 위해서는 필요하다고 생각된다.