How to pass data from child component to parent component?

Viewed 820

I am currently learning React, and I am trying to build a small weatherapp to practice with apis, axios and react generally. I built an input component where it's duty is getting the data from the API, and I am holding the data in the useState hook and I want to use the data in the main App component? I am able to pass data from parent App component to input component if I take the functionality in the app component, but this time I start to have problems with input text rendering problems. Here is the code: this is the input component where I search and get the data from the API, and I am trying to pass the weatherData into the main App component and render it there. How is it possible to achieve this?

import React, { useState, useEffect } from 'react';
import axios from 'axios';

const key = process.env.REACT_APP_API_KEY;

function SearchLocation() {
  const [text, textChange] = useState('');
  const [weatherData, setWeatherData] = useState([]);

  const handleText = (e) => {
    textChange(e.target.value);
  };

  const fetchData = async () => {
    const { data } = await axios.get(
      `https://api.weatherapi.com/v1/current.json`,
      {
        params: {
          key: key,
          q: text,
          lang: 'en',
        },
      }
    );
    setWeatherData(data);
  };

  useEffect(() => {
    try {
      fetchData();
    } catch (error) {
      console.log(error);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [text]);
  console.log(weatherData);
  return (
    <div>
      <form>
        <input
          onChange={handleText}
          className="locationInput"
          type="text"
          value={text}
          required
        ></input>
      </form>
    </div>
  );
}
export default SearchLocation;

EDIT:

After moving the states to main component and passing them to children as props I receive 3 errors, GET 400 error from the API, createError.js:16 Uncaught (in promise) Error: Request failed with status code 400 and textChange is not a function error. Here are how components look like. This is the input component:

import React, { useState, useEffect } from 'react';
import axios from 'axios';

const key = process.env.REACT_APP_API_KEY;

function SearchLocation({ weatherData, setWeatherData, text, textChange }) {
  const handleText = (e) => {
    textChange(e.target.value);
  };

  const fetchData = async () => {
    const { data } = await axios.get(
      `https://api.weatherapi.com/v1/current.json`,
      {
        params: {
          key: key,
          q: text,
          lang: 'en',
        },
      }
    );
    setWeatherData(data);
  };

  useEffect(() => {
    try {
      fetchData();
    } catch (error) {
      console.log(error);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [text]);
  console.log(weatherData);
  return (
    <div>
      <form>
        <input
          onChange={handleText}
          className="locationInput"
          type="text"
          value={text}
          required
        ></input>
      </form>
    </div>
  );
}
export default SearchLocation;

this is the parent app component:

import React from 'react';
import { useState } from 'react';
import './App.css';
import './index.css';
import SearchLocation from './components/Input';

function App() {
  const [weatherData, setWeatherData] = useState([]);
  const [text, textChange] = useState('');
  return (
    <div className="App">
      <SearchLocation
        setWeatherData={setWeatherData}
        lastData={weatherData}
        inputText={text}
      />
    </div>
  );
}

export default App;
2 Answers

You'll still need to store the state in the parent component. Pass the setter down as a prop. This is a React pattern called Lifting State Up.

Example:

const App = () => {
  const [weatherData, setWeatherData] = useState([]);

  ...

  return (
    ...
    <SearchLocation setWeatherData={setWeatherData} />
    ...
  );
};

...

function SearchLocation({ setWeatherData }) {
  const [text, textChange] = useState('');

  const handleText = (e) => {
    textChange(e.target.value);
  };

  const fetchData = async () => {
    const { data } = await axios.get(
      "https://api.weatherapi.com/v1/current.json",
      {
        params: {
          key,
          q: text,
          lang: 'en',
        },
      }
    );
    setWeatherData(data);
  };

  useEffect(() => {
    try {
      // Only request weather data if `text` is truthy
      if (text) {
        fetchData();
      }
    } catch (error) {
      console.log(error);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [text]);

  return (
    <div>
      <form>
        <input
          onChange={handleText}
          className="locationInput"
          type="text"
          value={text}
          required
        />
      </form>
    </div>
  );
}

There are two solutions to your problem:-

  1. Firstly you can create the states const [text, textChange] = useState(''); const [weatherData, setWeatherData] = useState([]);, inside your parent component and pass text, textChange, weatherData, setWeatherData as props to your child component.

  2. I would recommend the second way, i.e, implement redux for this and store text, and weatherData into your redux and try to access them from redux.

redux reference:- https://react-redux.js.org/introduction/getting-started

Related