I'm new to react native and currently trying to make an app with firebase v9 for practice. I'm kinda stuck with the hook issue, as it says:
[Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.]
This is the Login Component:
const LoginScreen = ( {navigation}) => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [storeKey, setStoreKey] = useState([]);
const [userName, setName] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [image, setImage] = useState(null);
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (authUser) => {
if(authUser){
AfterLogin();
}
});
return unsubscribe;
}, []);
const signInUser = () => {
signInWithEmailAndPassword(auth, email, password)
.then(async (re) => {
//setIsSignedIn(true);
AfterLogin();
})
.catch((error) => {
console.error(error);
})
}
const AfterLogin = () => {
if(!storeKey || !userName || !image){
getData(setStoreKey, setName, setIsLoading, setImage);
}
navigation.replace('Drawer', {
storeKey: storeKey,
userName: userName,
image: image,
});
}
And this is the getData Function:
const getData = async(setStoreKey, setName, setIsLoading, setImage) => {
const auth = getAuth();
const user = auth.currentUser;
if(user !== null){
const email = user.email;
const UserInfo = await getDoc(doc(db, 'users', email));
if(UserInfo.exists()){
setStoreKey(UserInfo.data().storeKey)
setName(UserInfo.data().name);
setIsLoading(false)
setImage(UserInfo.data().postImage)
}
else{
console.log('None')
}
return
}
}
I guess the problem is happening in the useEffect in Login, but I don't know how to solve this :S
EDIT: I think the problem is from getData function. When I comment out getData function in AfterLogin, it works fine without error :S
but I don't know how it causes error.
Is it because it's async function?
Try this
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (authUser) => {
if(authUser){
AfterLogin();
}
});
return () => {
unsubscribe();
}
}, []);
Related
Suppose I have a component that loads its content when an asynchronous call returns succesfuly:
const MyScreen = () => {
let userData: userDataResponse;
const [email, setEmail] = useState("");
const [firstTime, setFirstTime] = useState(true);
async function localGetUserData() {
userData = await getUserData();
setEmail(userData.email);
setFirstTime(false);
}
useEffect(() => {
localGetUserData();
}, []);
if (firstTime) {
return <Text>Cargando...</Text>;
}
return (
<SafeAreaView style={styles.formStyling}>
When the data is available, it sets a state variable so the real content renders
If I want to test it, I think I should mock the getUserData so the mocked function returns a mocked email, say {email: a#b.c}
What would be a good approach to achieve this?
Assuming following component setup (as I cannot see whole component):
myScreenUtils.js
export const getUserData = async () => {
return Promise.resolve('original implementation')
}
MyScreen.jsx
import { useState, useEffect } from "react";
import { getUserData } from './myScreenUtils.js'
const MyScreen = () => {
let userData;
const [email, setEmail] = useState("");
const [firstTime, setFirstTime] = useState(true);
async function localGetUserData() {
userData = await getUserData();
setEmail(userData.email);
setFirstTime(false);
}
useEffect(() => {
localGetUserData();
}, []);
if (firstTime) {
return <div>Cargando...</div>;
}
return (
<div>{email}</div>
)
};
export default MyScreen;
You can write following tests:
import { screen, render, waitFor, waitForElementToBeRemoved } from '#testing-library/react';
import MyScreen from "../MyScreen";
import * as utils from '../myScreenUtils';
describe('MyScreen', () => {
it('the text is displayed and then removed', async () => {
jest.spyOn(utils, 'getUserData').mockResolvedValue({ email: 'mocked value' });
render(<MyScreen />);
expect(screen.getByText('Cargando...')).toBeInTheDocument();
await waitForElementToBeRemoved(() => screen.queryByText('Cargando...'))
})
it('the text email is fetched and displayed', async () => {
jest.spyOn(utils, 'getUserData').mockResolvedValue({ email: 'mocked value' });
render(<MyScreen />);
await waitFor(() => {
expect(screen.getByText('mocked value')).toBeInTheDocument()
})
})
})
I'm doing the verification of the phone number, and I have to pass the phone number to the other checkCode.js component.
I have seen examples that pass it navigate() as a pramas, but how can I receive it in another component.
register.js
const SignUp = ({ navigation }) => {
const [phoneNumber, setPhoneNumber] = useState('');
let register = "https://app.herokuapp.com/api/v1/auth/register"
let sendVerification = "https://app.herokuapp.com/api/v1/auth/sendVerification-otp"
const signUp = () => {
const userParams = {
phone: phoneNumber,
};
const requestOne = axios.post(register, userParams)
const requestTwo = axios.post(sendVerification, userParams)
axios
.all([requestOne, requestTwo], userParams)
.then(axios.spread((...responses) => {
navigation.navigate('CodeVerification')
}))
.catch((err) => {
console.log('the error:', err.message);
})
}
checkCode.js
export default function CodeVerification({navigation}) {
//need phoneNumber param in this component
const [code, setCode] = useState('');
const confirm = () =>{
const userParams = {
phone: "+11111111",
code:code,
};
axios
.post('https://app.herokuapp.com/api/v1/auth/sendVerification-otp', userParams)
.then((response) =>{
console.log('response', response.data);
navigation.navigate('Welcome')
})
.catch((error) => {
console.log('the error:', error.message);
});
};
How can I pass it?
This might help
register.js
const SignUp = ({ navigation }) => {
// existing code remains the same
const signUp = () => {
....
axios
.all([requestOne, requestTwo], userParams)
.then(
axios.spread((...responses) => {
// send params like this
navigation.navigate("CodeVerification", {phone: phoneNumber});
})
)
.catch((err) => {
console.log("the error:", err.message);
});
};
};
checkCode.js
export default function CodeVerification({ route, navigation }) {
// get phoneNumber from props
const {phone} = route.params; // UPDATED this line
const [code, setCode] = useState("");
....
}
You can use Context Api
Context api is commonly used for transferring data to another component.
This is my code:
const [tourists, setTourists] = useState(null)
const [saved,setsave]=useState('');
const {user, logout} = useContext(AuthContext);
const [userData, setUserData] = useState(null);
const [loading, setLoading] = useState(true);
const getUser = async() => {
await firestore()
.collection('users')
.doc( user.uid)
.get()
.then((documentSnapshot) => {
if( documentSnapshot.exists ) {
console.log('User Data in BookMark', documentSnapshot.data());
const list=[];
setUserData(documentSnapshot.data());
console.log('savedPosts: ',documentSnapshot.data().savedPosts);
documentSnapshot.data().savedPosts.map((object, index) => (
firestore().collection('posts').doc(object).get().then((querySnapshot) => {list.push(querySnapshot.data())})
))
setTourists(list);
if (loading) {
setLoading(false);
}
}
})
}
useEffect(() => {
getUser();
}, []);
return (
<View style={{flex: 1, marginTop: Constants.statusBarHeight}}>
{!loading ? ((tourists ||[]).map((object, index) => (...
I have checked through the console and see that firestore worked correctly, I got the data, tourists is not null, but the screen still shows nothing. Can anybody hekp me plz !!!
The issue is in this part:
const list=[];
setUserData(documentSnapshot.data());
console.log('savedPosts: ',documentSnapshot.data().savedPosts);
documentSnapshot.data().savedPosts.map((object, index) => (
firestore().collection('posts').doc(object).get().then((querySnapshot) => {list.push(querySnapshot.data())})
))
setTourists(list);
Becase you use then the setTourists(list) will always save an empty array because then finished after you already set the value. Also a map doesn't support async calls. We need to use a for loop for this.
Change your code to something like this:
const getUser = async () => {
await firestore()
.collection("users")
.doc(user.uid)
.get()
.then(async (documentSnapshot) => {
if (documentSnapshot.exists) {
console.log("User Data in BookMark", documentSnapshot.data());
const list = [];
const posts = [];
setUserData(documentSnapshot.data());
documentSnapshot.data().savedPosts.map((object, index) => {
posts.push(object);
});
for (let i = 0; i < posts.length; i++) {
const post = posts[i];
const docSnapshot = await firestore()
.collection("posts")
.doc(post)
.get();
list.push(docSnapshot.data());
}
setTourists(list);
if (loading) {
setLoading(false);
}
}
});
};
When i try to set state from API, i have response.data but setState dont work.
const [items, setItems] = useState([]);
useEffect(() => {
cargarItems();
}, [])
const cargarItems = async () => {
const res = await fetch('http://localhost:4000/api/items');
const data= await res.json();
setItems(data);
console.log(data);
console.log(items); }
Also try:
await axios
.get('http://localhost:4000/api/items')
.then((res) => {
console.log(typeof res.data);
setItems(res.data);
})
.catch((err) => {
console.log(err);
});
console.log(items);
And allways i get:
I have the response but the state is not updated.
Thanks in advance
I'm taking the images I uploaded to cloud storage, but the problem is the variable is not an array, so it is only storing just one url. How do I make variables with state array?
My code:
const reference = storage().ref('images');
const [imageUrl, setImageUrl] = useState();
const refer = storage().ref('images');
useEffect(() => {
try {
listFilesAndDirectories(reference).then(() => {
console.log('Finished listing');
});
refer.list().then(result => {
result.items.forEach(element => {
element.getDownloadURL().then(downloadUrl => {
setImageUrl(downloadUrl)
console.log(imageUrl)
console.log("=================")
}).catch(error =>{
alert(error)
})
})
})
} catch (error) {
alert(error);
}
}, []);
Is that what you are looking for?
const [items, setItems] = useState([]);
const handleStateChange = () => {
setItems(state => [...state, 'someNewItem']);
}
With useCallback
const handleStateChange = useCallback(function () {
setItems(state => [...state, 'someNewItem']);
}, [])