Sid
Sid

Reputation: 1014

Rendering state right after updating the state (react)

Im just wondering how i can stop the function from returning before the state has been updated - Here is my code so i can explain more clearly.

    const mockData = {
    current: {
        temperature: 'loading'
    }
}
export default function Weather({ city }) {
    const [data, setData] = useState(mockData)
    const url = `http://api.weatherstack.com/current?access_key=2cbe1b14f771abee0713f93317e1b107&query=${city}`
    useEffect(() => {
        axios.get(url).then(({ data }) => {
            setData(data, () => { })
        })
    }, [])

    return (
        <div>
            <h1>Weather</h1>
            <p>temperature: {data.current.temperature}</p>

        </div>
    )
}

Right now i am using mockData because if i dont i will get an error because the .current.temperature properties do not exist (because set state hasnt been updated yet). How can i stop the error and stop the div being returned before the set state has been updated or atleast stop the error and return an empy div or something.

Upvotes: 1

Views: 37

Answers (2)

Mikhail
Mikhail

Reputation: 900

You need to check that data exists before you reference data.current, and you need to check that data.current exists before you reference data.current.temperature. If you access a property of undefined, your code will crash.

You need to have some additional state, such as isLoading

If you want a hack, you can do this:


export default function Weather({ city }) {
    const [data, setData] = useState(mockData)
    const url = `http://api.weatherstack.com/current?access_key=2cbe1b14f771abee0713f93317e1b107&query=${city}`
    useEffect(() => {
        axios.get(url).then(({ data }) => {
            setData(data, () => { })
        })
    }, [])

    function renderTemperature() {
      if (!data) {
        return null;
      } else if (data && !data.current) {
        return null;
      } else if (data.current && data.current.temperature) {
        return <p>temperature: {data.current.temperature}</p>
      }
    }
    return (
        <div>
            <h1>Weather</h1>
            { renderTemperature() }
        </div>
    )
}

Much better solution:


export default function Weather({ city }) {
    const [data, setData] = useState(null)
    const [isLoaded, setIsLoaded] = useState(false);
    const url = `http://api.weatherstack.com/current?access_key=2cbe1b14f771abee0713f93317e1b107&query=${city}`
    useEffect(() => {
        axios.get(url).then(({ data }) => {
            setData(data, () => { })
            setIsLoaded(true);
        })
    }, [])

    function renderTemperature() {
    }
    return (
        <div>
            <h1>Weather</h1>
            { isLoaded ? <p>temperature: {data.current.temperature}</p> : null}
        </div>
    )
}

Upvotes: 0

Spencer Bard
Spencer Bard

Reputation: 1035

What you can do is add a conditional within your return.

Try this:

return (
  <div>
    <h1>Weather</h1>
    <p>Temperature: {(data && data.current) ? data.current.temperature : ""}</p>
  </div>
)

You can also use optional chaining to achieve the same result.


return (
  <div>
    <h1>Weather</h1>
    <p>Temperature: {data?.current?.temperature || ""}</p>
  </div>
)

Upvotes: 1

Related Questions