Search code examples
javascriptreactjsreduxreact-redux

How can I persist redux state


I'm developing a CRUD and, in one of the components, when the user creates the post, a div is rendered with the title and content values of his post. I need his posts to be saved even when he navigates between pages (without refreshing the page). Currently I can create as many posts as I want and they will be lined up one below the other, but when I go back to the previous page they are deleted. I tried to implement redux in this part the way I implemented it to save the user input (creating a slice to save the posts) but it didn't work. I know normally posts wouldn't be deleted, so I'd like to know where I'm going wrong.

signup screen:

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;

CRUD screen:

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 postsSlice from '../redux/postsslice'

import Modal from "../components/modal.jsx";

function MainScreen() {

  const dispatch = useDispatch();

  const user = useSelector((state) => state.user)
  const loadPosts = useSelector((state) => state.loadPosts)

  const [title, setTitle] = useState("");
  const [content, setContent] = useState("");
  const [newPosts, setNewPosts] = useState([])
  const [buttonGreyOut, setButtonGreyOut] = useState("#cccccc");

  useEffect(() => {
    if (title && content !== "") {
      setButtonGreyOut("black");
    } else {
      setButtonGreyOut("#cccccc");
    }
  },[title, content]);



  const handleSubmitSendPost = (e) => {
    e.preventDefault();
    setNewPosts(newPosts.concat({title, content}))
    dispatch(postsSlice.actions.savePosts(newPosts))
    setTitle('')
    setContent('')
  };

  const handleChangeTitle = (text) => {
    setTitle(text);
  };

  const handleChangeContent = (text) => {
    setContent(text);
  };


  const [openModal, setOpenModal] = useState();

  if (user === '') {
    return <Navigate to="/" />
  } else {
    return (
      <div className="containerMainScreen">
        {openModal && <Modal closeModal={setOpenModal} />}
        <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>
  
        {newPosts.map((post) => (
          <div className="boxPost">
            <div className="bar">
              <h1>{post.title}</h1>
              <MdDeleteForever
                className="icon"
                onClick={() => {
                  setOpenModal(true);
                }}
              />
              <FiEdit
                style={{ color: "white", fontSize: "45px", paddingLeft: "23px" }}
              />
            </div>
            <div id="postowner">
              <h3>@{user}</h3>
            
             
              <h3>25 minutes ago</h3>
              <br></br>
              <textarea style={{ border: "none" }}>{post.content}</textarea>
            </div>
          </div>
        ))}
      </div>
    );
  }
  
  
  }export default MainScreen;

store.js:

import { configureStore } from '@reduxjs/toolkit';
import userSlice from './userslice';
import postsSlice from './postsslice'

export const store = configureStore({
  reducer: {
      user: userSlice.reducer,
      loadPosts: postsSlice.reducer
  },
})

postsSlice:

import { createSlice } from "@reduxjs/toolkit";

const postsSlice = createSlice({
  name: "posts",
  initialState: "",
  reducers: {
    savePosts: (state, action) => action.payload
  }
});

export default postsSlice

CRUD screenshot: https://i.sstatic.net/YoCJz.png


Solution

  • You are dispatching the savePosts action with the value of newPosts from before the current posts are added to it. The problem is in these lines:

    setNewPosts(newPosts.concat({title, content}))
    dispatch(postsSlice.actions.savePosts(newPosts))
    

    Try something like this instead:

    const posts = newPosts.concat({title, content})
    setNewPosts(posts)
    dispatch(postsSlice.actions.savePosts(posts))
    

    It does not make sense to store the posts array in Redux and also store it in local component state. In my opinion you should ditch the component newPosts state and access the posts via Redux with useSelector.

    I would also recommend dispatching an addPost action which requires just the current post. Let the reducer handle adding it to the array.

    The state is an array of posts, so your initialState should be an empty array rather than an empty string.

    const postsSlice = createSlice({
      name: "posts",
      initialState: [],
      reducers: {
        // add one post to the array.
        addPost: (state, action) => {
          state.push(action.payload); // modifies the draft state.
        },
        // replace the entire array.
        replacePosts: (state, action) => action.payload
      }
    });
    
    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 handleSubmitSendPost = (e) => {
        e.preventDefault();
        dispatch(postsSlice.actions.addPost({title, content}))
        setTitle('')
        setContent('')
      };