My code flow is that a user fills the form and submits it which goes to action calling the API and then it returns the postId
and stores it in the reducer. Now my main React component has useSelector
to get the latest state for the postId
. So after calling the action and submitting the form I am doing the navigate to that post through '/classifieds/{postId}'
but postId
shows null inside the handleSubmit
function whereas it shows the value of postId
outside that function. So probably my way of thinking is wrong. Can someone help me in suggesting how should be the flow?
classifieds.js
import React, { useEffect, useState } from "react";
import { TextEditor } from "../../../components/Text-Editor/text-editor";
import Button from 'react-bootstrap/Button';
import Form from 'react-bootstrap/Form';
import InputGroup from 'react-bootstrap/InputGroup';
import { useDispatch, useSelector } from "react-redux";
import { getCategories, postClassifieds } from "../../../redux/actions/classifiedsAction";
import './post-classified.scss'
import { useNavigate } from "react-router-dom";
export const PostClassified = () => {
const dispatch = useDispatch();
const navigate = useNavigate();
const classifiedsLoading = useSelector((state) => state.classifieds.loading);
const postId = useSelector((state) => state.classifieds.postId);
console.log("postID is 1",postId) // this shows up with the id when handleSubmit is called
const handleInputChange = (e) => {
const { name, value, type, checked } = e.target;
const inputValue = type === 'checkbox' ? checked : value;
setFormData({ ...formData, [name]: inputValue });
};
const [formData, setFormData] = useState({
heading: '',
category:''
});
const [formSubmitMessage, setFormSubmitMessage] = useState(false);
const newFormData = new FormData();
const handleSubmit = async (e) => {
e.preventDefault();
setFormSubmitMessage(false);
newFormData.append("heading",formData.heading);
newFormData.append("category",formData.category);
await dispatch(postClassifieds(newFormData));
console.log("postID is 2",postId) // this shows up null
navigate(`/classifieds/${postId}`)
};
return (
<div className="section properties">
<div className="container">
{classifiedsLoading
? (<></>)
: <Form onSubmit={handleSubmit} encType="multipart/form-data" className="form-post">
{/* form.............. */}
</Form>
}
</div>
</div>
);
}
classifiedsAction.js
import axios from "axios";
export const POST_SUCCESS = "POST_SUCCESS";
export const POST_FAILURE = "POST_FAILURE";
export const CLASSIFIEDS_LOADING = "CLASSIFIEDS_LOADING";
// post classified
export const postClassifieds = (formData) => async(dispatch) => {
try {
const response = await axios.post('/posts', formData);
dispatch({
type: 'POST_SUCCESS',
payload: response.data._id, // Assuming the server returns the new document's ID
});
} catch(error) {
console.log("error is", error);
dispatch({
type: 'POST_FAILURE',
payload: error.message,
});
}
};
// Classifieds loading
export const setClassifiedsLoading = () => {
return {
type: CLASSIFIEDS_LOADING,
};
};
classifiedsReducer.js
import {
CLASSIFIEDS_LOADING,
POST_SUCCESS,
POST_FAILURE
} from "../actions/classifiedsAction";
const initialState = {
loading: true,
postID: null,
error: null
};
export const ClassifiedsReducer = (state = initialState, action) => {
switch (action.type) {
case POST_SUCCESS:
return {
...state,
postId: action.payload,
loading: false,
};
case POST_FAILURE:
return {
...state,
error: action.payload,
loading: false,
};
case CLASSIFIEDS_LOADING:
return {
...state,
loading: true,
};
default:
return state;
}
};
The issue here is that the submit handler has a stale closure over the selected postId
state.
Update the code such that your postClassifieds
action returns a resolved value to the calling code that can be awaited.
Example:
export const postClassifieds = (formData) => async(dispatch) => {
try {
const { data } = await axios.post('/posts', formData);
dispatch({
type: 'POST_SUCCESS',
payload: data._id,
});
return data._id; // <-- return postId to UI/calling code
} catch(error) {
console.log("error is", error);
dispatch({
type: 'POST_FAILURE',
payload: error.message,
});
throw error; // <-- re-throw error for UI/calling code
}
};
Wrap the asynchronous logic in a try/catch
to handle the returned postId
value or any thrown errors or Promise rejections.
const handleSubmit = async (e) => {
e.preventDefault();
try {
setFormSubmitMessage(false);
const newFormData = new FormData();
newFormData.append("heading", formData.heading);
newFormData.append("category", formData.category);
const postId = await dispatch(postClassifieds(newFormData));
// Success if we get this far 😀
console.log("postID is 2",postId);
navigate(`/classifieds/${postId}`);
} catch(error) {
// handle/ignore fetch failures/etc
}
};