Rodolfo Mori
Rodolfo Mori

Reputation: 13

Setting variable with ReactJS's useState hook does not work

I'm trying to set a variable with useState after an API call, but it doesn't work. Debugging by reactotron, he makes the API call, but he doesn't set the variable.

export default function Forecast({ navigation }) {
  const [cityData, setCityData] = useState([]);
  const idNavigation = navigation.state.params.cityData.woeid;

  async function loadCityData(cityID) {
    const response = await api.get(`${cityID}`);
    setCityData([response]);
    console.tron.log(cityData);
  }

  useEffect(() => {
    if (idNavigation) {
      loadCityData(idNavigation);
    }
    return () => {};
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [idNavigation]);

  return <Text>Forecast Weather</Text>;
}

Forecast.propTypes = {
  navigation: PropTypes.shape({
    state: PropTypes.object,
  }).isRequired,
};

Upvotes: 0

Views: 766

Answers (3)

syjsdev
syjsdev

Reputation: 1336

because useState is asynchronous function.

    setCityData([response]); // asynchronous function so not set the new data to state yet.
    console.tron.log(cityData);// so you get the old data.

See this Example

const Forecast = ({ idNavigation }) => {
    const [cityData, setCityData] = React.useState([]);
  
    function loadCityData(cityID) {
      setTimeout(() => {
        setCityData([1,2,3,4,5]);
        console.log("this is old data", cityID, cityData); // because useState is asynchronous function
      }, 2000);
    }
  
    React.useEffect(() => {
      if (idNavigation) {
        console.log("load ", idNavigation);
        loadCityData(idNavigation);
      }
    }, [idNavigation]);
    
    React.useEffect(() => {
      if(cityData.length > 0) {
        console.log("this is new data", cityData);
      }
    }, [cityData]); // when cityData changed and component mounted, this function called.
  
    return <div>Forecast Weather</div>;
  }
  
  class App extends React.Component {
    state = {
      id: 1,
    }
    
    componentDidMount() {
      setTimeout(() => {
        this.setState({id: 2});
      }, 2000);
    }
  
    render() {
      return (
        <div>
          <Forecast idNavigation={this.state.id}/>
        </div>
      );
    }
  }
  
  ReactDOM.render( <App / > , document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.7.0-alpha.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.7.0-alpha.2/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Solution

if you use Class Component you can use callback of setState() function. but if you use Functional Component you can't use a callback.
so you should use useEffect() to solve this problem.

Upvotes: 0

sonicmario
sonicmario

Reputation: 609

Umm, I think it maybe reactotron or api problem. Just try

const [cityData, setCityData] = useState('foo');

...

return <Text>{JSON.stringify(cityData)}</Text>;

If your plobrem came from reactron, then you can see the response from API.

Upvotes: 0

Clarity
Clarity

Reputation: 10873

Setting state in React is async for the most part and the changes to the state might not be visible if you try to console.log them right away. The recommended way to do this with hooks, is to check for the updated state in useEffect:

 async function loadCityData(cityID) {
  const response = await api.get(`${cityID}`);
  setCityData([response]);
}

// Track cityData and log the changes to it
useEffect(() => {
  console.log(cityData);
}, [cityData]);

// Rest of the code
// ...

Upvotes: 1

Related Questions