Why is React-Redux not calling action creator function - reactjs

I have a piece of code which updates user settings when a number input box is called in react-redux. The first portion of the code works fine: The "onChange" function gets called. But now it gets weird. onChange is called twice and the code jumps through a bunch of the react back end, touches the action creator then execution skips the entire function and exits at the closing brace.
I have many other Redux functions created using the same model that work perfectly and cannot understand why this one is not being called.
Here is my code:
mapDispatchToProps
const mapDispatchToProps = (dispatch) => {
return {
SMSAdmin_Get_Users: () => { return dispatch(SMSAdmin_Get_Users()) },
SMSAdmin_Load_User: (userId, userName, oldData = null, startVal = 0, number = 20) =>
{
return dispatch(SMSAdmin_Load_User(userId, userName, oldData, startVal, number))
},
SMSAdmin_Update_User: (user, province, credits) => { return
SMSAdmin_Update_User(user, province, credits)
},
SMSAdmin_setCurrentUpload: (userName) => { return SMSAdmin_setCurrentUpload(userName) }
};
}
Calling Input
<Form.Control type="number"
style={{ width: '100px' }}
defaultValue={this.props.SMSAdmin.user.credits}
id='numCredits'
onChange={(e) => this.updateUser(e.target)}
/>
I know that in the input I don't need to pass in the event but included it should I need it in the future for some purpose.
onChange function
//this gets called twice when making a single change in the input box
updateUser = () => {
var province = document.getElementById("provinceId");
var credits = document.getElementById("numCredits");
var user = document.getElementById("lblUser");
if (user && user.value !== '') { //safeguard to avoid trying to update nothing
//execution hits here once, then after the second hit proceeds into Redux code
this.props.SMSAdmin_Update_User(user.value, province.value, credits.value);
}
}
Redux action creator
// Execution touches here
export const SMSAdmin_Update_User = (user, province, credits) => (dispatch) => {
//then here jumps to bottom
dispatch(Actions.SMSAdmin_User_Updating());
var data = { 'mode': 'UPDATE', 'user': user, 'province': province, 'credits': credits }
var init = fetchInit();//copy to not modify the original
init.method = "POST";
init.body = JSON.stringify(data);
var myReq = new Request('/dataAPI/SMS', init);
return fetch(myReq)
.then((response) => {
if (response.ok) {
return response;
}
else {
var error = new Error("Error " + response.statusText);
error.response = response;
throw error;
}
}, (error) => {
var err = new Error(error.message);
throw err;
})
.then((response) => { return response.json() })
.then((resp) => {
handleData(dispatch, Actions.SMSAdmin_User_Updated, resp)
})
.catch((err) => {
var msg = err.message;
return dispatch(Actions.SMS_Send_Failed(msg));
});
} //touches here and exits
I think that this is all the code involved. If I am missing anything let me know and I will include it. I am baffled how execution touches the function but does not execute it, nor does Chrome debug break at a breakpoint set on the function header.

The issue is that action is not dispatched properly. dispatch is not attached to SMSAdmin_Update_User function.
Inside mapDispatchToProps, update the following:
SMSAdmin_Update_User: (user, province, credits) => {
// Add dispatch here. It is already done for other functions such as SMSAdmin_Load_User
return dispatch(SMSAdmin_Update_User(user, province, credits))
},

Related

Why doesn't the parser wait for Promise.resolve?

I am using React and I do not understand why in the useEffect when running a map function the second part of the code runs before the first part (which is a promise resolve).
Shouldn't the parser wait for the promise to resolve and then run the second part of the code?
useEffect(() => {
const pools = mainnet.Exchanges.Pancakeswap.LpTokens.map((lpPool) => {
// part 1
const [tokenZeroSymbol, tokenOneSymbol] = lpPool.name.replace(' LP', '').split('-');
const prices = fetchTokenPrice(tokenZeroSymbol.toLowerCase(), tokenOneSymbol.toLowerCase());
Promise.resolve(prices).then((values) => {
const [priceTokenZero, priceTokenOne] = values;
filteredFarmPools.find((pool) => {
if (lpPool.name.replace(' LP', '') === pool.name) {
pool.priceTokenZero = values[0].usd;
pool.priceTokenOne = values[1].usd;
}
console.log('inside the fethcprice promise');
});
});
// part 2
filteredFarmPools.find((pool) => {
if (lpPool.name.replace(' LP', '') === pool.name) {
const tvl0 = (pool.reserveTokenZero / 10 ** 18) * pool.priceTokenZero;
const tvl1 = (pool.reserveTokenOne / 10 ** 18) * pool.priceTokenOne;
pool.tvl = tvl0 + tvl1;
}
console.log('inside the tvl calc');
});
});
No.
Promises give you an object that you can pass around and call then on.
They do not turn asynchronous code into blocking code.
The second part of the code isn't inside the then callback so it runs while the asynchronous code (that will trigger the first promise to resolve) is running in the background.
That said, see the await keyword for asyntax that can give the illusion that a promise is blocking.
useEffect(() => {
const processPools = async () => {
for (let lpPool of mainnet.Exchanges.Pancakeswap.LpTokens) {
const [tokenZeroSymbol, tokenOneSymbol] = lpPool.name.replace(' LP', '').split('-');
const values = await fetchTokenPrice(tokenZeroSymbol.toLowerCase(), tokenOneSymbol.toLowerCase());
// Promise.resolve(prices).then((values) => {
const [priceTokenZero, priceTokenOne] = values;
filteredFarmPools.find((pool) => {
if (lpPool.name.replace(' LP', '') === pool.name) {
pool.priceTokenZero = values[0].usd;
pool.priceTokenOne = values[1].usd;
}
console.log('inside the fethcprice promise');
// });
});
}
}
processPools();
});
Original Array.map does not support async
Promise.resolve return immediately, no difference with Promise.then

(Refactor/Improve) Loop to make API calls and manupilate Array following the "no-loop-func"

Despite looking and following numerous answers here at stackoverflow,I have still failed to refactor this code to abide by the ESLint no-loop-func.
I keep getting the following warning, despite my efforts to refactor the code:
Compiled with warnings.
Function declared in a loop contains unsafe references to variable(s) 'lastResult', 'biologyBooks', 'page' no-loop-func
Here's the code:
import React from 'react';
import { apiFullCall } from '../../apiHelper';
const MyComponent = props => {
const [state, setState] = React.useState({ total: 0, biologyBooksByAuthor: [] });
let isLoaded = React.useRef(true);
const token = sessionStorage.getItem('token');
const authorID = sessionStorage.getItem('author_id');
const getBooks = async() => { // fetch items
let page = 1;
let scienceBooks, biologyBooks;
// create empty arrays to store book objects for each loop
let scienceBooks = biologyBooks = [];
// create a lastResult object to help check if there is a next page
let lastResult = { next: null };
do { // the looping - this is what I have failed to refactor
try {
await apiFullCall( // Make API calls over paginated records
'',
token,
'get',
`books/?author_id=1&page=${page}`
).then(res => {
if (res) {
const { status, body } = res;
if (status === 200 || status === 201) {
lastResult = body; // assign lastResult to pick "next"
body &&
body.results &&
body.results.map(eachBook => { // we map() over the returned "results" array
// the author with queried "author_id" writes science books;
// so we add each book (an object) into the science category
scienceBooks.push(eachBook);
// We then filter the author's biology books (from other science books)
biologyBooks = scienceBooks.filter(
({ is_biology }) =>
typeof(is_biology) === "boolean" && is_biology === true
);
return null;
}
);
// increment the page with 1 on each loop
page++;
}
}
}).catch(error => console.error('Error while fetching data:', error));
} catch (err) { console.error(`Oops, something went wrong ${err}`); }
// keep running until there's no next page
} while (lastResult.next !== null);
// update the state
setState(prevState => ({
...prevState, total: scienceBooks.length, biologyBooksByAuthor: biologyBooks,
}));
};
React.useEffect(() => { // fetch science books by author (logged in)
if (isLoaded && authorID) {
getBooks();
};
return function cleanup() {...}; // clean up API call, on unmount
}, [isLoaded, authorID]);
return (
// render the JSX code
);
}
Please note that I actually declared the said variables lastResult, biologyBooks and page outside the "do-while".
Any help or clues will be greatly appreciated.
The function the warning is referring to is the .then callback, if you're using async/await stick to it, try removing the .then part by assigning the result to a variable instead and remove the unnecessary .map, you can concatenate previous results with spread operator or .concat.
import React from 'react';
import { apiFullCall } from '../../apiHelper';
const MyComponent = props => {
const [state, setState] = React.useState({
total: 0,
scienceBooksByAuthor: [],
});
const isLoaded = React.useRef(true);
const token = sessionStorage.getItem('token');
const authorID = sessionStorage.getItem('author_id');
const getBooks = async () => {
// fetch items
let page = 1;
let scienceBooks = [];
// create a lastResult object to help check if there is a next page
let lastResult = { next: null };
do {
// the looping - this is what I have failed to refactor
try {
const res = await apiFullCall(
// Make API calls over paginated records
'',
token,
'get',
`books/?author_id=1&page=${page}`,
);
if (res) {
const { status, body } = res;
if (status === 200 || status === 201) {
lastResult = body; // assign lastResult to pick "next"
// concatenate new results
scienceBooks = [
...scienceBooks,
...((lastResult && lastResult.results) || []),
];
// increment the page with 1 on each loop
page += 1;
}
}
} catch (err) {
console.error(`Oops, something went wrong ${err}`);
}
// keep running until there's no next page
} while (lastResult.next !== null);
const biologyBooks = scienceBooks.filter(
({ is_biology }) =>
typeof is_biology === 'boolean' && is_biology === true,
);
// update the state
setState(prevState => ({
...prevState,
total: scienceBooks.length,
scienceBooksByAuthor: scienceBooks,
}));
};
React.useEffect(() => {
// fetch science books by author (logged in)
if (isLoaded && authorID) {
getBooks();
}
return function cleanup() {...}; // clean up API call, on unmount
}, [isLoaded, authorID]);
return (
// render the JSX code
);
};

React State is not updated with socket.io

When page loaded first time, I need to get all information, that is why I am calling a fetch request and set results into State [singleCall function doing that work]
Along with that, I am connecting websocket using socket.io and listening to two events (odds_insert_one_two, odds_update_one_two), When I got notify event, I have to
check with previous state and modify some content and update the state without calling again fetch request. But that previous state is still [] (Initial).
How to get that updated state?
Snipptes
const [leagues, setLeagues] = useState([]);
const singleCall = (page = 1, params=null) => {
let path = `${apiPath.getLeaguesMatches}`;
Helper.getData({path, page, params, session}).then(response => {
if(response) {
setLeagues(response.results);
} else {
toast("Something went wrong, please try again");
}
}).catch(err => {
console.log(err);
})
};
const updateData = (record) => {
for(const data of record) {
var {matchId, pivotType, rateOver, rateUnder, rateEqual} = data;
const old_leagues = [...leagues]; // [] becuase of initial state value, that is not updated
const obj_index = old_leagues.findIndex(x => x.match_id == matchId);
if(obj_index > -1) {
old_leagues[obj_index] = { ...old_leagues[obj_index], pivotType, rateOver: rateOver, rateUnder:rateUnder, rateEqual:rateEqual};
setLeagues(old_leagues);
}
}
}
useEffect(() => {
singleCall();
var socket = io.connect('http://localhost:3001', {transports: ['websocket']});
socket.on('connect', () => {
console.log('socket connected:', socket.connected);
});
socket.on('odds_insert_one_two', function (record) {
updateData(record);
});
socket.on('odds_update_one_two', function (record) {
updateData(record);
});
socket.emit('get_odds_one_two');
socket.on('disconnect', function () {
console.log('socket disconnected, reconnecting...');
socket.emit('get_odds_one_two');
});
return () => {
console.log('websocket unmounting!!!!!');
socket.off();
socket.disconnect();
};
}, []);
The useEffect hook is created with an empty dependency array so that it only gets called once, at the initialization stage. Therefore, if league state is updated, its value will never be visible in the updateData() func.
What you can do is assign the league value to a ref, and create a new hook, which will be updated each time.
const leaguesRef = React.useRef(leagues);
React.useEffect(() => {
leaguesRef.current = leagues;
});
Update leagues to leaguesRef.current instead.
const updateData = (record) => {
for(const data of record) {
var {matchId, pivotType, rateOver, rateUnder, rateEqual} = data;
const old_leagues = [...leaguesRef.current]; // [] becuase of initial state value, that is not updated
const obj_index = old_leagues.findIndex(x => x.match_id == matchId);
if(obj_index > -1) {
old_leagues[obj_index] = { ...old_leagues[obj_index], pivotType, rateOver:
rateOver, rateUnder:rateUnder, rateEqual:rateEqual};
setLeagues(old_leagues);
}
}
}

Returning the result from dispatch to a component throws "undefined" exception

I have the following method inside the data-operations tsx file:
export function EnrollStudent(student, courseId) {
return dispatch => {
dispatch(enrollUserBegin());
axios
.post("api/Course/EnrollStudent/" + courseId + "/" + student.id)
.then(response => {
if (response.data === 1) {
dispatch(enrollUserSuccess(student, courseId));
console.log("Student is enrolled.");
toast.success("Student is enrolled successfully.", {
position: toast.POSITION.TOP_CENTER
});
} else {
dispatch(enrollUserSuccess(null, 0));
console.log("Failed to enroll.");
toast.warn(
"Failed to enroll the student. Possible reason: Already enrolled.",
{
position: toast.POSITION.TOP_CENTER
}
);
}
})
.catch(error => {
dispatch(enrollUserFailure(error));
toast.warn("An error occurred. Please contact the Admin.", {
position: toast.POSITION.TOP_CENTER
});
});
};
}
This method is called from another component by a button click:
const enroll_onClick = e => {
e.preventDefault();
const currentUserId = authenticationService.currentUserValue["id"];
var student: any = { id: currentUserId };
props.enrollStudent(student, st_courseId);
};
const mapDispatchToProps = dispatch => {
return {
enrollStudent: (student, courseId) => dispatch(EnrollStudent(student, courseId)),
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(CourseEnrollmentWithCode);
This works fine and the database is updated properly. But, I want to get the result of the enrollStudentand perform an action (e.g., navigate to another page).
I tried this but I received props.enrollStudent() is undefined error.
props.enrollStudent(student, st_courseId)
.then(() => {
console.log("enrolld");
history.push('/courses');
});
Any suggestions?
By looking at the code, I'm guessing you are using the redux-thunk middleware.
In the EnrollStudent function, you have to add a return statement before axios (line 5) so that your thunk function (the function returned by EnrollStudent) returns a promise.
Take a look at the examples here: https://github.com/reduxjs/redux-thunk, especially the makeASandwichWithSecretSauce function.
You must not use promises in action creators ... you can only return an action object ... https://redux.js.org/api/store/
In this case (side effects) you have to use a middleware (redux-thunk, redux-promise, redux-saga) ... https://redux.js.org/api/applymiddleware/
If middleware not used ... current behaviour (looking as partially working) is at least missleading.

React Concurrent Modify Same State

I get errors while modifying a state concurrently.
I have a function called getAvailableSpace(i, newValue) which fetch data from the back end and set to the state. This function can be called concurrently. The errors happen when it is called before the last call finished.
getAvailableSpace(i, newValue) {
this.setState(() => {
let schema_filters = _.cloneDeep(this.state.schema_filters);
schema_filters[i].loadingSpaces = true;
return {schema_filters: schema_filters}
}, () => {
this.invokeAPI("sitecomparsion", "getallspaces" + this.props.router.location.search + "&selectedTdid=" + newValue.value, requestHeaders.getHeader, (data) => {
let schema_filters = _.cloneDeep(this.state.schema_filters);
if(data) {
//schema_filters[i].availableSpaces = {...data.spaces, ...data.areas}
let spaces= (Object.keys(data.spaces).map((key) =>{
return {label: data.spaces[key], value: ('space_' + Number(key))}
}))
schema_filters[i].availableSpaces = [];
schema_filters[i].availableSpaces.push({label: 'Spaces:', type: 'optgroup'})
schema_filters[i].availableSpaces = schema_filters[i].availableSpaces.concat(areas)
}
this.setState((prevState) => {
prevState.schema_filters = schema_filters;
return prevState;
});
});
});
}
The problem I believe is caused by the race condition since two calls are trying to modify the same state.
Also, I think if the _.cloneDeep(this.state.schema_filters) happens before the last call is finished, the update from the last call is lost.
Can I get some help?
If you have a lot of status to update at once, group them all in the same set. State:
this.setState(() => {
this.setState(() => {
let schema_filters = _.cloneDeep(this.state.schema_filters);
schema_filters[i].loadingSpaces = true;
return {schema_filters: schema_filters}
this.setState({schema_filters: schema_filters }, () => {
// Do something here.
});
});

Resources