I am developing a video chat app using react-native-voximplant, everything works, but when the app loads first time, the login functionality is happening inside the useEffect. So, the problem is, when my HomeScreen mounts, the useEffects' should fire and I should get logged in voximplant sdk and now if I tap on call icon, the following is shown in the console:
When HomeScreen mounts:
LOG Client: emit: no handlers for event: AuthResult
LOG Error while trying to login to voximplant: {"code": 491, "name": "AuthResult", "result": false}
LOG Client: emit: no handlers for event: ConnectionEstablished
Now when I tap on call to make a call:
// after a second...
WARN Possible Unhandled Promise Rejection (id: 0):
"NOT_LOGGED_IN"
Now when I hit save (ctrl+s), I get logged in and now if I make a call, it works! I don't know what I am doing wrong here. Is something wrong with useEffect or I'm doing something wrong?
The HomeScreen code:
import {Voximplant} from 'react-native-voximplant';
const HomeScreen = () => {
const voximplant = Voximplant.getInstance();
useEffect(() => {
const signInToVoximplant = async () => {
try {
const fqUsername = '...'; // credentials are correct, don't worry about them.
const password = '...';
await voximplant.login(fqUsername, password);
} catch (error) {
console.log('Error while trying to login to voximplant: ', error);
}
};
signInToVoximplant();
}, []);
useEffect(() => {
const connect = async () => {
const status = await voximplant.getClientState();
if (status === Voximplant.ClientState.DISCONNECTED) {
await voximplant.connect();
} else if (status === Voximplant.ClientState.LOGGED_IN) {
console.log('[INFO] VOXIMPLANT CONNECTED');
return;
}
};
connect();
}, []);
}
return ...
The call will only work if I hit ctrl+s i.e. save the file, otherwise it will throw Unhandled Promise rejection... because it is not logged in, however I have written/provided the login inside the useEffect.
According to your logs, when the application starts, login is failed with 491 error that means that the client is in invalid state. It happens because you invoke login API before the connection the Voximplant Cloud is established (connect API).
When the app is reloaded, native code is not reinitialised (but js does) and actually the client is still connected to the Voximplant Cloud, that's why login is successful the second time.
To fix the issue, you need to change the order of Voximplant SDK API invocation: first call Client.connect API and then Client.login.
It is also better to move connect/login to the a single useEffect.
Both useEffect is run on mounted, but ¿whichone first?.
Try put both functions in the same useEffect, and call them in order:
useEffect(() => {
const signInToVoximplant = async () => {
...
};
const connect = async () => {
...
};
signInToVoximplant();
connect();
}, []);
Related
Is there a way during webdriverio runtime to simulate an actioncable receive?
I am using a fork of the package action-cable-react called actioncable-js-jwt for Rails actioncable js connections. Both of these packages are no longer maintained, but actioncable-js-jwt was the only actioncable for react package I could find that supported jwt authentication. I am building an app in my company's platform and jwt authentication is required.
The problem I am running into is that I have a react component which dispatches a redux action to call an api. The api returns a 204, and the resulting data is broadcasted out from Rails to be received by the actioncable connection. This triggers a dispatch to update the redux store with new data. The component does actions based on new data compared to the initial value on component load, so I cannot simply just set initial redux state for wdio - I need to mock the actioncable receive happening.
The way the actioncable subscription is created is:
export const createChannelSubscription = (cable, receivedCallback, dispatch, channelName) => {
let subscription;
try {
subscription = cable.subscriptions.create(
{ channel: channelName },
{
connected() {},
disconnected(res) { disconnectedFromWebsocket(res, dispatch); },
received(data) { receivedCallback(data, dispatch); },
},
);
} catch (e) {
throw new Error(e);
}
return subscription;
};
The receivedCallback function is different for each channel, but for example the function might look like:
export const handleUpdateRoundLeaderWebsocket = (data, dispatch) => {
dispatch({ type: UPDATE_ROUNDING_LEADER, round: data });
};
And the redux state is used here (code snippets):
const [currentLeader, setCurrentLeader] = useState(null);
const userId = useSelector((state) => state.userId);
const reduxStateField = useSelector((state) => state.field);
const onChange = useCallback((id) => {
if (id !== currentLeader) {
if (id !== userId && userId === currentLeader) {
setShow(true);
} else {
setCurrentLeader(leaderId);
}
}
}, [currentLeader, userId]);
useEffect(() => {
onChange(id);
}, [reduxStateField.id, onChange]);
Finally, my wdio test currently looks like:
it('has info dialog', () => {
browser.url('<base-url>-rounding-list-view');
$('#my-button').click();
$('div=Continue').click();
// need new state after the continue click
// based on new state, might show an info dialog
});
Alternatively, I could look into manually updating redux state during wdio execution - but I don't know how to do that and can't find anything on google except on how to provide initial redux state.
I want to connect a Solana wallet (phantom or any other) to a web application through the web3js library. I've read docs for most wallets and it seems like it's just as simple as await window.solana.request({ method: "connect" }); but window.solana is undefined in my case.
When I do console.log(window) I can see the Solana value with all its corresponding keys and values.
How can I do this?
I've found a working code that solved my issue. I am not sure what was the issue as I'm not very experienced with js, but the following code lets me connect to phantom.
I found this on StackOverflow on a similar thread, although I belive the original answer is missing some brackets.
Solana : Adding Sollet / Phantom Wallet Connect to my website - Steps?
const getProvider = async () => {
if ("solana" in window) {
await window.solana.connect(); // opens wallet to connect to
const provider = window.solana;
if (provider.isPhantom) {
console.log("Is Phantom installed? ", provider.isPhantom);
return provider;
}
} else {
document.write('Install https://www.phantom.app/');
}
};
window.onload = () => {
getProvider().then(provider => {
console.log('key', provider.publicKey.toString())
})
.catch(function(error){
console.log(error)
});
}
With your current implementation, everytime you refresh the app, you will get pop up to connect to the wallet. Instead you add {onlyIfTrusted:true} option to connect.
const getProvider = async () => {
if ("solana" in window) {
await window.solana.connect({onlyIfTrusted:true}); // opens wallet to connect to
const provider = window.solana;
if (provider.isPhantom) {
console.log("Is Phantom installed? ", provider.isPhantom);
return provider;
}
} else {
document.write('Install https://www.phantom.app/');
}
};
then instead of getting pop up when you reload the app, write a connection function to handle the connection when a user clicks on the button
const connectToWallet=async ()=>{
const {solana}=window
if(solana){
const response=await solana.connect()
console.log('address',response.publicKey.toString())
}
}
<button onClick={connectToWallet} >
Connect to Wallet
</button>
Now once user is connected, when you reload the app, it you wont get pop up to connect to the wallet
Is your website https enabled? If not then it won't work
I am working on a React.js app using Axios for HTTP processing and MobX for state management. I am getting this error, where I sent a test invalid HTTP response that should result in an 404 error. But what I got was a loop of 404 errors keep repeating itself, here's how the console looks:
Here is the method that produces this error. I created a "debugging mode" version:
// for testing
#action loadActivityDos = async (id: string) => {
let activity = this.getActivity(id);
console.log("Step 1: get activity from local activityRegistry");
if (activity) {
this.activity = activity;
console.log("Step 2-1: set local activity to scoped activity");
} else {
this.loadingInitial = true;
try {
activity = await agent.Activities.details(id);
runInAction('getting activity', () => {
this.activity = activity;
this.loadingInitial = false;
})
console.log("Step 2-2: get activity from API in the try block");
} catch(error) {
runInAction('get activity error', () => {
this.loadingInitial = false;
})
console.log("Errored -> error block");
}
}
First, the method hits the try block after failing to get the 'activity' from local array. The first line which awaits for the 'activity' from API throws an Http 404 error, sending to the catch block where the "Errored -> error block" logs in console.
It should end there, but after the "Errored -> error block" log, for an unknown reason it prints the "Step 1: get activity from local activityRegistry", meaning the whole method is ran from beginning again. Then it continues to repeat itself.
Here is the Axios code that communicates with the API, but I couldn't find anything that would cause this behavior:
axios.defaults.baseURL = 'https://localhost:5001/api';
const responseBody = (response: AxiosResponse) => response.data;
const sleep = (ms: number) => (response: AxiosResponse) =>
new Promise<AxiosResponse>(resolve => setTimeout(() => resolve(response), ms));
const requests = {
get: (url: string) => axios.get(url).then(responseBody),
}
const Activities = {
list: ():Promise<IActivity[]> => requests.get('/activities'),
details: (id: string) => requests.get(`/activities/${id}`),
}
I am oblivious why this loop is happening, if anyone could shed some light, I would be very grateful. I'll be happy to provide any other detail.
From where loadActivityDos is called? Do you have a reaction to call it or maybe you use useEffect?
Maybe you have endless reaction loop which reacts to some flag you change
Or maybe useEffect does not have deps array in the end and is called every render.
The Context:
I want to know how to get rid of this error:
Warning: Can't perform a React state update on an unmounted component.
I think I know exactly why this error shows up:
I have a Signin Route. I have a litte piece of code in the useEffect that does this:
if (!auth.isEmpty && auth.isLoaded) {
history.push("/");
}
So when someone goes to mypage/signin and is already signed in, he gets redirected to Homepage. This works fine BUT:
The Problem:
When he is not signed in I have a little Signin Function:
const signin = async (e: React.MouseEvent) => {
setIsLoading(true);
e.preventDefault();
try {
const user = await firebase.login({ email, password });
setIsLoading(false);
if (user) {
history.push("/");
}
} catch (error) {
setIsLoading(false);
setError(error.message);
}
};
So when the users hits enter, he gets redirected to home when there is no error. It works fine but I get this error in the console, because I set the state and the the snippet in useEffect routes me to /Home, but the promise is not yet completed from firebase. And when it's finished it tries to set state, but component already unmounted.
What have I tried
I added a isMounted hook and changed my signin function to look like this:
const signin = async (e: React.MouseEvent) => {
e.preventDefault();
if (isMounted) {
setIsLoading(true);
try {
const user = await firebase.login({ email, password });
setIsLoading(false);
if (user) {
history.push("/");
}
} catch (error) {
setIsLoading(false);
setError(error.message);
}
}
};
But still the same error on route change.
Additional Info
Don't get confused about these 2 loading states auth.isLoaded (from react-redux-firebase) and isLoading (my own state). Basically why I did it this way is, because when someone is already logged in and then goes to /signin he sees the login form for a tiny moment, because firebase doesn't know yet if user is authenticated, so I handled it like this, so the user definetily sees a spinner and then gets redirected if already logged in.
How to solve this little problem?
You can use React hooks for this. The useEffect return method is called when compoment is unmonuted from screen. This is like compomentdidunmount in class based react.
declare global variable _isMounted to false. When useEffect is called, it changes to true and components are on screen.
If component are unmounted, then return method from useEffect is called and _isMounted is set to false;
while updating the state, you can check using _isMounted variable that is component is mounted or not.
var _isMounted = false;
const fetchuser = () => {
if(_isMounted)
{
// code
}
}
useEffect(() => {
_isMounted = true;
// your code;
return()
{
_isMounted = false;
console.log("Component Unmounted");
}
},[])
if you redirected after login, you dont have to change loading state. Just remove setIsLoading(false)
const user = await firebase.login({ email, password });
if (user) {
history.push("/");
}
High-level description
I have a React/redux/electron app that uses Google Oauth. I want to be able to refresh the access token automatically when it expires. I've researched this and solved it semi-successfully using middleware, but my solution is erroring in certain situations.
I've implemented a refresh middleware that runs on every API action. It checks whether the access token is expired or about to expire. If so, instead of dispatching the action it received, it dispatches a token refresh action and queues up any other actions until a new access token is received. After that, it dispatches all actions in its queue.
However, one of my action creators looks something like this:
function queryThreads(params) {
return async (dispatch) => {
const threads = await dispatch(fetchThreads(params))
const newPageToken = threads.payload.nextPageToken
}
}
When the refresh middleware doesn't run because the token isn't expiring, threads.payload will be defined here and everything will work as intended.
However, when the refresh middleware does run, threads.payload will be undefined because the dispatch seems to resolve with the value of the token refresh action, rather than the fetchThreads action.
How do I ensure that the token gets refreshed (and updated in state/localStorage), fetchThreads gets dispatched with the updated token, and the threads variable gets assigned to the resolved value of the correct Promise?
Links to Project Code
This is my refresh middleware. It was inspired by this article by kmmbvnr.
This is the token refresh action creator.
This is the line in my queryThreads action creator that throws when the token has to refresh (threads.payload is undefined).
This is the reducer where I update state in response to a token refresh.
This is the middleware where I update localStorage in response to a token refresh.
It looks like I've solved the issue by rewriting the refresh middleware like this:
function createRefreshMiddleware() {
const postponedRSAAs = [];
return ({ dispatch, getState }) => {
const rsaaMiddleware = apiMiddleware({ dispatch, getState });
return next => action => {
if (isRSAA(action)) {
try {
const auth = JSON.parse(localStorage.getItem('auth'));
const { refresh_token: refreshToken } = auth;
const expirationTime = jwtDecode(auth.id_token).exp * 1000;
const isAccessTokenExpiring =
moment(expirationTime) - moment() < 300000;
if (refreshToken && isAccessTokenExpiring) {
postponedRSAAs.push(action);
if (postponedRSAAs.length === 1) {
return rsaaMiddleware(next)(
dispatch(() => attemptTokenRefresh(refreshToken))
).then(() => {
const postponedRSAA = postponedRSAAs.pop();
return dispatch(postponedRSAA);
});
}
return rsaaMiddleware(next)(action);
}
return rsaaMiddleware(next)(action);
} catch (e) {
console.log(e);
return next(action);
}
}
return next(action);
};
};
}
export default createRefreshMiddleware();
Now the postponed action will always be chained off of the token refresh action, so we don't have the problem of the original promise resolving with the wrong value; plus it's more concise.