Search code examples
reactjsreact-hooksuse-effect

Unable to pass data between components in react


I'm trying to create a Weather app to practice React and have a few issues. You can find my code here: Codesandbox

I have 3 components:

  • Form.jsx
  • Weather.jsx
  • WeatherDetail.jsx

Weather.jsx

I'm using axios to pull data from the openweather API. The default city is set to "New York". Passing {data, city, set city, handleClick} as props to the Form.jsx.


    const Weather = () => {
      const [data, geWeatherData] = useState(undefined);
      const [city, setCity] = useState("New York");
      const [click, handleClick] = useState(false);
    
      useEffect(() => {
        if (!click) {
          const getWeather = async () => {
            city &&
              (await weatherData(city).then((response) => {
                geWeatherData(response.data);
                // console.log(response.data);
                console.log(response.data.main.temp);
              }));
          };
          getWeather();
          handleClick(true);
        }
      }, [click, city]);
    
      const classes = useStyles();
      return (
        <Box className={classes.component}>
          <Box className={classes.weatherContainer}>
            <Form
              data={data}
              city={city}
              setCity={setCity}
              handleClick={handleClick}
            />
          </Box>
        </Box>
      );
    };
    ```
    **Form.jsx**
    
    I'm planning to use this component to design the input but also get the city from the user.I'm also passing data to WeatherDetail component so that I can show content. Ideally, I should make this into a separate one, I decided to just club them together.
    ```
    const Form = ({ city, setCity, handleClick, data }) => {
      const classes = useStyles();
    
      const handleCityChange = (value) => {
        setCity(value);
      };
      return (
        <>
          <Box>
            <TextField
              inputProps={{ className: classes.input }}
              value={city}
              autoFocus
              className={classes.input}
              onKeyPress={(e) => {
                if (e.key === "Enter") {
                  handleClick(true);
                }
              }}
              onChange={(e) => handleCityChange(e.target.value)}
              label="Enter any City in USA"
              variant="standard"
            />
          </Box>
          <Weatherdetail data={data} />
        </>
      );
    };
    
    export default Form;

WeatherDetail.jsx

Passing the city and data from the API to construct the visual details of city weather.


    const WeatherDetail = ({ data }) => {
      const classes = useStyles();
      return data ? (
        <>
          <Box className={classes.temp} component="div">
            {data.main.temp}
            <Box style={{ fontSize: "10px" }} component="span">
              Fahranheit
            </Box>
          </Box>
        </>
      ) : (
        <Box>
          <p>Lets do something</p>
        </Box>
      );
    };
    
    export default WeatherDetail;

Things that are not working out for me:

  • When I change city, for some reason setCity(value) does not call the API and update the the temp.
  • I want to pass change background when temperature changes.

    <Box className={data.main.temp < 40 ?classes.componentFog : classes.component}>
          <Box className={classes.weatherContainer}>
            <Form
              data={data}
              city={city}
              setCity={setCity}
              handleClick={handleClick}
            />
          </Box>
        </Box>

But data does not seem to pass.

I tried speaking to a few dev friends but could not resolve the issue. Thank you in advance.


Solution

  • You only set the click to true once, it's never toggled back to false so additional queries can be made.

    You also need to handle failed weather requests.

    Form - When the TextField is interacted with you should reset the click state.

    const Form = ({ city, setCity, handleClick, data }) => {
      const classes = useStyles();
    
      const handleCityChange = (value) => {
        handleClick(false); // <-- onChange reset the click state
        setCity(value);
      };
      
      return (
        <>
          <Box>
            <TextField
              inputProps={{ className: classes.input }}
              value={city}
              autoFocus
              className={classes.input}
              onKeyPress={(e) => {
                if (e.key === "Enter") {
                  handleClick(true);
                }
              }}
              onChange={(e) => handleCityChange(e.target.value)}
              label="Enter any City in USA"
              variant="standard"
            />
          </Box>
          <Weatherdetail data={data} />
        </>
      );
    };
    

    Weather - Catch rejected Promises from the axios requests/weatherData service.

    useEffect(() => {
      if (!click) {
        const getWeather = async () => {
          city &&
            (await weatherData(city).then((response) => {
              geWeatherData(response.data);
              console.log(response.data.main.temp);
            }).catch(error => {
              // log/show error message/etc... 
            }));
        };
        getWeather();
        handleClick(true);
      }
    }, [click, city]);
    

    Since the data state is potentially undefined you should handle conditionally rendering the class using Optional Chaining operator.

    className={data?.main?.temp < 40 ? classes.componentFog : classes.component}
    

    Edit unable-to-pass-data-between-components-in-react