1

I have a conditional rendering while fetching user location using React Native and Expo. I am changing my state once the button is clicked to show spinner, however for some reason my state variable is always false. I understand useEffect hook however I don't think I need to change the variable on any particular lifecycle. Is this the right reasoning? At first I thought it was due to the async nature of the function however it does not seem likely.

State Init

const [loading, toggleLoading] = useState(false);

Code for onClick

{props.location === null ? (
        <Button onPress={getLocationAsync} mode="contained">
          Get Location
        </Button>
      ) : loading === true ? (
        <Button mode="contained">
          <ActivityIndicator />
        </Button>
      ) : (
        <Button mode="outlined">Received</Button>
      )}

Code for getLocationAsync

const getLocationAsync = async () => {
    toggleLoading(true);
    let { status } = await Permissions.askAsync(Permissions.LOCATION);

    if (status !== "granted") { }

    let location = await Location.getCurrentPositionAsync();
    toggleLoading(false);
  };

Logging state anywhere always yields loading as false. Do I actually require something like useEffect in this case?

Yashank
  • 743
  • 2
  • 7
  • 23

3 Answers3

2

Setting a state in React is asynchronous! That's why your logs shows false.

const onPress = () => toggleLoading(true)

useEffect(() => {
  const getLocationAsync = async () => {
    // your actions here
    toggleLoading(false)
  }
  if (loading) getLocationAsync()
}, [loading])

// in your render
<Button {...{ onPress}} mode="contained">Get Location</Button>

If you do like this, you are 100% sure that you getLocationAsync() code is executed only when loading === true!

vitosorriso
  • 765
  • 1
  • 7
  • 16
  • Yes youre totally right about the state being async hence log showing false. I did get the logical part working so thankyou! – Yashank Mar 28 '20 at 10:23
1

Are you sure it doesn't work? State in React is asynchronous too so your console.log() might not be accurate.

I've created simple example with your code and it works well. Only thing which might be different is your ternary condition. You can't see 'loading' because your first condition is location === null which is null even while loading. I've just modified your first condition like this: location === null && loading !== true and works fine.

Jax-p
  • 7,225
  • 4
  • 28
  • 58
  • 1
    So interesting! The problem indeed was with my logic in displaying the conditional render. However I'm still bit confused with the console.log always being false. Even after an async call, shouldnt my component be rerendered after state change which should still give me true? – Yashank Mar 28 '20 at 10:22
  • @YashankVarshney Depends where do you log your state. If you put `console.log()` right before `return` you will see `false` on load, `true` after click and `false` after async function being completed. Also you can use [state's callback function](https://stackoverflow.com/questions/43370176/using-async-setstate) which is being called right after the state is changed. – Jax-p Mar 28 '20 at 15:04
  • That's the issue however. Console logging always showed me false as the output. Both at initial render and after the state was changed. Usually this wouldnt be an issue but somehow for this case it was. – Yashank Mar 30 '20 at 01:37
0

Can you try

onPress={() => {
   toggleLoading(true);
   getLocationAsync();
}}
iamhuynq
  • 5,357
  • 1
  • 13
  • 36
  • Calling the function this way is interestingly not calling any function at all... I wonder if this is infact due to async calls? – Yashank Mar 27 '20 at 10:39
  • Anonymous arrow functions into render methods are a bad practice, because they are always re-allocated on every render() ! – vitosorriso Mar 27 '20 at 10:48