I'm trying to learn redux-toolkit with a project.
I'm using toEditPostId
variable (initial value is null) inside the store to get the id of the post I want to edit, the app works fine when I set its value to an id string, but when I click the clear button in my form component which calls dispatch(setToEditPostId(null))
, then the entire app re-renders and handleSubmit
function is automatically called and that then adds a new empty post, which I don't want to happen.
I have added ****************
near the snippets which are causing problem.
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import axiosInstance from "../utils/axiosInstance";
export const fetchMemoryPosts = createAsyncThunk(
'posts/fetchPosts',
async (_, thunkApi) => {
try {
const response = await axiosInstance.get('/posts');
return response.data.posts;
} catch (error) {
const errorMessage = error.response.data.match(/Error: (.*?)<br>/)[1];
const errorCode = error.response.status;
return thunkApi.rejectWithValue({ errorMessage, errorCode });
}
}
);
export const createMemoryPost = createAsyncThunk(
'posts/createPosts',
async(data, thunkApi) => {
const tagsArr = data.tags.split(',').map((ele) => ele.trim());
const preparedData = { ...data, tags: tagsArr };
try {
const response = await axiosInstance.post(
'/posts/create',
preparedData
);
return response.data.data;
} catch (error) {
const errorMessage = error.response.data.match(/Error: (.*?)<br>/)[1];
const errorCode = error.response.status
return thunkApi.rejectWithValue({ errorMessage, errorCode })
}
}
)
const initialState = {
posts: [],
status: 'idle',
error: null,
// The state variable to track the id of the post to edit *************
toEditPostId: null,
}
const postsSlice = createSlice({
name: 'posts',
initialState,
reducers: {
// changing the value to an id and back to null here ******************
setToEditPostId(state, action) {
state.toEditPostId = action.payload;
}
},
extraReducers(builder) {
builder
.addCase(fetchMemoryPosts.pending, (state, _) => {
state.status = 'loading';
})
.addCase(fetchMemoryPosts.fulfilled, (state, action) => {
state.status = 'success';
state.posts = action.payload;
})
.addCase(fetchMemoryPosts.rejected, (state, action) => {
state.status = 'failure';
state.error = action.payload;
})
.addCase(createMemoryPost.fulfilled, (state, action) => {
state.status = 'success';
state.posts = state.posts.concat(action.payload);
})
.addCase(createMemoryPost.rejected, (state, action) => {
state.status = 'failure';
state.error = action.payload;
})
}
});
export const getAllPostsSelector = (state) => state.posts.posts;
export const getPostsErrorSelector = (state) => state.posts.error;
export const getPostsStatusSelector = (state) => state.posts.status;
export const getToEditPostIdSelector = (state) => state.posts.toEditPostId;
export const { setToEditPostId } = postsSlice.actions;
export default postsSlice.reducer;
Below is the Form component.
import React, { useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import {
createMemoryPost,
setToEditPostId,
getToEditPostIdSelector,
getAllPostsSelector
} from '../../features/postsSlice';
const Form = () => {
const dispatch = useDispatch();
const allPosts = useSelector(getAllPostsSelector);
const postId = useSelector(getToEditPostIdSelector);
let targetPost;
if (postId) {
targetPost = allPosts.filter((post) => post._id === postId);
}
console.log("pi->", postId)
console.log("po->", targetPost);
const initialPostDataState = {
creator: '', title: '', message:'', tags:''
}
const [postData, setPostData] = useState(initialPostDataState);
// Below function runs unnecessarily when the
// dispatch(setToEditPostId(null)) is called
// *********************************
const handleSubmit = (e) => {
console.log("it ran");
e.preventDefault()
dispatch(createMemoryPost(postData));
setPostData(initialPostDataState);
}
const handleInput = (e) => {
setPostData({ ...postData, [e.target.name]: e.target.value })
}
const clearForm = () => {
dispatch(setToEditPostId(null))
setPostData(initialPostDataState);
}
return (
<main className='bg-transparent_bg w-full flex'>
<form
className='w-full text-center space-y-3 p-2 box-border'
onSubmit={handleSubmit}
>
<h3 className='font-extrabold'>
{postId !== null? "" : "Create a memory"}
</h3>
<input
className='input'
type="text"
placeholder='Creator'
name='creator'
value={postData.creator}
onChange={handleInput}
/>
<input
className='input'
type="text"
placeholder='Title'
name='title'
value={postData.title}
onChange={handleInput}
/>
<textarea
className='input'
placeholder='Message'
name="message"
cols="30"
rows="5"
value={postData.message}
onChange={handleInput}
/>
<input
className='input'
type="text"
placeholder='Tags (coma seperated)'
name='tags'
value={postData.tags}
onChange={handleInput}
/>
<div className='flex justify-around py-1 box-border'>
<button
type='submit'
className='bg-blue-400 w-24'
>
Submit
</button>
<button
// clear button to call dispatch to set value to
// null *********************
onClick={() => dispatch(setToEditPostId(null))}
className='bg-red-500 w-24'
>
Clear
</button>
</div>
</form>
</main>
)
}
export default Form;
button
elements have type="submit"
by default if you do not explicitly specify the button type.
type
The default behavior of the button. Possible values are:
submit
: The button submits the form data to the server. This is the default if the attribute is not specified for buttons associated with a<form>
, or if the attribute is an empty or invalid value.reset
: The button resets all the controls to their initial values, like<input type="reset">
. (This behavior tends to annoy users.)button
: The button has no default behavior, and does nothing when pressed by default. It can have client-side scripts listen to the element's events, which are triggered when the events occur.
The second button to "clear" the toEditPostId
state back to null is submitting the form as well. Specify that the "clear" button is not a "submit"
type button.
<button
type='submit'
className='bg-blue-400 w-24'
>
Submit
</button>
<button
type="button" // <-- non-submit type button
onClick={() => dispatch(setToEditPostId(null))}
className='bg-red-500 w-24'
>
Clear
</button>