고민의 시작은 styled-component로 만들어진 태그에 ref를 걸고 싶었다...
useRef를 이용해 기존에 하던 방식대로 해보았으나 당연히 안됨. styled-component의 진짜 태그는 style.js 안에 모여있기 때문일 것이라고 생각했다.
그럼 ref를 props 보내듯이 보내줘야 하는데....
그럴때 사용하는것이 forwardRef!!
// Modal.js
...
const attentionInput = useRef();
...
<Input
ref={attentionInput}
...
/>
// style.js
const Input = forwardRef((props, ref) => {
...
return <SearchInput ref={ref} />;
});
위 코드를 보면 html 엘리먼트에 forwardRef를 사용해 prop을 받아오고 있다.
prop 받아오는 형태가 조금 특이한데 'HTML 엘리먼트 접근이라는 특수한 용도로 사용되기 때문에 일반적인 prop으로 사용을 할 수 없다' 고 한다.
HTML 엘리먼트가 아닌 React 컴포넌트에서 ref prop을 사용하려면 React에서 제공하는 forwardRef()라는 함수를 사용해야 합니다. React 컴포넌트를 forwardRef()라는 함수로 감싸주면, 해당 컴포넌트는 함수는 두 번째 매개 변수를 갖게 되는데, 이를 통해 외부에서 ref prop을 넘길 수 있습니다.
라고 설명되어 있다.
각 종목의 순서를 바꿔줘야 하는데 디자이너 분이 제안한 방식은 드래그 앤 드랍이었다.
라이브러리를 찾던 중 'react-dnd' 를 적용해보았는데 너무 활용하기가 어려웠다...
이름도 아름다운 'react-beautiful-dnd' 라이브러리를 두번째로 적용해보았는데 굉장히 쉽고 빠르게 적용할 수 있었다.
// FormExerciseDnd.js
import React, { useState } from 'react';
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';
import { useDispatch, useSelector } from 'react-redux';
import styled from 'styled-components';
import { Text, Image } from '../shared/Styles';
import DragIndicator from '../img/drag-indicator.svg';
import { actionCreators as exerciseCreator } from '../redux/modules/exercise';
export default function FormExerciseDnd() {
const dispatch = useDispatch();
const lists = useSelector((state) => state.exercise.routine.myExercise);
const handlelists = (result) => {
// 드래그 앤 드랍 동작 로직 함수 시작
// result에는 드래그의 시작 idx와 종료 idx 등 드래그 관련 여러 정보가 담겨있음.
if (!result.destination) return;
const items = [...lists];
const [reorderedItem] = items.splice(result.source.index, 1);
items.splice(result.destination.index, 0, reorderedItem);
// 드래그 앤 드랍 동작 로직 함수 끝
// 위 드래그 앤 드랍 로직 함수로는 드랍 이후 상태가 저장이 안됨
// 당연한 얘기지만 새롭게 상태 저장을 하지 않았으므로, lists에 저장된 순서대로 다시 돌아감...
// 따라서 아래처럼 순서를 새롭게 변경해서 redux에 저장해줘야 함.
dispatch(exerciseCreator.reArrangeMyExercise(items));
};
return (
// onDragEnd에 드래그 앤 드랍 로직 함수를 넣어야 함.
<DragDropContext onDragEnd={handlelists}>
<Droppable droppableId="lists">
{(provided) => (
<Container
className="lists"
{...provided.droppableProps}
ref={provided.innerRef}
>
{lists.map((list, listIdx) => (
<Draggable
key={listIdx}
// draggableId에는 string 값이 들어가야 함.
draggableId={listIdx.toString()}
index={listIdx}
disableInteractiveElementBlocking="true"
>
{(provided) => (
<List
ref={provided.innerRef}
{...provided.dragHandleProps}
{...provided.draggableProps}
id={listIdx}
>
...
</List>
)}
</Draggable>
))}
{/* 가장 위로 위치를 변경할때 필요한 높이를 확보해준다고 한다. */}
{provided.placeholder}
</Container>
)}
</Droppable>
</DragDropContext>
);
}
...