I have recently refactored my app from using Class components to Functional components and having issues with a few last things.
My Home.js looks like the following (simplified):
// imports....
import { StartStopButtons } from "../components/Button";
export default ({ navigation }) => {
const [scrollEnabled, setScrollEnabled] = useState(false);
const [elapsedMilliseconds, setElapsedMilliseconds] = useState(0);
const [isRunning, setIsRunning] = useState(false);
const [startTime, setStartTime] = useState(false);
const [stopTime, setStopTime] = useState(false);
const [isReset, setIsReset] = useState(true);
start = () => {
console.log("START");
// stuff
};
reset = () => {
console.log("RESET");
// stuff
};
stop = () => {
console.log("STOP");
// stuff
};
return (
<View style={styles.container}>
<StartStopButtons
isRunning={isRunning}
isReset={isReset}
elapsedMilliseconds={elapsedMilliseconds}
/>
</View>
);
};
My StartStopButtons has a different look, depending of the current state, it will either display Start, Stop or Reset and call the corresponding function. I am currently putting this intelligence in another file, my Button.js file.
Button.js :
//imports....
export const StartStopButtons = ({
isRunning,
isReset,
elapsedMilliseconds,
}) => {
if (isRunning && isReset === false) {
return (
<View>
<TouchableOpacity onPress={stop}>
<Text>Stop</Text>
</TouchableOpacity>
<TouchableOpacity onPress={pause}>
<Text>Pause</Text>
</TouchableOpacity>
</View>
);
} else {
if (elapsedMilliseconds === 0) {
return (
<TouchableOpacity onPress={start}>
<Text>Start</Text>
</TouchableOpacity>
);
} else {
return (
<TouchableOpacity onPress={reset}>
<Text>Reset</Text>
</TouchableOpacity>
);
}
}
};
Before the refactoring, I was using this.state.start, this.state.stop to call my start and stop functions, located in Home.js.
How can I achieve that now? Is there a better approach?
You can pass the functions as props exactly like how you pass isRunning, isReset, and elapsedMilliseconds.
But please add const before function names as well.
// imports....
import { StartStopButtons } from "../components/Button";
export default ({ navigation }) => {
const [scrollEnabled, setScrollEnabled] = useState(false);
const [elapsedMilliseconds, setElapsedMilliseconds] = useState(0);
const [isRunning, setIsRunning] = useState(false);
const [startTime, setStartTime] = useState(false);
const [stopTime, setStopTime] = useState(false);
const [isReset, setIsReset] = useState(true);
const start = () => {
console.log("START");
// stuff
};
const reset = () => {
console.log("RESET");
// stuff
};
const stop = () => {
console.log("STOP");
// stuff
};
const pause = () => {};
return (
<View style={styles.container}>
<StartStopButtons
start={start}
stop={stop}
reset={reset}
pause={pause}
isRunning={isRunning}
isReset={isReset}
elapsedMilliseconds={elapsedMilliseconds}
/>
</View>
);
};
and use them like
//imports....
export const StartStopButtons = ({
start,
stop,
reset,
pause,
isRunning,
isReset,
elapsedMilliseconds,
}) => {
if (isRunning && isReset === false) {
return (
<View>
<TouchableOpacity onPress={stop}>
<Text>Stop</Text>
</TouchableOpacity>
<TouchableOpacity onPress={pause}>
<Text>Pause</Text>
</TouchableOpacity>
</View>
);
} else {
if (elapsedMilliseconds === 0) {
return (
<TouchableOpacity onPress={start}>
<Text>Start</Text>
</TouchableOpacity>
);
} else {
return (
<TouchableOpacity onPress={reset}>
<Text>Reset</Text>
</TouchableOpacity>
);
}
}
};
Related
im trying to get repos in order of stars from the API, but as i scroll, i keep getting random duplicates :
export default function App() {
const [repos, setRepos] = useState([]);
const [page, setPage] = useState(1);
const [loading, setLoading] = useState(true);
useEffect(() => {
const getUsers = async () => {
const res = await axios.get(
`https://api.github.com/search/repositories?q=created:>2020-01-01&sort=stars&order=desc&page=${page}`
);
const data = await res.data;
// setRepos([...repos, ...data.items]);
setRepos([...repos, ...data.items]);
setLoading(false);
};
getUsers();
}, [page]);
const scrollToEnd = () => {
if (loading === false) {
setPage(page + 1);
setLoading(true);
}
console.log("page", page);
};
const renderItem = ({ item }) => (
<Text className="boxed">
Stars: {item.stargazers_count} Id: {item.id}
</Text>
);
return (
<SafeAreaView style={styles.screen}>
<View style={styles.container}>
<FlatList
data={repos}
renderItem={renderItem}
keyExtractor={(item) => item.id}
onEndReached={scrollToEnd}
showsVerticalScrollIndicator={false}
/>
{loading && <Text className="loading">...loading</Text>}
</View>
<StatusBar style="auto" />
</SafeAreaView>
);
}
You should avoid duplicate data before set it to state, update your getUsers function with this,
useEffect(() => {
const getUsers = async () => {
const res = await Axios.get(
`https://api.github.com/search/repositories?q=created:>2020-01-01&sort=stars&order=desc&page=${page}`,
);
const data = await res.data;
let arrRepos = [...repos, ...data.items];
let uniq = new Set(arrRepos.map((objRepo) => JSON.stringify(objRepo)));
arrRepos = Array.from(uniq).map((objRepo) => JSON.parse(objRepo));
setRepos(arrRepos);
setLoading(false);
};
getUsers();
}, [page]);
I think you got too much going on in your useEffect function, maybe missing a closing }. Try this:
function App() {
const [repos, setRepos] = useState([]);
const [page, setPage] = useState(1);
const [loading, setLoading] = useState(true);
// This will trigger when page changes:
useEffect(() => {
getUsers();
}, [page]);
const getUsers = async () => {
const res = await axios.get(
`https://api.github.com/search/repositories?q=created:>2020-01-01&sort=stars&order=desc&page=${page}`
);
const data = await res.data;
// setRepos([...repos, ...data.items]);
setRepos([...repos, ...data.items]);
setLoading(false);
};
const scrollToEnd = () => {
if (loading === false) {
setPage(page + 1);
setLoading(true);
}
console.log("page", page);
};
const renderItem = ({ item }) => (
<Text className="boxed">
Stars: {item.stargazers_count} Id: {item.id}
</Text>
);
return (
<SafeAreaView style={styles.screen}>
<View style={styles.container}>
<FlatList
data={repos}
renderItem={renderItem}
keyExtractor={(item) => item.id}
onEndReached={scrollToEnd}
showsVerticalScrollIndicator={false}
/>
{loading && <Text className="loading">...loading</Text>}
</View>
<StatusBar style="auto" />
</SafeAreaView>
);
}
export default App;
Home Component :
export const Home: React.FC<Props> = (): any => {
const [recipesList, setRecipesList] = useState([] as Array<any>);
const { loading, error, data } = useQuery(GET_RECIPES);
useEffect(() => {
const getRecipes = () => {
if (error) {
return console.log(error);
}
if (loading) {
return console.log("LOADING =>", loading)
}
setRecipesList(data);
};
getRecipes();
}, [data]);
return (
<View style={styles.container}>
<Recipe recipesList={recipesList} />
</View>
);
};
Recipe Component :
export const Recipe: React.FC<Props> = (props: Props): any => {
const { recipesList } = props;
const displayRecipe = ({ item }: any) => {
console.log("RENDER ITEM")
return null;
};
return (
<View style={styles.container}>
<FlatList
data={recipesList}
extraData={recipesList}
numColumns={2}
renderItem={displayRecipe}
/>
</View>
);
};
Impossible to display data in the flatlist component, it never enters in the renderItem function no matter what I do. The recipesList is never empty when i log in.
You need to pass an array to the Flatlists data prop.
Try to change this line:
setRecipesList(data);
To this:
// I'm guessing that your query is named getRecipes
setRecipesList(data.getRecipes);
I'm trying to access a screen when you click on an item in my flatlist by passing the date I retrieved from the firebase before, I've tried several things without success so I come to you.
Basically when I click on one of the elements -> A screen with details should appear.
export default function Notifications() {
const dbh = firebase.firestore();
const [loading, setLoading] = useState(true); // Set loading to true on component mount
const [deliveries, setDeliveries] = useState([]); // Initial empty array of users
useEffect(() => {
const subscriber = dbh
.collection("deliveries")
.onSnapshot((querySnapshot) => {
const deliveries = [];
querySnapshot.forEach((documentSnapshot) => {
deliveries.push({
...documentSnapshot.data(),
key: documentSnapshot.id,
});
});
setDeliveries(deliveries);
setLoading(false);
});
// Unsubscribe from events when no longer in use
return () => subscriber();
}, []);
if (loading) {
return <ActivityIndicator />;
}
return (
<FlatList
style={{ flex: 1 }}
data={deliveries}
renderItem={({ item }) => (
<TouchableOpacity
onPress={() => { * HERE I NEED TO PASS DATA AND SHOW AN ANOTHER SCREEN FOR DETAILS * }}>
<View style={styles.container}>
<Text>DATE: {item.when}</Text>
<Text>ZIP DONATEUR: {item.zip_donator}</Text>
<Text>ZIP BENEFICIAIRE: {item.zip_tob_deliv}</Text>
</View>
</TouchableOpacity>
)}
/>
);
}
EDIT: Small precision this screen is located in a Tab.Navigator
you can pass params in navigation,
export default function Notifications(props) {
const { navigation } = props
const dbh = firebase.firestore();
const [loading, setLoading] = useState(true); // Set loading to true on component mount
const [deliveries, setDeliveries] = useState([]); // Initial empty array of users
useEffect(() => {
const subscriber = dbh
.collection("deliveries")
.onSnapshot((querySnapshot) => {
const deliveries = [];
querySnapshot.forEach((documentSnapshot) => {
deliveries.push({
...documentSnapshot.data(),
key: documentSnapshot.id,
});
});
setDeliveries(deliveries);
setLoading(false);
});
// Unsubscribe from events when no longer in use
return () => subscriber();
}, []);
if (loading) {
return <ActivityIndicator />;
}
return (
<FlatList
style={{ flex: 1 }}
data={deliveries}
renderItem={({ item }) => (
<TouchableOpacity
onPress={() => {
navigation.navigate('screenName', {
//pass params here
})
}}>
<View style={styles.container}>
<Text>DATE: {item.when}</Text>
<Text>ZIP DONATEUR: {item.zip_donator}</Text>
<Text>ZIP BENEFICIAIRE: {item.zip_tob_deliv}</Text>
</View>
</TouchableOpacity>
)}
/>
);
}
you can access params in the navigated screen by props.route.params
I'm struggling in using setInterval on react native with hooks;
I've seen some materials about it online and I'm using this custom hook which uses the state counter to display the current element from the array, overtime the counter is increased but it goes blank after it's done with the life cycle of setInterval;
How can I set it to leave it at the latest value or resetting to the first once it's done?
Also the reset button bugs sometimes, it tries to reset, but then come back stopped at the previous position, Am I doing something wrong?
My code so far:
const SensorsDetail = ({ evaluation }) => {
const [ state ] = useState(evaluation);
const [count, setCount] = useState(0)
const [running, setRunning] = useState(false)
const cb = useRef()
const id = useRef()
const start = () => setRunning(true)
const pause = () => setRunning(false)
const reset = () => {
setRunning(false)
setCount(0)
}
function callback () {
setCount(count + 1)
}
// Save the current callback to add right number to the count, every render
useEffect(() => {
cb.current = callback
})
useEffect(() => {
// This function will call the cb.current, that was load in the effect before. and will always refer to the correct callback function with the current count value.
function tick() {
cb.current()
}
if (running && !id.current) {
id.current = setInterval(tick, 250)
}
if (!running && id.current) {
clearInterval(id.current)
id.current = null
}
return () => id.current && clearInterval(id.current)
}, [running])
return(
<View style={styles.view}>
<Card>
<Text style={styles.text}>{state.dados_sensor_1[count]}</Text>
</Card>
<Card>
<Text style={styles.text}>{state.dados_sensor_2[count]}</Text>
</Card>
<Card>
<Text style={styles.text}>{state.dados_sensor_3[count]}</Text>
</Card>
<Card>
<Text style={styles.text}>{state.dados_sensor_4[count]}</Text>
</Card>
<TouchableOpacity onPress={start} style={styles.buttonStyle}>
<Text style={styles.textStyle2}>
Start
</Text>
</TouchableOpacity>
<TouchableOpacity onPress={pause} style={styles.buttonStyle}>
<Text style={styles.textStyle2}>
Pause
</Text>
</TouchableOpacity>
<TouchableOpacity onPress={reset} style={styles.buttonStyle}>
<Text style={styles.textStyle2}>
Reset
</Text>
</TouchableOpacity>
</View>
);
};
This is silly but after studying more about it, I saw that actually what was happening was that the counter was moving past the array size, that's why it was showing blank values;
I just add a limit to value the counter could raise and it's working just fine;
The reset though is still bugging sometimes...
this is the code for the custom hook:
import { useState, useEffect, useRef } from 'react';
export default (initialCount, finalCount, autoStart) => {
const [count, setCount] = useState(initialCount)
const [running, setRunning] = useState(autoStart)
const cb = useRef()
const id = useRef()
const start = () => {
if (count < finalCount){
setRunning(true)
}
}
const pause = () => setRunning(false)
const reset = () => {
setRunning(false);
setCount(initialCount)
}
function callback () {
setCount(count + 1)
if (count == finalCount){
setRunning(false)
}
}
useEffect(() => {
cb.current = callback
})
useEffect(() => {
function tick() {
cb.current()
}
if (running && !id.current) {
id.current = setInterval(tick, 250)
}
if (!running && id.current) {
clearInterval(id.current)
id.current = null
}
return () => id.current && clearInterval(id.current)
}, [running])
return {
count,
start,
pause,
reset
}
};
I want to login to my app but when I first login it works correctly but once I logout from my app and again try to login I get the following error 'Can't perform a state update on an unmount component'. Even though second time it also enters in the app but with the error which should be not there. Only one time it works correctly.
/*Component*/
const LoginScreen = props => {
let _isMounted = false;
const [isLoading , setIsLoading] = useState(false);
const [error , setError] = useState();
const [token , setToken] = useState();
const [url , setUrl] = useState({});
const dispatch = useDispatch();
/*Receiving the token*/
useEffect(() => {
let _isMounted = false;
const tokenReceive = () => {
if(Object.entries(url).length !== 0)
{
const getTokenFromUrl = url['url'].split('=')[1].split('&')[0];
if(getTokenFromUrl !== '')
{
setToken(getTokenFromUrl)
}
}
}
tokenReceive();
return(() => {
_isMounted = true
} )
}, [url ])
/*Dispatching after receiving token*/
useEffect(() =>{
_isMounted = true;
const loginHandler = async ()=> {
if(token !== undefined)
{
setError(null)
setIsLoading(true);
try{
await dispatch(authActions.login(token))
// if(_isMounted){
// props.navigation.navigate('afterAuth')
// }
}
catch(err)
{
setError(err.message)
}
setIsLoading(false)
if(_isMounted){
props.navigation.navigate('afterAuth')
}
}
}
loginHandler()
return(() => {
_isMounted = false
} )
} , [token ])
/*If any error occur*/
useEffect(() => {
if (error) {
Alert.alert('An error occured',error,[{text : 'Okay'}]);
}
return(() => {
console.log('Error'),
error
})
} , [error])
/*Event listener when url changes*/
useEffect(() => {
Expo.Linking.addEventListener('url', (url) => {
setUrl(url);
})
return () => {
Expo.Linking.removeEventListener('url' , (url) => {
setUrl(url)
})
};
} , [])
const prefix = Expo.Linking.makeUrl('token');
const _handlePressButtonAsync = async () => {
let result = await WebBrowser.openBrowserAsync(`https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=**********&response_type=id_token&redirect_uri=${prefix}&scope=openid email profile&response_mode=fragment&state=*****&nonce=****`);
};
return(
<ScrollView >
<TouchableWithoutFeedback onPress={() => {Keyboard.dismiss()}} >
<View style={styles.screen}>
<CircleDiv style={styles.userlogoDiv}>
<View style={styles.userLogo}>
<AntDesign name="user" size={RFValue(39)} color='#4D4848'/>
</View>
</CircleDiv>
<BackgroundUpper style={styles.upperDiv}>
<LogoLong style={ {marginTop : RFValue(100)}}/>
</BackgroundUpper>
<BackgroundLower >
<ScrollView style={{ flex : 1 } } decelerationRate='fast' >
<KeyboardAvoidingView behavior='position' keyboardVerticalOffset={Dimensions.get('screen').height / RFValue(10)}>
<View style={styles.loginDiv}>
<View style={styles.headingDiv}>
<Text style={styles.heading}>LOGIN</Text>
</View>
<View style={styles.buttonDiv}>
<TouchableOpacity>
{!isLoading ? <Button
style={styles.button}
title='LOGIN'
color= '#00B49D'
//onPress = {navigate}
onPress={_handlePressButtonAsync}
/> : <ActivityIndicator size="small" color={Colors.GREEN}/>}
</TouchableOpacity>
</View>
<View style={styles.forgetDiv}>
<Text style={styles.forget}>Forget Password</Text>
</View>
</View>
</KeyboardAvoidingView>
</ScrollView>
</BackgroundLower>
</View>
</TouchableWithoutFeedback>
</ScrollView>
)
};
Error - 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 %s.%s, a useEffect cleanup function,