Search code examples
javascriptreactjsreduxreact-reduxredux-toolkit

Redux does not updates state


App.js:

function App() {
  return (
    <main>
      <AddPostForm />
      <PostList />
    </main>
  );
}

store.js:

export const store = configureStore({
  reducer: {
    posts: postsReducer
  }
})

postSlice.js:

const initialState = [
  { id: 1, title: 'Learning Redux Toolkit', content: 'I have heard good things.' },
  { id: 2, title: 'Slices...', content: 'The more I say slice, the more I want pizza.' }
];

const postsSlice = createSlice({
  name: 'posts',
  initialState,
  reducers: {
    postAdded: (state, action) => {
      console.log(action.payload)
      state.push(action.payload);
    }
  }
});

export const selectAllPosts = state => state.posts;

export const { postAdded } = postsSlice.actions;

export default postsSlice.reducer;

AddPostForm.js:

function AddPostForm() {
  const dispatch = useDispatch();
  const [title, setTitle] = useState('');
  const [content, setContent] = useState('');

  function onSubmit() {
    if (title && content) {
      console.log('after if');
      dispatch(postAdded({
        id: nanoid(),
        title,
        content
      }));
      setTitle('');
      setContent('');
    }
  }

  return (
    <section onSubmit={onSubmit}>
      <h2>Add New Post</h2>
      <form>
        <label htmlFor="title">Post Title:</label>
        <input
          type="text"
          id="title"
          value={title}
          onChange={e => setTitle(e.target.value)}
        />
        <label htmlFor="content">Content:</label>
        <textarea
          id="content"
          value={content}
          onChange={e => setContent(e.target.value)}
        />
        <button>Save Post</button>
      </form>
    </section>
  )
}

export default AddPostForm

PostList.js:

function PostList() {
  const posts = useSelector(selectAllPosts);

  return (
    <section>
      <h2>Posts</h2>
      {posts.map(post => (
        <article key={post.id}>
          <h3>{post.title}</h3>
          <p>{post.content.substring(0, 100)}</p>
        </article>
      ))}
    </section>
  )
}

export default PostList

The postAdded reducer is pushing to the state, but after I add a test post (via the form), I dont see the form added. However the console.log(action.payload) does shows the object (for example Object { id: "rTuWGH9pfK-cQvCZecSvt", title: "asd", content: "asd" }). So why is it not pushing it into the state?


Solution

  • The button element has type="submit" by default if not specified. The issue here is that the form element is submitted and and default form action is not prevented. The result is that the page reloads, e.g. the React app is remounted and all state is the initial state.

    Move the onSubmit handler to the form element and call preventDefault on the onSubmit event object. Also, it's highly advisable to be explicit with attributes, add type="submit" to the button element.

    function AddPostForm() {
      const dispatch = useDispatch();
    
      const [title, setTitle] = useState('');
      const [content, setContent] = useState('');
    
      function onSubmit(event) {
        event.preventDefault(); // <-- prevent form submission, don't reload page
    
        if (title && content) {
          dispatch(postAdded({ title, content }));
          setTitle('');
          setContent('');
        }
      }
    
      return (
        <section>
          <h2>Add New Post</h2>
          <form onSubmit={onSubmit}> // <-- onSubmit on form element
            <label htmlFor="title">Post Title:</label>
            <input
              type="text"
              id="title"
              value={title}
              onChange={e => setTitle(e.target.value)}
            />
            <label htmlFor="content">Content:</label>
            <textarea
              id="content"
              value={content}
              onChange={e => setContent(e.target.value)}
            />
            <button type="submit"> // <-- explicit button type
              Save Post
            </button>
          </form>
        </section>
      );
    }
    
    import { createSlice, nanoid } from '@reduxjs/toolkit';
    
    ...
    
    const postsSlice = createSlice({
      name: 'posts',
      initialState,
      reducers: {
        postAdded: {
          reducer: (state, action) => {
            state.push(action.payload);
          },
          prepare: (payload) => ({
            payload: {
              ...payload,
              id: nanoid();
            },
          })
        },
      },
    });