Search code examples
reactjsreduxreact-reduxreact-hooks

Cannot read properties of undefined (reading 'find')


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;

Solution

  • 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...