Reputation: 131
I am building a to-do/notes app in order to learn the basics of Redux, using React hooks and Typescript.
A note is composed of an ID and a value. The user can add, delete or edit a note.
The add / delete mechanics work fine. But the edit one is trickier for me, as I'm questionning how it should be implemented.
I think my reducer's code is fine. The problem lies between my component (Note.tsx) and its parent one (App.tsx).
When i'm logging the value, I can see that the new updated/edited value of the note is not sent to the reducer. As a result, my note is not edited with the new value.
I've tried "cloning" the redux store and making my changes here, but it seems tedious and unnatural to me. Should I just call the edit method from my Note.tsx component ?
Is there a clean / conventional way to do this ?
Here is my code :
App.tsx
function App() {
const notes = useSelector<NotesStates, NotesStates['notes']>(((state) => state.notes));
const dispatch = useDispatch();
const onAddNote = (note: string) => {
dispatch(addNote(note));
};
const onDeleteNote = (note: NoteType) => {
dispatch(deleteNote(note));
};
const onEditNote = (note: NoteType) => {
dispatch(updateNote(note));
};
return (
<div className="home">
<NewNoteInput addNote={onAddNote} />
<hr />
<ul className="notes">
{notes.map((note) => (
<Note
updateNote={() => onEditNote(note)}
deleteNote={() => onDeleteNote(note)}
note={note}
/>
))}
</ul>
</div>
);
}
Note.tsx
interface NoteProps {
deleteNote(): void
updateNote(noteValue: string | number): void
note: NoteType
}
const Note: React.FC<NoteProps> = ({ deleteNote, updateNote, note: { id, value } }) => {
const [isEditing, setIsEditing] = useState(false);
const [newNoteValue, setNewNoteValue] = useState(value);
const onDeleteNote = () => {
deleteNote();
};
const onUpdateNote = () => {
updateNote(newNoteValue);
setIsEditing(false);
};
const handleOnDoubleClick = () => {
setIsEditing(true);
};
const renderBody = () => {
if (!isEditing) {
return (
<>
{!value && <span className="empty-text">Note is empty</span>}
<span>{value}</span>
</>
);
}
return (
<input
value={newNoteValue}
onChange={(e) => setNewNoteValue(e.target.value)}
onBlur={onUpdateNote}
/>
);
};
return (
<li className="note" key={id}>
<span className="note__title">
Note n°
{id}
</span>
<div className="note__body" onDoubleClick={handleOnDoubleClick}>
{renderBody()}
</div>
<button type="button" onClick={onDeleteNote}>Delete</button>
</li>
);
};
export default Note;
and the notesReducer.tsx
export interface NotesStates {
notes: Note[]
}
export interface Note {
id: number
value: string
}
const initialState = {
notes: [],
};
let noteID = 0;
export const notesReducer = (state: NotesStates = initialState, action: NoteAction): NotesStates => {
switch (action.type) {
case 'ADD_NOTE': {
noteID += 1;
return {
...state,
notes: [...state.notes, {
id: noteID,
value: action.payload,
}],
};
}
case 'UPDATE_NOTE': {
return {
...state,
notes: state.notes.map((note) => {
if (note.id === action.payload.id) {
return {
...note,
value: action.payload.value,
};
}
return note;
}),
};
}
case 'DELETE_NOTE': {
return {
...state,
notes: [...state.notes
.filter((note) => note.id !== action.payload.id)],
};
}
default:
return state;
}
};
Upvotes: 1
Views: 273
Reputation: 131
Thanks to @secan in the comments I made this work, plus some changes.
In App.tsx :
<Note
updateNote={onEditNote}
deleteNote={() => onDeleteNote(note)}
note={note}
/>
In Note.tsx :
interface NoteProps {
deleteNote(): void
updateNote(newNote: NoteType): void // updated the signature
note: NoteType
}
// Now passing entire object instead of just the value
const onUpdateNote = (newNote: NoteType) => {
updateNote(newNote);
setIsEditing(false);
};
const renderBody = () => {
if (!isEditing) {
return (
<>
{!value && <span className="empty-text">Note is empty</span>}
<span>{value}</span>
</>
);
}
return (
<input
value={newNoteValue}
onChange={(e) => setNewNoteValue(e.target.value)}
// modifying current note with updated value
onBlur={() => onUpdateNote({ id, value: newNoteValue })}
/>
);
};
Upvotes: 1