L01 should always be either Yes or No based on a query result returned. It starts out as 'No' and then as as soon as the query is checked it receives 'Yes'. However my return View does not update to Yes even though the setLevel01 value of it clearly shows 'Yes' in the console log. Any thoughts?
function DashScreen() {
let [L01, setLevel01 ] = useState('No');
useEffect(() => {
const timer = setInterval(() => (
setLevel01 = data.getlevels[0].level01,
console.log("inside: ",setLevel01)
), 1000);
return () => {
clearInterval(timer);
}
});
const { loading, error, data } = useQuery(GetLevelsQuery)
if (loading) return 'Loading...'
if (error) return 'Something Bad Happened'
console.log("outside: ",L01);
return (
<View><Text>{L01}</Text></View>
);}
setLevel01 is a function use to set the new state value. Try this:
function DashScreen() {
const [L01, setLevel01 ] = useState('No');
useEffect(() => {
const timer = setInterval(() => (
setLevel01(data.getlevels[0].level01)
), 1000);
return () => {
clearInterval(timer);
}
});
const { loading, error, data } = useQuery(GetLevelsQuery)
if (loading) {
return <Text>'Loading...'</Text>;
}
else if (error) {
return <Text>'Something Bad Happened'</Text>;
}
return (
<View>
<Text>{L01}</Text>
</View>
);
}
Related
I am building a React Native (Expo) app that scans for Bluetooth devices. The Bluetooth API exposes a callback for when devices are detected, which I use to put non-duplicate devices into an array:
const DeviceListView = () => {
const [deviceList, setDeviceList] = useState([]);
const startScanning = () => {
manager.startDeviceScan(null, null, (error, device) => {
// Add to device list if not already in list
if(!deviceList.some(d => d.device.id == device.id)){
console.log(`Adding ${device.id} to list`);
const newDevice = {
device: device,
...etc...
};
setDeviceList(old => [...old, newDevice]);
}
});
}
// map deviceList to components
componentList = deviceList.map(...);
return <View> {componentList} </View>
}
The problem is that the callback is called many many times faster than setDeviceList updates, so the duplicate checking doesn't work (if I log deviceList, it's just empty).
If I use an additional, separate regular (non-useState) array, the duplicate checking works, but the state doesn't update consistently:
const DeviceListView = () => {
const [deviceList, setDeviceList] = useState([]);
var deviceList2 = [];
const startScanning = () => {
manager.startDeviceScan(null, null, (error, device) => {
// Add to device list if not already in list
if(!deviceList2.some(d => d.device.id == device.id)){
console.log(`Adding ${device.id} to list`);
const newDevice = {
device: device,
...etc...
};
deviceList2.push(newDevice);
setDeviceList(old => [...old, newDevice]);
}
});
}
// map deviceList to components
componentList = deviceList.map(...);
return <View> {componentList} </View>
}
This code almost works, but the deviceList state doesn't update correctly: it shows the first couple of devices but then doesn't update again unless some other component causes a re-render.
What do I need to do to make this work as expected?
I would suggest wrap your duplicate check within the state set function itself, and then return the same device list if no new devices have been found. This offloads race condition handling to the underlying react implementation itself, which I've found to be good enough for most cases.
Thus it would look something like this:
const DeviceListView = () => {
const [deviceList, setDeviceList] = useState([]);
const startScanning = () => {
manager.startDeviceScan(null, null, (error, device) => {
// Add to device list if not already in list
setDeviceList(old => {
if(!old.some(d => d.device.id == device.id)){
console.log(`Adding ${device.id} to list`);
const newDevice = {
device: device,
// ...etc...
};
return [...old, newDevice]
}
return old
});
});
}
// map deviceList to components
componentList = deviceList.map(...);
return <View> {componentList} </View>
}
Since old is unchanged if no new unique devices are found it will also skip next re-render according to the docs ( which is a neat optimisation :) )
This is the preferred way to implement state updates that are dependant on previous state according to the docs
https://reactjs.org/docs/hooks-reference.html#functional-updates
convert your callback to promise so that until you get completed device list, checkout below code (PS. not tested, please change as you need)
const [deviceList, setDeviceList] = useState([]);
const [scanning, setScanning] = useState(false);
useEffect(() => {
if(scanning) {
setDeviceList([]);
startScanning();
}
}, [scanning]);
const subscription = manager.onStateChange(state => {
if (state === "PoweredOn" && scanning === false) {
setCanScan(true);
subscription.remove();
}
}, true);
const fetchScannedDevices = () => {
return new Promise((resolve, reject) => {
manager.startDeviceScan(null, null, (error, device) => {
// Add to device list if not already in list
if (!deviceList.some(d => d.device.id == device.id)) {
console.log(`Adding ${device.id} to list`);
const newDevice = {
device: device,
// ...etc...
};
resolve(newDevice);
}
if (error) {
reject({});
}
});
});
};
const startScanning = async () => {
try {
const newDevice = await fetchScannedDevices();
setDeviceList(old => [...old, newDevice]);
} catch (e) {
//
}
};
const handleScan = () => {
setScanning(true);
};
// map deviceList to components
componentList = deviceList.map(() => {});
return (
<View>
<Button
onPress={() => handleScan()}>
Scan
</Button>
<View>{componentList}</View>
</View>
);
};
I am using react native, and axios.
I have two parts.
The exercice list that is rendered with useEffect in a div. Inside, there is a Input form which once pressed the Add set button, the set is added to the database and the exercices are fetched again with the passed function.
The main problem is that when I first add an exercice, the exercice s not rendering. I must go back and come again in the page to render the first one. after doing this process I can add as many exercices... And with delete is same. I can delete any exercice but when deleting the last one, it persist and I must leave the page to see the changes...
THIS IS THE FUNCTION THAT ADD THE exercices. It executes once the alert button is pressed
const NewExercice = ({dayID, getAllEx}) => {
// States and ontext change functions
const [exName, setexName] = useState('');
const [comment, setcomment] = useState('');
const handleExname = text => setexName(text);
const handleComments = text => setcomment(text);
// Add new exercices
const handleNewExercice = async () => {
try
{
const status = await data.post('/api/create-exercice', {dayID, exName, comments: comment});
Alert.alert(
'Exercice created',
'Please add new sets to existing exercices',
[
{
text: 'Ok!',
// Fetch again for all the exercices
onPress: getAllEx
}
]
)
}
catch (error)
{
console.log(error);
}
}
Bellow is the component that adds map over the array state
<View>
{error ? (<Text>No exercices created yet.</Text>) :
exArray.map(obj => (
<ExerciceWrapper getAllEx={getAllExercices} navigation={navigation} key={obj.exID} object={obj} />
))}
</View>
Bellow is the function that fetch the data from the DB and set the state to be able to be rendered in the component above
const getAllExercices = async () => {
try
{
const response = await data.get('/api/get-all-ex/' + dayID);
setExArray(response.data);
}
catch (error)
{
if (error.response.status === 404) return setError(true);
else return console.log(error);
}
}
useEffect(() => {
getAllExercices();
}, []);
You need to toggle the error value when you have successful fetch as well
update code to this
const getAllExercices = async () => {
try
{
const response = await data.get('/api/get-all-ex/' + dayID);
setExArray(response.data);
setError(response.data.length < 1)
}
catch (error)
{
if (error.response.status === 404) return setError(true);
else return console.log(error);
}
}
I'm trying to make an api call to retrieve some data:
index.js
const [companyInfo, setCompanyInfo] = useState({})
useEffect(() => {
getCompaniesInfo()
}, [])
getCompaniesInfo = async () => {
try {
const { data } = await axios({
method: 'GET',
url: `myURL/${companyID}/settings`,
headers: { Authorization: `Bearer ${user.token}` },
})
if (data) {
setCompanyInfo(data)
}
} catch (error) {
console.log(error)
}
}
return (
{componentSelected == 0 && <NewRequest companyInfo={companyInfo} />})
)}
const mapStateToProps = (state) => {
return {
companyID: state.auth.id,
user: state.auth,
}
}
export default connect(mapStateToProps)(Parent)
NewRequest.js
const NewRequest= ({ companyInfo }) => {
console.log('companyInfo', companyInfo.time.minuteInterval) // it should print a number, but it says Cannot read property 'minuteInterval' of undefined
return (
<DatePicker
minuteInterval={companyInfo.time.minuteInterval}
)
}
How can I do to use info that I recover from api in the parent also in the child?
The problem is NewRequest is receiving an empty object on first render, you can avoid it by doing something like this
return (
{componentSelected == 0 && companyInfo?.time && <NewRequest companyInfo={companyInfo} />})
)}
I also suggest that you switch useState({}) to either useState(undefined) or some default values object
that way it will be easier to check for a truthy value before rendering or you could have some values to fall back to if the request fails
This is because NewRequest at first render has not yet the result from the API call.
You can add another check when rendering NewRequest to make sure it only render when it gets the result from API.
return (
{
componentSelected == 0 && companyInfo && companyInfo.time &&
<NewRequest companyInfo={companyInfo} />
}
)
I'm trying to output the version number of my app using react-native-version-check, am I missing something here? Console logging latestVersion works but not this.
renderAppVersion = () => {
VersionCheck.getLatestVersion({
provider: 'appStore'
})
.then(latestVersion => {
return latestVersion;
});
}
<Text>App Version: {this.renderAppVersion()}</Text>
VersionCheck.getLatestVersion is an Async function so you wont get value when you use this.renderAppVersion() because it is also an Async function use state for storing the version which got from function and render it
code:
const App = () => {
const [version, setVersion] = useState();
useEffect(() => {
VersionCheck.getLatestVersion().then(v => setVersion(v))
}, [])
return (
<View>
{version && <Text>{version}</Text>}
</View>
);
}
I'm trying to customize the back navigation event on my react native app. Basically, in the body of the component I render different sections depending on the currentSection state variable. What I found out is that the value of currentSection inside the onBackPress function does not get updated, and apparently always has the value of when it was first called (during the useEffect invocation).
const [currentSection, setCurrentSection] = useState<ProviderDetailSections>(ProviderDetailSections.MAIN);
useEffect(() => {
BackHandler.addEventListener(
"hardwareBackPress",
onBackPress
);
return () => BackHandler.removeEventListener(
"hardwareBackPress",
onBackPress
);
}, []);
function onBackPress(): boolean {
console.log(`onBackPress currentSection: ${currentSection}`);
if (currentSection === ProviderDetailSections.MAIN) {
return false;
}
else {
setCurrentSection(ProviderDetailSections.MAIN);
return true;
}
}
The console.log() inside onBackPress always logs the same section (MAIN), no matter what is the actual current section.
Any help would be greatly appreciated!
Check the answer
const [currentSection, setCurrentSection] = useState<ProviderDetailSections>(ProviderDetailSections.MAIN);
useEffect(() => {
BackHandler.addEventListener(
"hardwareBackPress",
onBackPress
);
return () => BackHandler.removeEventListener(
"hardwareBackPress",
onBackPress
);
}, []);
function onBackPress(): boolean {
console.log(`onBackPress currentSection: ${currentSection}`);
setCurrentSection(ProviderDetailSections.MAIN);
return true;
}
}
I've just encountered the same problem - the root of it is the fact, that your useEffect() function is called only once when the component is rendered, and I think the eventListeners are then created for the component with default values, which means your currentSection has default value for your BackHandler functions at all times. What you have to do is add currentSection to dependency array in your useEffect().
const [currentSection, setCurrentSection] = useState<ProviderDetailSections>(ProviderDetailSections.MAIN);
useEffect(() => {
BackHandler.addEventListener(
"hardwareBackPress",
onBackPress
);
return () => BackHandler.removeEventListener(
"hardwareBackPress",
onBackPress
);
}, [currentSection]);
function onBackPress(): boolean {
console.log(`onBackPress currentSection: ${currentSection}`);
if (currentSection === ProviderDetailSections.MAIN) {
return false;
}
else {
setCurrentSection(ProviderDetailSections.MAIN);
return true;
}
}