Neil Karania
Neil Karania

Reputation: 1

useRef is null in Modal

import React, { useState, useEffect, useRef } from "react";
import axios from "axios";
import toast from "react-hot-toast";
import jwt_decode from "jwt-decode";
import Modal from "react-modal";
import "../styles/Blogs.css";

const Blogs = () => {
  const { userId } = jwt_decode(localStorage.getItem("token"));
  const [mainSections, setMainSections] = useState([]);
  const [subSections, setSubSections] = useState([]);
  const [blogs, setBlogs] = useState([]);
  const [newBlog, setNewBlog] = useState({
    mainSection: "",
    subSection: "",
    title: "",
    description: "",
    thumbnail: null,
  });
  const [editBlog, setEditBlog] = useState(null);
  const [editThumbnail, setEditThumbnail] = useState(null);
  const [loading, setLoading] = useState(false);
  const [editModalIsOpen, setEditModalIsOpen] = useState(false);

  const refdivNew = useRef(null);
  const rteNew = useRef(null);
  const refdivEdit = useRef(null);
  const rteEdit = useRef(null);

  useEffect(() => {
    if (refdivNew.current) {
      rteNew.current = new window.RichTextEditor(refdivNew.current);
      rteNew.current.setHTMLCode(""); // Initialize RTE
    }
  }, []); // Run once on component mount

  useEffect(() => {
    console.log(refdivEdit.current);
    if (editBlog && refdivEdit.current) {
      if (!rteEdit.current) {
        rteEdit.current = new window.RichTextEditor(refdivEdit.current);
      }
      rteEdit.current.setHTMLCode(editBlog.content || ""); // Initialize RTE for editing blog
    }
  }, [editBlog]); // Re-run when modal opens or editBlog changes

  useEffect(() => {
    fetchMainSections();
    fetchBlogs();
  }, []);

  const fetchMainSections = async () => {
    try {
      const response = await axios.get("/doctor/getmainsections", {
        headers: {
          Authorization: `Bearer ${localStorage.getItem("token")}`,
        },
      });
      setMainSections(response.data);
    } catch (error) {
      console.error("Error fetching main sections:", error);
      toast.error("Failed to fetch main sections");
    }
  };

  const fetchSubSections = async (mainSectionId) => {
    try {
      const response = await axios.get(
        `/doctor/getsubsection/${mainSectionId}`,
        {
          headers: {
            Authorization: `Bearer ${localStorage.getItem("token")}`,
          },
        }
      );
      setSubSections(response.data);
    } catch (error) {
      console.error("Error fetching sub sections:", error);
      toast.error("Failed to fetch sub sections");
    }
  };

  const fetchBlogs = async () => {
    try {
      const response = await axios.get("/doctor/getAllBlogs", {
        headers: {
          Authorization: `Bearer ${localStorage.getItem("token")}`,
        },
      });
      setBlogs(response.data);
    } catch (error) {
      console.error("Error fetching blogs:", error);
      toast.error("Failed to fetch blogs");
    }
  };

  const addBlog = async (e) => {
    e.preventDefault();
    const { mainSection, subSection, title, description, thumbnail } = newBlog;
    const content = rteNew.current ? rteNew.current.getHTMLCode() : "";
    try {
      const formData = new FormData();
      formData.append("mainSection", mainSection);
      formData.append("subSection", subSection);
      formData.append("title", title);
      formData.append("description", description);
      formData.append("content", content);
      formData.append("user", userId);
      if (thumbnail) {
        formData.append("thumbnail", thumbnail);
      }

      const response = await axios.post("/doctor/addblog", formData, {
        headers: {
          Authorization: `Bearer ${localStorage.getItem("token")}`,
          "Content-Type": "multipart/form-data",
        },
      });
      setBlogs([...blogs, response.data]);
      toast.success("Blog added successfully");

      setNewBlog({
        mainSection: "",
        subSection: "",
        title: "",
        description: "",
        thumbnail: null,
      });
      rteNew.current.setHTMLCode(""); // Reset RTE
    } catch (error) {
      console.error("Error adding blog:", error);
      toast.error("Failed to add blog");
    }
  };

  const deleteBlog = async (id) => {
    try {
      await axios.delete(`/doctor/deleteblog/${id}`, {
        headers: {
          Authorization: `Bearer ${localStorage.getItem("token")}`,
        },
      });
      setBlogs(blogs.filter((blog) => blog._id !== id));
      toast.success("Blog deleted successfully");
    } catch (error) {
      console.error("Error deleting blog:", error);
      toast.error("Failed to delete blog");
    }
  };

  const openEditModal = (blog) => {
    setEditBlog(blog);
    setEditThumbnail(blog.thumbnail);
    setEditModalIsOpen(true);
  };

  const closeEditModal = () => {
    setEditBlog(null);
    setEditThumbnail(null);
    setEditModalIsOpen(false);
  };

  const handleEditChange = (e) => {
    const { name, value } = e.target;
    setEditBlog({ ...editBlog, [name]: value });
  };

  const handleEditThumbnailChange = (e) => {
    setEditThumbnail(e.target.files[0]);
  };

  const saveEditedBlog = async (e) => {
    e.preventDefault();
    const content = rteEdit.current ? rteEdit.current.getHTMLCode() : "";
    try {
      const formData = new FormData();
      formData.append("mainSection", editBlog.mainSection);
      formData.append("subSection", editBlog.subSection);
      formData.append("title", editBlog.title);
      formData.append("description", editBlog.description);
      formData.append("content", content);
      if (editThumbnail) {
        formData.append("thumbnail", editThumbnail);
      }

      const response = await axios.put(
        `/doctor/editblog/${editBlog._id}`,
        formData,
        {
          headers: {
            Authorization: `Bearer ${localStorage.getItem("token")}`,
            "Content-Type": "multipart/form-data",
          },
        }
      );

      setBlogs(
        blogs.map((blog) => (blog._id === editBlog._id ? response.data : blog))
      );
      toast.success("Blog updated successfully");
      closeEditModal();
      setEditThumbnail(null);
    } catch (error) {
      console.error("Error updating blog:", error);
      toast.error("Failed to update blog");
    }
  };

  return (
    <div className="blogs-container">
      <h2>Blogs</h2>
      {loading ? (
        <p>Loading blogs...</p>
      ) : (
        <div className="blogs-list">
          {blogs.length === 0 ? (
            <p>No blogs found.</p>
          ) : (
            blogs.map((blog) => (
              <div className="blog-card" key={blog._id}>
                <span>{blog.title}</span>
                {blog.thumbnail && (
                  <img
                    src={`${process.env.REACT_APP_CLOUDINARY_BASE_URL}${blog.thumbnail}`}
                    alt="Thumbnail"
                    className="thumbnail"
                  />
                )}
                <div className="btn-group">
                  <button
                    className="btn form-btn"
                    onClick={() => deleteBlog(blog._id)}
                  >
                    Delete
                  </button>
                  <button
                    className="btn form-btn"
                    onClick={() => openEditModal(blog)}
                  >
                    Edit
                  </button>
                  <button
                    className="btn form-btn"
                    onClick={() => alert(`Viewing blog: ${blog.title}`)}
                  >
                    View
                  </button>
                </div>
              </div>
            ))
          )}
        </div>
      )}
      <form onSubmit={addBlog} className="blog-form">
        <select
          value={newBlog.mainSection}
          onChange={(e) => {
            setNewBlog({ ...newBlog, mainSection: e.target.value });
            fetchSubSections(e.target.value);
          }}
        >
          <option value="">Select Main Section</option>
          {mainSections.map((section) => (
            <option key={section._id} value={section._id}>
              {section.title}
            </option>
          ))}
        </select>
        <select
          value={newBlog.subSection}
          onChange={(e) =>
            setNewBlog({ ...newBlog, subSection: e.target.value })
          }
        >
          <option value="">Select Sub Section</option>
          {subSections.map((subSection) => (
            <option key={subSection._id} value={subSection._id}>
              {subSection.title}
            </option>
          ))}
        </select>
        <input
          type="text"
          placeholder="Enter blog title"
          value={newBlog.title}
          onChange={(e) => setNewBlog({ ...newBlog, title: e.target.value })}
        />
        <input
          type="text"
          placeholder="Enter blog description"
          value={newBlog.description}
          onChange={(e) =>
            setNewBlog({
              ...newBlog,
              description: e.target.value,
            })
          }
        />
        <input
          type="file"
          onChange={(e) =>
            setNewBlog({
              ...newBlog,
              thumbnail: e.target.files[0],
            })
          }
          accept="image/*"
        />
        <div ref={refdivNew} />
        <button className="btn form-btn" type="submit">
          Add Blog
        </button>
      </form>

      <Modal isOpen={editModalIsOpen} onRequestClose={closeEditModal}>
        {editBlog && (
          <form onSubmit={saveEditedBlog} className="blog-form">
            <select
              value={editBlog.mainSection}
              onChange={(e) => {
                setEditBlog({ ...editBlog, mainSection: e.target.value });
                fetchSubSections(e.target.value);
              }}
            >
              <option value="">Select Main Section</option>
              {mainSections.map((section) => (
                <option key={section._id} value={section._id}>
                  {section.title}
                </option>
              ))}
            </select>
            <select
              value={editBlog.subSection}
              onChange={(e) =>
                setEditBlog({ ...editBlog, subSection: e.target.value })
              }
            >
              <option value="">Select Sub Section</option>
              {subSections.map((subSection) => (
                <option key={subSection._id} value={subSection._id}>
                  {subSection.title}
                </option>
              ))}
            </select>
            <input
              type="text"
              name="title"
              placeholder="Enter blog title"
              value={editBlog.title}
              onChange={handleEditChange}
            />
            <input
              type="text"
              name="description"
              placeholder="Enter blog description"
              value={editBlog.description}
              onChange={handleEditChange}
            />
            <input
              type="file"
              onChange={handleEditThumbnailChange}
              accept="image/*"
            />

            <div ref={refdivEdit} />

            <button className="btn form-btn" type="submit">
              Save Changes
            </button>
          </form>
        )}
      </Modal>
    </div>
  );
};

export default Blogs;

refdivEdit.current is null when the modal is loaded, so rich text editor is not rendering. What should I do in this case?

I did read somewhere on Stack Overflow that useRef hook will not work in modals as the component will mount but the jsx will not render until you make show prop to true. useRef is asynchronous in nature, that's why at the time of declaration it sets current to null but after you assign it to any element ref got value of it. But in case of modals the case is different. Here the elements are not registered instantly but after modal show prop is set to true.

I am new to React and JavaScript.

I am expecting rich text editor to render on modal open.

Upvotes: 0

Views: 50

Answers (1)

Hamude Shahin
Hamude Shahin

Reputation: 323

Modal content isn't rendered until the modal opens.

So try this code:

const Blogs = () => {
  // ... other code ...

  const refdivEdit = useRef(null);
  const rteEdit = useRef(null);

  useEffect(() => {
    if (editBlog && refdivEdit.current) {
      if (!rteEdit.current) {
        rteEdit.current = new window.RichTextEditor(refdivEdit.current);
      }
      rteEdit.current.setHTMLCode(editBlog.content || ""); // Initialize RTE
    }
  }, [editBlog]); // Re-run only when editBlog changes

  const openEditModal = (blog) => {
    setEditBlog(blog);
    setEditThumbnail(blog.thumbnail);
    setEditModalIsOpen(true);
  };

  return (
    <div className="blogs-container">
      <h2>Blogs</h2>
      {/* ... other code ... */}

      <Modal isOpen={editModalIsOpen} onRequestClose={closeEditModal}>
        {editBlog && (
          <form onSubmit={saveEditedBlog} className="blog-form">
            {/* ... other form fields ... */}
            <div ref={refdivEdit} />
            <button className="btn form-btn" type="submit">
              Save Changes
            </button>
          </form>
        )}
      </Modal>
    </div>
  );
};

Upvotes: 0

Related Questions