Nodnin
Nodnin

Reputation: 451

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:

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:


    <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.

Upvotes: 1

Views: 68

Answers (3)

Narek Ghazaryan
Narek Ghazaryan

Reputation: 3123

Here is a forked example to make search work:

https://codesandbox.io/s/newweather-dec-forked-113re

Upvotes: 1

DeveloperLV
DeveloperLV

Reputation: 1781

Sorry if I missed something.. but I have made a simple change and it seems to work for me. I am new to React myself, just started getting into it last night. But here is the code:

const Weather = () => {
  const [data, geWeatherData] = useState(undefined);
  const [city, setCity] = useState("New York");
  const [click, handleClick] = useState(false); // not sure why this is used


  const updateApi = () => {
    const getWeather = async () => {
      city &&
        (await weatherData(city).then((response) => {
          geWeatherData(response.data);
          console.log(response.data.main.temp);
        }));
    };
    getWeather();
  }

  // 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={updateApi} // Altered line here
        />
      </Box>
    </Box>
  );
};

export default Weather;

I figured that you search the City via key press (Enter). So I thought it's better to call a function (updateApi) when Enter is pressed.

Regarding the background change, I see Drew Reese has got the answer for you.

Upvotes: 1

Drew Reese
Drew Reese

Reputation: 202618

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

Upvotes: 1

Related Questions