Reputation: 79
I'm doing a CRUD with redux. The application has a form with a username, and then a screen to create, view, edit and delete posts and I'm doing the editing part. However, when I use array.find()
to traverse the array and look for each post, I'm getting this error:
Cannot read properties of undefined (reading 'find')
I will leave the codes below.
EditScreen:
import React, {useState} from 'react';
import '../_assets/modal.css';
import { useDispatch, useSelector } from 'react-redux';
import { useLocation, useNavigate } from 'react-router-dom'
import {editPost} from '../redux/postsslice';
function EditModal({ closeModal}) {
const { pathname } = useLocation();
const postId = pathname.replace("/edit-post/", "")
const post = useSelector((state) => state.posts.find((post) => post.id === postId))
const dispatch = useDispatch()
const navigation = useNavigate();
const [title, setTitle] = useState(post.title)
const [content, setContent] = useState(post.content)
const onTitleChanged = e => setTitle(e.target.value)
const onContentChanged = e => setContent(e.target.value)
const onSavePostClicked = (e) => {
if (title && content) {
dispatch(editPost({id: postId, title, content}))
}
}
return (
<div className="modalBackground">
<div className="modalContainer">
<div className="title"><h1>Edit item</h1></div>
<h2>Title</h2>
<form>
<input
type="text"
placeholder="Hello World"
name="name"
value={title}
onChange={onTitleChanged}
></input>
<h2>Content</h2>
<textarea
placeholder="Content"
name="content"
value={content}
onChange={onContentChanged}
></textarea>
<button onClick={onSavePostClicked}>SAVE</button></form>
</div>
</div>
)
}
export default EditModal
mainscreen:
import React, { useState, useEffect } from "react";
import "../_assets/App.css";
import "../_assets/mainscreen.css";
import { MdDeleteForever } from "react-icons/md";
import { FiEdit } from "react-icons/fi";
import { useSelector } from 'react-redux';
import { useDispatch } from 'react-redux';
import { Navigate } from 'react-router-dom';
import { addPost } from '../redux/postsslice'
import {nanoid} from 'nanoid'
import Modal from "../components/modal.jsx";
import EditModal from '../components/editmodal.jsx';
function MainScreen() {
const dispatch = useDispatch();
const user = useSelector((state) => state.user)
const posts = useSelector((state) => state.loadPosts)
const [title, setTitle] = useState("");
const [content, setContent] = useState("");
const [buttonGreyOut, setButtonGreyOut] = useState("#cccccc");
useEffect(() => {
if (title && content !== "") {
setButtonGreyOut("black");
} else {
setButtonGreyOut("#cccccc");
}
},[title, content]);
const handleSubmitSendPost = (e) => {
e.preventDefault();
dispatch(
addPost({
id: nanoid(),
title,
content
})
)
setTitle('')
setContent('')
};
const handleChangeTitle = (text) => {
setTitle(text);
};
const handleChangeContent = (text) => {
setContent(text);
};
const [openEditModal, setOpenEditModal] = useState();
const [openModal, setOpenModal] = useState();
if (user === '') {
return <Navigate to="/" />
} else {
return (
<div className="containerMainScreen">
{openModal && <Modal closeModal={setOpenModal} />}
{openEditModal && <EditModal closeModal={setOpenEditModal} />}
<div className="bar">
<h1>Codeleap</h1>
</div>
<div className="boxPost">
<h2 style={{ fontWeight: 700 }}>What's on your mind?</h2>
<h2>Title</h2>
<form onSubmit={handleSubmitSendPost}>
<input
type="text"
placeholder="Hello World"
name="name"
value={title}
onChange={(e) => handleChangeTitle(e.target.value)}
></input>
<h2>Content</h2>
<textarea
placeholder="Content"
name="content"
value={content}
onChange={(e) => handleChangeContent(e.target.value)}
></textarea>
<button
className="createButton"
type="submit"
style={{ backgroundColor: buttonGreyOut }}
disabled={!title || !content}
>
CREATE
</button>
</form>
</div>
{posts.map((post) => (
<div className="boxPost" key={post.id}>
<div className="bar">
<h1>{post.title}</h1>
<MdDeleteForever
className="icon"
onClick={() => {
setOpenModal(true);
}}
/>
<FiEdit
onClick={() => {
setOpenEditModal(true);
}}
style={{ color: "white", fontSize: "45px", paddingLeft: "23px" }}
/>
</div>
<div id="postowner">
<h3>@{user}</h3>
<br></br>
<textarea style={{ border: "none" }}>{post.content}</textarea>
</div>
</div>
))}
</div>
);
}
}export default MainScreen;
postSlice:
import { createSlice } from "@reduxjs/toolkit";
const postsSlice = createSlice({
name: "posts",
initialState: [],
reducers: {
addPost (state, action) {
state.push(action.payload); // modifies the draft state.
},
editPost(state, action) {
const { id, title, content } = action.payload;
const existingPost = state.find((post) => post.id === id);
if (existingPost) {
existingPost.title = title
existingPost.content = content
}
}
}
});
export const { addPost, editPost } = postsSlice.actions
export default postsSlice
editModal.js (edit page)
import React, {useState} from 'react';
import '../_assets/modal.css';
import { useDispatch, useSelector } from 'react-redux';
import { useLocation, useNavigate } from 'react-router-dom'
import {editPost} from '../redux/postsslice';
export function EditModal({ closeModal}) {
const { pathname } = useLocation();
const postId = parseInt(pathname.replace("edit-post/", ""))
const post = useSelector((state) => state.loadPosts.find((post) => post.id === postId))
const dispatch = useDispatch()
const navigation = useNavigate();
const [title, setTitle] = useState(post.title)
const [content, setContent] = useState(post.content)
const onTitleChanged = e => setTitle(e.target.value)
const onContentChanged = e => setContent(e.target.value)
const onSavePostClicked = (e) => {
if (title && content) {
dispatch(editPost({id: postId, title, content}))
}
}
return (
<div className="modalBackground">
<div className="modalContainer">
<div className="title"><h1>Edit item</h1></div>
<h2>Title</h2>
<form>
<input
type="text"
placeholder="Hello World"
name="name"
value={title}
onChange={onTitleChanged}
></input>
<h2>Content</h2>
<textarea
placeholder="Content"
name="content"
value={content}
onChange={onContentChanged}
></textarea>
<button onClick={onSavePostClicked}>SAVE</button></form>
</div>
</div>
)
}
export default EditModal
store.js:
import { configureStore } from '@reduxjs/toolkit';
import userSlice from './userslice';
import postsSlice from './postsslice'
const store = configureStore({
reducer: {
user: userSlice.reducer,
loadPosts: postsSlice.reducer
},
})
export default store
signup page:
import React, {useState, useEffect} from "react";
import "../_assets/signup.css";
import "../_assets/App.css";
import { useDispatch } from 'react-redux';
import userSlice from '../redux/userslice';
import { useNavigate } from "react-router-dom";
function Signup() {
const navigate = useNavigate();
const dispatch = useDispatch();
const [name, setName] = useState('')
const [buttonGrey, setButtonGrey] = useState('#cccccc')
useEffect(() => {
if (name!== '') {
setButtonGrey("black")
}
else {
setButtonGrey('#cccccc')
}
}, [name])
const handleSubmitForm= (e) => {
e.preventDefault()
dispatch(userSlice.actions.saveUser(name))
navigate("/main")
}
const handleChangeName = (text) => {
setName(text)
}
return (
<div className="container">
<div className="LoginBox">
<form onSubmit={handleSubmitForm}>
<h2>Welcome to codeleap network</h2>
<text>Please enter your username</text>
<input type="text" name="name" value={name} onChange = {e => handleChangeName(e.target.value)} placeholder="Jane Doe" />
<div className="button">
<button type="submit" style={{backgroundColor: buttonGrey}} disabled={!name} >
ENTER
</button>
</div>
</form>
</div>
</div>
);
}
export default Signup;
Upvotes: 3
Views: 10603
Reputation: 202706
With the state shape:
const store = configureStore({
reducer: {
user: userSlice.reducer,
loadPosts: postsSlice.reducer
},
});
Then the path to the posts state is state.loadPosts
, so the selector in EditModal
should be:
const post = useSelector((state) => state.loadPosts.find(
(post) => post.id === postId),
);
The error TypeError: Cannot read properties of undefined (reading 'title')
is closely related to this line of code. If state.loadPosts
is an empty array or there are not matching posts by id
, then .find
returns undefined.
const [title, setTitle] = useState(post.title); // <-- throws error on undefined post
const [content, setContent] = useState(post.content); // <-- throws error on undefined post
A quick fix could be to use the Optional Chaining operator
const [title, setTitle] = useState(post?.title);
const [content, setContent] = useState(post?.content);
but this only sets the initial state. If there is no matching post to edit then there's no point in rendering the editing UI. At this point you should render some fallback UI or navigate back, etc...
Upvotes: 1