리액트에서 요소를 생성할때
React.createElement('p',{},'Hello, World');
하는것처럼
HTML string을 분리해서 parse함.
tag를 없애준다.
NoteCard.tsx
import parse from 'html-react-parser';
...
<ContentBox>
{parse(content)}
</ContentBox>
const func = () => {
const imgContent = content.includes("img");
if(imgContent) {
return content;
} else {
return content.length > 75 ? content.slice(0, 75) + "..." : content;
}
}
...
<ContentBox>{parse(func())}</ContentBox>
NoteCard.tsx
...
return (
<>
{isRead && <ReadNoteModal type={type} note={note}/>}
...
<ContentBox onClick={() => dispatch(readNote({ type, id }))}>
{parse(func())}
</ContentBox>
...
</>
src > Modal > ReadNoteModal.tsx
interface ReadNoteModalProps {
type: string,
note: Note
}
const ReadNoteModal = ({ type, note }: ReadNoteModalProps) => {
const dispatch = useAppDispatch();
return (
<FixedContainer>
<Box style={{ backgroundColor: note.color }}>
<DeleteBox
onClick={() => dispatch(readNote({ type, id: note.id }))}
className="readNote__close-btn"
>
<FaTimes />
</DeleteBox>
<div className="readNote__title">{note.title}</div>
<div className="readNote__content">{parse(note.content)}</div>
</Box>
</FixedContainer>
);
}
pages > AllNotes.tsx
filterHandler에서 이벤트객체 받아서 filter 값(순위별, 날짜별) 가져와서 setFilter하고
cleaerHandler에서 필터값 비우기
FiltersModal에 props로 filterHandler, cleaerHandler, filter를 넘겨준다.
const filterHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
setFilter(e.target.value);
};
const clearHandler = () => {
setFilter("");
};
return (
<Container>
{viewFiltersModal && (
<FiltersModal
handleFilter={filterHandler}
handleClear={clearHandler}
filter={filter}
/>
)}
...
FiltersModal.tsx
interface FiltersModalProps {
handleFilter: (e: React.ChangeEvent<HTMLInputElement>) => void;
handleClear: () => void;
filter: string;
}
const FiltersModal = ({ handleFilter, handleClear, filter }: FiltersModalProps) => {
const dispatch = useAppDispatch();
return (
<FixedContainer>
<Container>
<DeleteBox
onClick={() => dispatch(toggleFiltersModal(false))}
className='filters__close'
>
<FaTimes />
</DeleteBox>
<TopBox>
<div className="filters__title">정렬</div>
<small onClick={handleClear} className="filters__delete">
Clear
</small>
</TopBox>
<Box>
<div className="filters__subtitle">PRIORITY</div>
<div className="filters__check">
<input
type="radio"
name="filter"
value="low"
id="low"
checked={filter === "low"}
onChange={(e) => handleFilter(e)}
/>
<label htmlFor="low">Low to High</label>
</div>
<div className="filters__check">
<input
type="radio"
name="filter"
value="high"
id="high"
checked={filter === "high"}
onChange={(e) => handleFilter(e)}
/>
<label htmlFor="high">High to Low</label>
</div>
</Box>
<Box>
<div className="filters__subtitle">DATE</div>
<div className="filters__check">
<input
type="radio"
name="filter"
value="latest"
id="new"
checked={filter === "latest"}
onChange={(e) => handleFilter(e)}
/>
<label htmlFor="new">Sort by Latest</label>
</div>
<div className="filters__check">
<input
type="radio"
name="filter"
value="created"
id="create"
checked={filter === "created"}
onChange={(e) => handleFilter(e)}
/>
<label htmlFor="create">Sort by Created</label>
</div>
<div className="filters__check">
<input
type="radio"
name="filter"
value="edited"
id="edit"
checked={filter === "edited"}
onChange={(e) => handleFilter(e)}
/>
<label htmlFor="edit">Sort by Edited</label>
</div>
</Box>
</Container>
</FixedContainer>
);
}
getAllNotes.tsx
를 수정한다.
pinned에 isPinned true인것을 넣어주고 false인것은 normal에 넣는다.
그리고 다음 조건에 따라 리스트 뷰가 달라진다.
1. 일반노트가 0이 아니고 핀노트가 0일때(All Notes만 보여짐)
2. 일반노트가 0이고 핀노트가 0이 아닐때(Pinned Notes만 보여짐)
3. 일반노트와 핀노트가 0이 아닐때(각각 따로 보여짐)
4. 일반노트와 핀노트가 0일때(아무것도 없음 안내문구 보여짐)
const getAllNotes = (mainNotes: Note[], filter: string) => {
const pinned = mainNotes.filter(({ isPinned }) => isPinned); // isPinned true인 notes들만
const normal = mainNotes.filter(({ isPinned }) => !isPinned); // isPinned false
if (normal.length !== 0 && pinned.length === 0) {
return (
<>
<div className="allNotes__notes-type">
All Notes <span>({normal.length})</span>
</div>
<NotesContainer>
{normal.map((note) => (
<NoteCard key={note.id} note={note} type="notes" />
))}
</NotesContainer>
</>
);
}
if (normal.length === 0 && pinned.length !== 0) {
return (
<>
<div className="allNotes__notes-type">
Pinned Notes <span>({pinned.length})</span>
</div>
<NotesContainer>
{pinned.map((note) => (
<NoteCard key={note.id} note={note} type="notes" />
))}
</NotesContainer>
</>
);
}
if (normal.length !== 0 && pinned.length !== 0) {
return (
<>
<div>
<div className="allNotes__notes-type">
Pinned Notes <span>({pinned.length})</span>
</div>
<NotesContainer>
{pinned.map((note) => (
<NoteCard key={note.id} note={note} type="notes" />
))}
</NotesContainer>
</div>
<div>
<div className="allNotes__notes-type">
All Notes <span>({normal.length})</span>
</div>
<NotesContainer>
{normal.map((note) => (
<NoteCard key={note.id} note={note} type="notes" />
))}
</NotesContainer>
</div>
</>
);
}
if (normal.length === 0 && pinned.length === 0) {
return (
<>
<p>저장된 노트가 없습니다.</p>
</>
);
}
}
mdn web docs-Array.prototype.sort()
var items = [
{ name: "Edward", value: 21 },
{ name: "Sharpe", value: 37 },
{ name: "And", value: 45 },
{ name: "The", value: -12 },
{ name: "Magnetic", value: 13 },
{ name: "Zeros", value: 37 },
];
// value 기준으로 정렬 (최신방법)
items.sort(function (a, b) {
if (a.value > b.value) {
return 1;
}
if (a.value < b.value) {
return -1;
}
// a must be equal to b
return 0;
});
// value 기준으로 정렬 (이전방법)
items.sort((a,b) => a.value - b.value)); // value가 작은것부터 큰순서
items.sort((a,b) => b.value - a.value)); // value가 큰것부터 작은순서
mdn docs를 찾아봤는데 sort방법이 바뀌어서 콘솔에 찍어봤더니... return값으로 정렬이 되는듯하다... 일단 강의대로 이전방법을 써볼것이다.
getAllNotes.tsx
const filteredNotes = (notes: Note[], filter: string) => {
const lowPriority = notes.filter(({ priority }) => priority === "low");
const highPriority = notes.filter(({ priority }) => priority === "high");
if(filter === "low") {
return [...lowPriority, ...highPriority];
} else if(filter === "high") {
return [...highPriority, ...lowPriority];
} else if(filter === "latest") {
return notes.sort((a, b) => b.createdTime - a.createdTime);
} else if(filter === "created") {
return notes.sort((a, b) => a.createdTime - b.createdTime);
} else if(filter === "edited") {
const editedNotes = notes.filter(({ editedTime })=> editedTime); // editedTime있는것
const normalNotes = notes.filter(({ editedTime })=> !editedTime); // 없는것
const sortEdited = editedNotes.sort((a, b) => ((b.editedTime as number) - (a.editedTime as number)));
return [...sortEdited, ...normalNotes];
} else {
return notes;
}
}
const getAllNotes = (mainNotes: Note[], filter: string) => {
const pinned = mainNotes.filter(({ isPinned }) => isPinned); // isPinned true인 notes들만
const normal = mainNotes.filter(({ isPinned }) => !isPinned); // isPinned false
if (normal.length !== 0 && pinned.length === 0) {
return (
<>
<div className="allNotes__notes-type">
All Notes <span>({normal.length})</span>
</div>
<NotesContainer>
{filteredNotes(normal, filter).map((note) => (
<NoteCard key={note.id} note={note} type="notes" />
))}
</NotesContainer>
</>
);
}
if (normal.length === 0 && pinned.length !== 0) {
return (
<>
<div className="allNotes__notes-type">
Pinned Notes <span>({pinned.length})</span>
</div>
<NotesContainer>
{filteredNotes(pinned, filter).map((note) => (
<NoteCard key={note.id} note={note} type="notes" />
))}
</NotesContainer>
</>
);
}
if (normal.length !== 0 && pinned.length !== 0) {
return (
<>
<div>
<div className="allNotes__notes-type">
Pinned Notes <span>({pinned.length})</span>
</div>
<NotesContainer>
{filteredNotes(pinned, filter).map((note) => (
<NoteCard key={note.id} note={note} type="notes" />
))}
</NotesContainer>
</div>
<div>
<div className="allNotes__notes-type">
All Notes <span>({normal.length})</span>
</div>
<NotesContainer>
{filteredNotes(normal, filter).map((note) => (
<NoteCard key={note.id} note={note} type="notes" />
))}
</NotesContainer>
</div>
</>
);
}
if (normal.length === 0 && pinned.length === 0) {
return (
<>
<p>저장된 노트가 없습니다.</p>
</>
);
}
}
src > pages > ArchiveNotes
ArchiveNotes.tsx
const ArchiveNotes = () => {
const { archiveNotes } = useAppSelector((state) => state.notesList);
return (
<Container>
{archiveNotes.length === 0 ? (
<EmptyMsgBox>노트가 없습니다.</EmptyMsgBox>
) : (
<MainWrapper
notes={archiveNotes}
type="archive"
/>
)}
</Container>
)
}
src > components > MainWrapper.tsx
interface MainWrapperProps {
notes: Note[];
type: string;
}
const MainWrapper = ({ notes, type }: MainWrapperProps) => {
return (
<NotesContainer>
{notes.map((note) => (
<NoteCard
key={note.id}
note={note}
type={type}
/>
))}
</NotesContainer>
)
}
src > pages > TrashNotes.tsx
TrashNotes 페이지는 ArchiveNotes와 똑같다.
const TrashNotes = () => {
const { trashNotes } = useAppSelector((state) => state.notesList);
return (
<Container>
{trashNotes.length === 0 ? (
<EmptyMsgBox>노트가 없습니다.</EmptyMsgBox>
) : (
<MainWrapper
notes={trashNotes}
type="trash"
/>
)}
</Container>
)
}
태그이름을 useParams에서 받아와서
태그이름이 같은 것만 모아서 새로운 notes에 push하고
그 notes를 뿌려준다.
src > pages >TagNotes.tsx
const TagNotes = () => {
const { name } = useParams() as {name: string}; // App.tsx에서 지정한 route에 parameter 이름을 name으로 지정함
const { mainNotes } = useAppSelector((state) => state.notesList);
let notes: Note[] = [];
mainNotes.forEach((note) => {
if(note.tags.find(({tag}) => tag === name)) {
notes.push(note);
}
})
return (
<Container>
{notes.length === 0 ? (
<EmptyMsgBox>노트가 없습니다.</EmptyMsgBox>
) : (
<MainWrapper
notes={notes}
type={name}
/>
)}
</Container>
)
}