I'm retrieving data from an API, right now there are only two endpoints, one for adding users and other one for get the users added. The users are added using a button and its textbox.
let [showAlert, setAlertState] = useState(false);
let [twitterAccount, setTwitterAcount] = useState("Twitter Account here");
let [dataInfo, setDataInfo] = useState([]);
const getCurrentSpiedUsers = async () => {
fetch("https://localhost:7021/GetCurrentSpiedUsers")
.then((res) => res.json())
.then((res) => {setDataInfo(res)});
}
useEffect(function () {
getCurrentSpiedUsers();
}, []);
This part works as expected, the first time I enter into the website it loads data from the API.
This one is the other method for adding users.
const addTwitterAccount = (account) => {
fetch(`https://localhost:7021/AddTwitterAccount?account=${account}`, {
method:'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
}
)
.then((res) => res.json())
.then((res) => { });
}
and this is the function inside of the onClick event button.
const activeAlert = () => {
addTwitterAccount(twitterAccount);
getCurrentSpiedUsers();
setAlertState(true);
setTimeout(() => {
setAlertState(false);
}, 5000);
};
but this is not working as expected, once I click the button I add the account correctly, but can't reload the component using the getCurrentSpiedUsers();
function. I noticed that when I click twice I can get the last one but not the actual one, so I assume it's because the code it's executed faster than my function is retrieving the data from the API.
I tried using async/await for both methods but the result is always the same. What can I do?
updated with the server-side:
AddTwitterAcccount endpoint:
[ApiController]
[Route("[controller]")]
public class AddTwitterAccount : ControllerBase
{
private readonly ISpiedAccounts _spiedAccounts;
public AddTwitterAccount(ISpiedAccounts spiedAccounts)
{
_spiedAccounts = spiedAccounts;
}
[HttpPost(Name = "AddTwitterAccount")]
public void AddAccount([FromQuery] string account)
{
_spiedAccounts.AddTwitterAccount(account);
}
}
SpiedAccounts class
public interface ISpiedAccounts
{
public void AddTwitterAccount(string accountName);
public List<TwitterAccount> GetTwitterAccounts();
}
public class SpiedAccounts : ISpiedAccounts
{
private List<TwitterAccount> accounts = new();
private ResponseMessage _responseMessage;
public void AddTwitterAccount(string accountName)
{
if (accounts.Any(account => account.ScreenName == accountName))
{
_responseMessage = ResponseMessage.UserExists;
return;
}
int maxUsersSpied = 5;
if (accounts.Count <= maxUsersSpied)
{
accounts.Add(new TwitterAccount
{
ScreenName = accountName
});
_responseMessage = ResponseMessage.Added;
return;
}
_responseMessage = ResponseMessage.LimitUsersExceeded;
}
public string GetResponseMessage()
{
return _responseMessage switch
{
ResponseMessage.Added
=> "The account was added correctly.",
ResponseMessage.LimitUsersExceeded
=> "Only can be spied 6 accounts at the time. Please, wait for one of them to be free.",
ResponseMessage.UserExists
=> "This account is currently being spied.",
_
=> string.Empty
};
}
public List<TwitterAccount> GetTwitterAccounts()
=> accounts;
}
public enum ResponseMessage
{
Added,
LimitUsersExceeded,
UserExists
}
CodePudding user response:
There's a race condition between the WRITE (addTwitterAccount
) and the READ (getCurrentSpiedUsers
).
In other words, when you call addTwitterAccount()
it will request the addition of a new twitter account, but nothing guarantees such operation has been completed (aka the account has been added) before getCurrentSpiedUsers()
reads info from the datasource.
In fact, given you don't await for addTwitterAccount()
, getCurrentSpiedUsers()
will likely never bring the new account in its results.
What to do?
First, await for the result of the POST
(addTwitterAccount()
) before proceeding to the GET
(addTwitterAccount()
):
const activeAlert = async () => { // added async here
await addTwitterAccount(twitterAccount); // added await here
getCurrentSpiedUsers();
...
For that to work, make sure the addTwitterAccount()
returns the Promise
:
const addTwitterAccount = (account) => {
return fetch(`https://loca......ount?account=${account}`, { // added return
Second, edit the (server-side) endpoint at https://localhost:7021/AddTwitterAccount
to guarantee** it only returns after the account has been completely added.
** There are other alternatives to this. You can make this endpoint be async, but you would need to use other tactics to know the user has been added. But all these other tactics would involve way more work, and you likely don't need the scalability they would provide you at the moment. Example tactics: WebSockets, SSE, long-polling. Making the endpoint block until the account is created, as I suggested above, is likely the more cost-effective solution at this point.
CodePudding user response:
you can't use multiple setState in one event it will do only the first one, my suggestion is to use only one state with an object that contains your data