skip to Main Content

I am dispatching an action that is supposed to take an input from the user and store it in a database. However, when I inspect my posts state in redux after the action is dispatched, there is a null value appended to the state array before the actual post. This is preventing me from working with the actual data in the posts array. Basically I’m wondering how to prevent null from being appended each time I dispatch a new post. Here are the relevant code enter image description heresnippets and images.

Post Reducer:

import { enableAllPlugins, produce } from 'immer';

enableAllPlugins();

const initialState = {
  posts: [],
  loading: false,
  error: false,
  uploading: false,
};

const postReducer = produce((draftstate, action = {}) => {
  switch (action.type) {
    case 'UPLOAD_START':
      draftstate.loading = true;
      draftstate.error = false;
    case 'UPLOAD_SUCCESS':
      draftstate.posts.push(action.data);
      draftstate.uploading = false;
      draftstate.error = false;
    case 'UPLOAD_FAIL':
      draftstate.uploading = false;
      draftstate.error = true;
    default:
      return draftstate;
  }
}, initialState);

export default postReducer;

Upload Post action:

export const uploadPost = (data) => async (dispatch) => {
  dispatch({ type: 'UPLOAD_START' });
  try {
    const newPost = await UploadApi.uploadPost(data);
    console.log('new post before', newPost);
    dispatch({ type: 'UPLOAD_SUCCESS', data: newPost.data });
  } catch (error) {
    console.log(error);
    dispatch({ type: 'UPLOAD_FAIL' });
  }
};

Share Post code:

import React, { useState, useRef } from "react";
import ProfileImage from "../../img/profileImg.jpg";
import "./PostShare.css";
import { UilScenery } from "@iconscout/react-unicons";
import { UilPlayCircle } from "@iconscout/react-unicons";
import { UilLocationPoint } from "@iconscout/react-unicons";
import { UilSchedule } from "@iconscout/react-unicons";
import { UilTimes } from "@iconscout/react-unicons";
import { useSelector, useDispatch } from "react-redux";
import { uploadImage, uploadPost } from "../../actions/uploadAction";


const PostShare = () => {
  const loading = useSelector((state) => state.postReducer.uploading);
  const [image, setImage] = useState(null);
  const imageRef = useRef();
  const desc = useRef();
  const dispatch = useDispatch();

  const { user } = useSelector((state) => state.authReducer.authData);

  // handle Image Change
  const onImageChange = (event) => {
    if (event.target.files && event.target.files[0]) {
      let img = event.target.files[0];
      setImage(img);
    }
  };

  const reset = () => {
    setImage(null);
    desc.current.value = "";
  };

  const handleSubmit = async (e) => {
    e.preventDefault();

    const newPost = {
      userId: user._id,
      desc: desc.current.value,
    };

    if (image) {
      const data = new FormData();
      const filename = Date.now() + image.name;
      data.append("name", filename);
      data.append("file", image);
      newPost.image = filename;

      console.log(newPost);

      try {
        dispatch(uploadImage(data));
      } catch (error) {
        console.log(error);
      }
    }

    dispatch(uploadPost(newPost));
    reset();
  };

  return (
    <div>
      <div className="PostShare">
        <img src={ProfileImage} alt="" />
        <div>
          <input
            ref={desc}
            required
            type="text"
            placeholder="What's happening"
          />
          <div className="postOptions">
            <div
              className="option"
              style={{ color: "var(--photo)" }}
              onClick={() => imageRef.current.click()}
            >
              <UilScenery />
              Photo
            </div>

            <div className="option" style={{ color: "var(--video" }}>
              <UilPlayCircle />
              Video
            </div>

            <div className="option" style={{ color: "var(--location)" }}>
              <UilLocationPoint />
              Location
            </div>

            <div className="option" style={{ color: "var(--shedule)" }}>
              <UilSchedule />
              Schedule
            </div>

            <button
              className="button ps-button"
              onClick={handleSubmit}
              disabled={loading}
            >
              {loading ? "Uploading..." : "Share"}
            </button>
            <div style={{ display: "none" }}>
              <input
                type="file"
                name="myImage"
                ref={imageRef}
                onChange={onImageChange}
              />
            </div>
          </div>
          {image && (
            <div className="previewImage">
              <UilTimes onClick={() => setImage(null)} />
              <img src={URL.createObjectURL(image)} alt="" />
            </div>
          )}
        </div>
      </div>
    </div>
  );
};

export default PostShare;

I would be glad to provide any other details if that helps.

Update with other portions of code:

Dispatcher of RETRIEVING_SUCCESS:

import * as PostApi from '../api/PostRequest';

export const getTimelinePosts = (id) => async (dispatch) => {
  dispatch({ type: 'RETRIEVING_START' });

  try {
    const { data } = await PostApi.getTimelinePosts(id);
    dispatch({ type: 'RETRIEVING_SUCCESS', data: data });
  } catch (error) {
    dispatch({ type: 'RETRIEVING_FAIL' });
    console.log(error);
  }
};

getTimelinePosts usage:

import React, { useEffect } from 'react';
import './Posts.css';
import { PostsData } from '../../Data/PostsData';
import { useDispatch, useSelector } from 'react-redux';
import { getTimelinePosts } from '../../actions/postAction';
import Post from '../Post/Post';

const Posts = () => {
  const dispatch = useDispatch();
  const { user } = useSelector((state) => state.authReducer.authData);
  let { posts, loading } = useSelector((state) => state.postReducer);

  console.log('posts content', posts);

  useEffect(() => {
    dispatch(getTimelinePosts(user._id));
  }, []);

  return (
    <div className="Posts">
      {/* {posts.map((post, id) => {
        return <Post data={post} id={id}></Post>;
      })} */}
    </div>
  );
};

export default Posts;

2

Answers


  1. Chosen as BEST ANSWER

    Okay I found a solution to the null entries in my posts array. Although I'm not sure why that works, it only appends actual posts to the posts array and no null entried. All I had to do was change the way my reducer updated the state. I was using immer initially, but not anymore. Here is the code:

    const postReducer = (
      state = { posts: [], loading: false, error: false, uploading: false },
      action
    ) => {
      switch (action.type) {
        case 'UPLOAD_START':
          return { ...state, uploading: true, error: false };
        case 'UPLOAD_SUCCESS':
          return {
            ...state,
            posts: [action.data, ...state.posts],
            uploading: false,
            error: false,
          };
        case 'UPLOAD_FAIL':
          return { ...state, uploading: false, error: true };
        default:
          return state;
      }
    };
    

  2. in postReducer, let’s remove the default on the switch statement, we don’t need it on reducer because other actions will come here and the code make all states return the initial state.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search