Redux state showing previous or default value while submitting function called - reactjs

Async await is not functioning properly on my React project. It is not awaiting a response. Redux state displaying the previous or default value while calling the function but outside the function, it's working fine. As you can see, I print the employee from the selector in the handleSubmit function, however this prints the prior or default state, and I need the updated value to proceed. Data is not being awaited.
// handle submit function
const handleSubmit = async(values) => {
const personalData = new FormData();
Object.keys(personalValue).forEach((key) => personalData.append(key, personalValue[key]));
await dispatch(addEmployeePersonal(personalData));
console.log(employee) // Inside the function Employee prints default or previous state value
};
console.log(employee) // Here it's working fine, Outside the function employee prints updated value
// Selector
const {
employee,
employeeLoading,
employeeError
} = useSelector((state) => state.employee);
// Redux Reducer
export const employeeReducer = (state = {
employee: 0
}, action) => {
switch (action.type) {
case UPDATE_EMPLOYEE_PERSONAL_REQUEST:
case EMPLOYEE_ID_REQUEST:
case NEW_EMPLOYEE_PERSONAL_REQUEST:
case NEW_EMPLOYEE_OFFICIAL_REQUEST:
case DELETE_EMPLOYEE_REQUEST:
return {
...state,
employeeLoading: true,
employee: 0,
};
case UPDATE_EMPLOYEE_PERSONAL_SUCCESS:
case UPDATE_EMPLOYEE_OFFICIAL_SUCCESS:
case EMPLOYEE_ID_SUCCESS:
case NEW_EMPLOYEE_PERSONAL_SUCCESS:
case NEW_EMPLOYEE_OFFICIAL_SUCCESS:
case DELETE_EMPLOYEE_SUCCESS:
return {
...state,
employeeLoading: false,
employee: action.payload,
};
case UPDATE_EMPLOYEE_PERSONAL_FAILED:
case UPDATE_EMPLOYEE_OFFICIAL_FAILED:
case EMPLOYEE_ID_FAILED:
case NEW_EMPLOYEE_PERSONAL_FAILED:
case NEW_EMPLOYEE_OFFICIAL_FAILED:
case DELETE_EMPLOYEE_FAILED:
return {
...state,
employeeLoading: false,
employeeError: action.payload,
};
case CLEAR_ERRORS:
return {
...state,
employeeError: null,
};
default:
return state;
}
};
// Redux Action
export const addEmployeePersonal = (info) => async(dispatch) => {
try {
dispatch({
type: NEW_EMPLOYEE_PERSONAL_REQUEST
});
const {
data
} = await coreAxios.post("/api/Employee/PersonalInfo", info);
dispatch({
type: NEW_EMPLOYEE_PERSONAL_SUCCESS,
payload: data,
});
} catch (error) {
dispatch({
type: NEW_EMPLOYEE_PERSONAL_FAILED,
payload: error.response,
});
}
};

Reason: At the first render of the component, the value employee is 0. You trigger the form submit handler, it uses the employee, which value is 0. Even though you dispatch an action and try to change it, the handleSubmit still uses the employee evaluated in the first render(execution of function component) of the component before finishing executing. await dispatch(thunk()) will NOT wait for next render of the component. That's why you get the previous value of employee.
After handleSubmit finishes its execution, the state in the redux store has been changed, the redux store context provider will subscribe to that change and rerender the children. Your component will re-render(the second execution of the function component), useSelector will execute again and return the new employee. A new handleSubmit function will be declared and it reference the new employee defined in the function component scope.
There are two solutions:
Option 1: useEffect
useEffect(() => {
// get the latest employee to do something.
}, [employee])
Option 2: return action payload in thunk so that you can get it after dispatching action
export const addEmployeePersonal = (info) => async(dispatch) => {
try {
dispatch({
type: NEW_EMPLOYEE_PERSONAL_REQUEST
});
const {
data
} = await coreAxios.post("/api/Employee/PersonalInfo", info);
dispatch({
type: NEW_EMPLOYEE_PERSONAL_SUCCESS,
payload: data,
});
return data; // here
} catch (error) {
dispatch({
type: NEW_EMPLOYEE_PERSONAL_FAILED,
payload: error.response,
});
}
};
const handleSubmit = async(values) => {
const personalData = new FormData();
Object.keys(personalValue).forEach((key) => personalData.append(key, personalValue[key]));
const newEmployee = await dispatch(addEmployeePersonal(personalData));
// Do something with newEmployee.
};

Related

It seems to ignore the Async / Await

On a React page, I have a method called goOut. This method calls upon a Redux action > Node controller > Redux reducer. I can confirm that the correct data values are returned inside the Redux action, the controller method, and the reducer. However, nonetheless, at point 1 below inside the goOut method, it returns undefined.
What am I doing wrong / how could it return undefined if the the reducer is returning the correct values? It is as if the await inside the goOut method is not working...
React page:
import { go_payment } from "../../appRedux/actions/paymentAction";
<button onClick={this.goOut}>
Button
</button>
async goOut(ev) {
try {
const data = { user: parseInt(this.state.userId, 10) };
let result = await this.props.go_payment({data});
console.log(result);
// 1. RETURNS UNDEFINED. As if it tries to execute this line before it has finished the previous line.
{
}
const mapDispatchToProps = (dispatch) => {
return bindActionCreators(
{go_payment}, dispatch
);
};
Redux Action:
export const go_payment = (data) => {
let token = getAuthToken();
return (dispatch) => {
axios
.post(`${url}/goController`, data, { headers: { Authorization: `${token}` } })
.then((res) => {
if (res.status === 200) {
// console.log confirms correct data for res.data
return dispatch({ type: GO_SUCCESS, payload: res.data });
})
}
}
Node controller method:
Returns the correct data in json format.
Reducer:
export default function paymentReducer(state = initial_state, action) {
switch (action.type) {
case GO_SUCCESS:
// console.log confirms action.payload contains the correct data
return { ...state, goData: action.payload, couponData: "" };
}
}

React Redux Reducers and Immutable Updates

In a previous scenario to Update Profile values, i created a new store slice (updatedProfileDetails) and stored a new object there, but i think this isn't the best practice to solve my problem (since i have now two slices profileDetails & updateProfileDetails),
Now I found in redux documentation that we can make immutable update reducers to change data in the same slice without mutate states but in this scenario (set conversation unseen to false) , the API don't send an object as a response but just a success message,
SO, I'm trying to passe my conversation ID from Action To reducer to check it and change a single value of this conversation[id]
but i'm unable to make it correctly
My code:
1- Action: where I'm supposed to send id after action success
export const updateSeenConversation = (id) => async (dispatch, getState) => {
try {
dispatch({
type: SEEN_CONVERSATIONS_REQUEST,
})
const {
userLogin: { userInfo },
} = getState()
const config = {
headers: {
// headers
},
}
const { data } = await axios.put(
`${process.env.REACT_APP_API_KEY}/conversation/unseen/${id}`,
"",
config
)
// if success data : {success:true , message:"..."}
dispatch({
type: SEEN_CONVERSATIONS_SUCCESS,
payload: id, // usually i send data sent from API as dispatch payload,, to check for the exact conversation to update i tried to send id
})
} catch (error) { //error...})
}
}
2- my reducer:
export const conversationsListReducer = (
state = { conversations: {} },
action
) => {
// get conversations works Fine and return a slice conversations{..} that i need to update
switch (action.type) {
case GET_CONVERSATIONS_REQUEST:
return { ...state, loading: true }
case GET_CONVERSATIONS_SUCCESS:
return { loading: false, conversations: action.payload }
case GET_CONVERSATIONS_FAIL:
return { loading: false, error: action.payload }
// here ERROR
// i cant access the exact value i want "unseen" **NB:CHECK store in IMAGE BELLOW**
case SEEN_CONVERSATIONS_REQUEST:
return { ...state, loading: true }
case SEEN_CONVERSATIONS_SUCCESS:
return {state.conversations.conversation.map((conversation) => {
if (conversation._id !== action.payload) {
return conversation // return without change if !== myid
}
return {
...conversation, // make a copy
unseen: false, // change this value to false/true
}
})}
case SEEN_CONVERSATIONS_FAIL:
return { loading: false, error: action.payload }
case USER_LOGOUT:
return { conversations: {} }
default:
return state
}
}
Redux store Slice Image Link
Thank You.
Ok, I still don't see where in your post you say what isn't working but I'm gathering it is something in the SEEN_CONVERSATIONS_SUCCESS reducer case since that's where you focused some comments.
From what I can tell in this reducer case, you are mutating the state invariant from { conversations: {} } to { [] } since mapping returns an array, and isn't a valid object. When updating state you need to shallow copy the state object and also any nested state being updated.
case SEEN_CONVERSATIONS_SUCCESS:
return {
...state, // <-- shallow copy state
conversations: {
...state.conversations // <-- shallow copy state.conversations
conversation: state.conversations.conversation.map( // <-- shallow copy conversation array
conversation => conversation._id === action.payload
? {
...conversation, // <-- shallow copy conversation
unseen: false
}
: conversation
),
}
};
Since this reducer case is accessing nested state properties that are possibly undefined, you should also fully declare your state.
const initialState = {
conversations: {
conversation: [], // <--- now access this deep won't throw error
},
loading: false,
error: null,
};
export const conversationsListReducer = (
state = initialState,
action
) => {
...
case USER_LOGOUT:
return initialState; // <-- reset to initial state

Is there a way to save [[Promise Result]] as a state variable in the 'index.js'page?

I tried to get Array from asynchronous call in 'index.js' file.
// index.js
const loadItem = async () => {
const items = await axios.get("https://api.example.com")
.then(res => {
return res.data
})
.catch(() => {
console.log('error')
});
console.log([items]) // Array(25)
return [items]
};
loadItem()
//And want to put this result value to in initstate(to make reducer) as below.
const initState = loadItem();
function reducer(state=initState, action) {
return state
}
But, after "console.log(props.state)" at App.js page, I can see the result below
// App.js
Promise
__proto__: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: Array(25)
In this case, how can I access the [PromiseResult] as a state value?
(I'd like to put this Array value as a initState value in 'index.js' page)
I hope to use this Array, but I can't find the way to access this PromiseResult state.
You can put initState as an empty Array first and dispatch an action to update the state when data come back from the api call like this
// reducer.js
const initState = []
function reducer(state=initState, action) {
switch(action.type){
case "UPDATE_DATA": return action.data
default: return state
}
}
// index.js
const loadItem = async () => {
try{
const items = await axios.get("https://api.example.com")
console.log([items]) // Array(25)
dispatch({action: "UPDATE_DATA", data: items})
} catch(e){
console.log('error')
}
};
loadItem()

React Redux - How to make a double dispatch

I'm fetch some data from my API and it correctly works. But when a double dispatch on the same page the API doesn't work anymore. It's better code to explain it:
Server:
router.get("/", (req, res) => {
let sql = "SELECT * FROM design_categories";
let query = connection.query(sql, (err, results) => {
if (err) throw err;
res.header("Access-Control-Allow-Origin", "*");
res.send(results);
});
});
router.get("/", (req, res) => {
let sql = "SELECT * FROM food_categories";
let query = connection.query(sql, (err, results) => {
if (err) throw err;
res.header("Access-Control-Allow-Origin", "*");
res.send(results);
});
});
They work.
action.js
export const fetchDesignCat = () => {
setLoading()
return async dispatch => {
const response = await axios
.get("http://localhost:5000/api/designcategories")
.then(results => results.data)
try {
await dispatch({ type: FETCH_DESIGN_CAT, payload: response })
} catch (error) {
console.log("await error", error)
}
}
}
export const fetchFoodCat = () => {
setLoading()
return async dispatch => {
const response = await axios
.get("http://localhost:5000/api/foodcategories")
.then(results => results.data)
try {
await dispatch({ type: FETCH_FOOD_CAT, payload: response })
} catch (error) {
console.log("await error", error)
}
}
}
Both of them work perfectly.
reducer.js
const initalState = {
db: [],
loading: true,
designcat: [],
foodcat: [],
}
export default (state = initalState, action) => {
switch (action.type) {
// different cases
case FETCH_DESIGN_CAT:
return {
designcat: action.payload,
loading: false,
}
case FETCH_FOOD_CAT:
return {
food: action.payload,
loading: false,
}
}
The reducer updates the states perfectly.
Page settings.js
const Settings = ({ designcat, foodcat, loading }) => {
const dispatch = useDispatch()
// ... code
useEffect(() => {
dispatch(fetchDesignCat()) // imported action
dispatch(fetchFoodCat()) // imported action
// eslint-disable-next-line
}, [])
// ... code that renders
const mapStateToProps = state => ({
designcat: state.appDb.designcat,
foodcat: state.appDb.foodcat,
loading: state.appDb.loading,
})
export default connect(mapStateToProps, { fetchDesignCat, fetchFoodCat })(
Settings
)
Now there's the problem. If I use just one dispatch it's fine I get one or the other. But if I use the both of them look like the if the second overrides the first. This sounds strange to me.
From my ReduxDevTools
For sure I'm mistaking somewhere. Any idea?
Thanks!
Your reducer does not merge the existing state with the new state, which is why each of the actions just replace the previous state. You'll want to copy over the other properties of the state and only replace the ones your specific action should replace. Here I'm using object spread to do a shallow copy of the previous state:
export default (state = initalState, action) => {
switch (action.type) {
case FETCH_DESIGN_CAT:
return {
...state, // <----
designcat: action.payload,
loading: false,
}
case FETCH_FOOD_CAT:
return {
...state, // <----
food: action.payload,
loading: false,
}
}
}
Since the code is abbreviated, I'm assuming you're handling the default case correctly.
As an additional note, since you're using connect with the Settings component, you don't need to useDispatch and can just use the already connected action creators provided via props by connect:
const Settings = ({
designcat,
foodcat,
loading,
fetchDesignCat,
fetchFoodCat,
}) => {
// ... code
useEffect(() => {
fetchDesignCat();
fetchFoodCat();
}, [fetchDesignCat, fetchFoodCat]);
// ... code that renders
};
There's also a race condition in the code which may or may not be a problem to you. Since you start both FETCH_DESIGN_CAT and FETCH_FOOD_CAT at the same time and both of them set loading: false after finishing, when the first of them finishes, loading will be false but the other action will still be loading its data. If this case is known and handled in code (i.e., you don't trust that both items will be present in the state if loading is false) that's fine as well.
The solution to that would be either to combine the fetching of both of these categories into one thunk, or create separate sub-reducers for them with their own loading state properties. Or of course, you could manually set and unset loading.

How to prevent non-deterministic state updation in Redux?

When working with Redux, maintaining the shape of the initial state is crucial. The results/data of side effects like API call will change the shape of the state since we have no control over the properties. For example, consider this initial state:
const book = {
id: 0,
name: 'something'
};
And updation is made to it by the book sub-reducer as follows based on the API data:
//receives `book` part of the state
const bookReducer = (state=book, action) => {
switch(action.type) {
case 'SET_BOOK': {
return { ...action.payload };
} default:
return state;
}
}
Two scenarios that could happen:
If the data sent from the API is null, then newly produced state is now {} as a result of the spread operator. If some parts of UI were to listen to the book part of the state, then it will break. Possibly access individual properties from the API data? In that case, null/undefined checks needs to be performed for properties. Is there a more elegant solution?
There could also be additional properties in the data which we may not be interested in. Possibly use an object mapper to filter unused properties?
What is the best practice to handle these kind of scenarios and prevent state becoming non-deterministic? Please share your experience on how you approached these scenarios.
Only the reducer has to be pure/deterministic, not the stuff outside of it.
To prevent your reducer from overwriting data incorrectly, write some logic between the API response and the dispatch-call to ensure the reducer always gets valid data.
For example a thunk might look like:
const createBook = (name) => {
return async dispatch => {
// suppose the api call gives back "uid" plus extra data
const { uid, ...unneededData } = await myApi.setBook(name);
// dispatch the data in the way the reducer expects it
dispatch({ type: 'SET_BOOK', id: uid, name });
}
}
In the above example, the api call gives me uid, but no name, and a bunch of extra data. Just prepare the data before dispatching it.
The best practice is the one where you prevent your app from breaking from every aspect, which means you need to check and format your data before returning from the reducer.
In your case, I would check for both data validity and map it to a necessary format.
only dispatch 'SET_BOOK' if API response has both id and book.
in order to avoid unnecessary additional properties, you can always map your data const book = {id: apiData.id, book: apiData.book} before dispatching.
In your reducer you can do like below. This way only id and name will get updated even though there are other key/values in the response. Also this will make sure that if null values are received then those values will not be updated in the state. Hope this will help in resolving the issue.
//receives `book` part of the state
const bookReducer = (state=book, action) => {
const { type, payload } = action;
switch(type) {
case 'SET_BOOK': {
return {
...state,
...(payload.id && {id: payload.id}),
...(payload.name && {name: payload.name})
};
} default:
return state;
}
}
Your redux reducer logic should not worry about that due to its deterministic nature. You handle your api call and response handling elsewhere (redux thunk or component), and then dispatch the action to set your redux. Building off of your example:
book.reducer.js
const book = {
id: 0,
name: ''
};
const bookReducer = (state=book, action) => {
switch(action.type) {
case 'SET_BOOK': {
return { ...action.payload };
} default:
return state;
}
book.actions.js
const setBook = (book) => ({
type: SET_HEROES,
payload: book
});
// thunk
const findBook = name => async dispatch => {
const book = await bookService.findBook(name);
if (book) {
dispatch(setBook(book));
}
};
book.service.js
const findBook = async (name) => {
// Do your api call
const bookResponse = axios.get(`${apiUrl}/book/search/${name}`);
// Handle the response
if (!bookResponse) {
// Logic you do if book not found
return null;
}
return {id: bookResponse.id, name: bookResponse.name};
}
Now in a component you can just dispatch the findBook call
Component.js
const Component = () => {
const [search, setSearch] = useState('');
const dispatch = useDispatch();
const handleOnSearch = () => {
dispatch(findBook(search));
}
return (
<div>
<input value={search} onChange={(e) => setSearch(e.target.value)}/>
<button onClick={handleOnSearch}>Search</button>
</div>
);
}
If field value from API is undefined then convert it into null and store so that the code doesn't break and operatable. If API gives other params as well then de-structure the API returned object and extract the required fields. So that storing unnecessary data can be avoided.
const bookReducer = (state=book, action) => {
switch(action.type) {
case 'SET_BOOK': {
const {id, name, otherParam1, otherParam2} = action.payload;
return {
id: id || null,
name: name || null,
otherParam1,
otherParam2
}
} default:
return state;
}
}
Having the value null won't break the code instead, it renders nothing
which is better than undefined which breaks the code
Hope this helps you
What I do is to have all of my logic in my action method and create reducers for when an action is correctly fulfilled and another one for when is rejected. In the fulfilled reducer, I would do the regular instructions and in the rejected reducer I would add the data to a variable called error which I always have in my state and use in the frontend to show an error message if needed.
Example
This is an action that creates a house by sending a post request to my api which returns the created object or an error if something went wrong.
export const createHouse = houseData => {
const URL = HTTP://EXAMPLE.URL
return async dispatch => {
try {
const response = await axios.post(`${URL}`, houseData);
const data = await response.data;
dispatch({ type: "CREATE_HOUSE_DRAFT_FULFILLED", data });
} catch (err) {
dispatch({ type: "CREATE_HOUSE_DRAFT_REJECTED", data: err });
}
};
};
Then I would have 2 reducer methos to recieve the fulfilled or the rejected response, like this.
case 'CREATE_HOUSE_DRAFT_FULFILLED': {
return {
houses: [action.data, ...state.houses],
house: action.data,
houseCount: state.houseCount + 1,
fetched: true,
error: null
};
}
case 'CREATE_HOUSE_DRAFT_REJECTED': {
return {
...state,
error: action.data.response.data,
fetched: false,
success: null
};
}
Hope this works for you!

Resources