Bit stuck on this. I have a Main parent component that goes to a Search component which then goes to a Select component. The search asks for a city and then goes to an API and brings back 5 search results and I send them to the select component where I map the results into a selection form. Once the user selects the city and submits it I need the selection to be stored somewhere in the main so I can use it on other children. Any help is appreciated, still getting a handle on React.
I've tried just making the Select component a direct child of the main but it didn't like that. I've tried sending the "cityObj" down through the search into the select but no luck, unless I just did something wrong. I thought maybe making the "Body" component that needs the info a child of the "Select" but I'm still working on that.
Main.js
function Main() {
const [cityObj, setCityObj] = React.useState('');
return (
<div className="container p-5 bg-primary">
<i className='text-danger fw-bold'>Main component</i>
<h2 className='m-3'>My React Weather Application</h2>
<Search />
<hr />
<Body />
</div>
);
}
Search.js
function Search(props) {
const [searchTerm, setSearchTerm] = React.useState('');
const [cityArray, setCityArray] = React.useState([]);
const [cel, setCel] = React.useState(null);
const [selectedCity, setSelectedCity] = React.useState(null);
const Weather_API_key = '55c5392db030dbb75249aa1ff9b8a871';
const url = 'http://api.openweathermap.org/geo/1.0/direct';
const handleChange = (event) => {
setSearchTerm(event.target.value);
}
const handleSubmit = (event) => {
console.log('The form submitted with input: ' + searchTerm);
setCel(() => (searchTerm - 32) / 1.8);
event.preventDefault(); // Prevent default form submission behavior
console.log(searchTerm);
fetch(`${url}?q=${searchTerm}&limit=5&appid=${Weather_API_key}&units=metric`)
.then(response => response.json())
.then(data => {
console.log(data);
setCityArray(data);
console.log('API data came mounted');
});
}
return (
<div className="container p-3 bg-success">
<i className='text-danger fw-bold'>Search component</i>
<form onSubmit={handleSubmit} className='my-3 row g-3'>
<label className="col-sm-4 col-form-label">
Please enter city name:
</label>
<div className="col-sm-4">
<input type="text" value={searchTerm}
onChange={handleChange} className="form-control" />
</div>
<div className="col-sm-4">
<input type="submit" value="Search" className="btn btn-primary mb-3" />
</div>
</form>
<Select cityArray={cityArray} />
</div>
);
}
Select.js
function Select(props) {
const [userInput, setUserInput] = React.useState('');
const [selectedValue, setSelectedValue] = React.useState(null);
const handleChange = (event) => {
// Get the input from the user and save it in a state variable
// event.target is the input element
setUserInput(event.target.value);
}
const handleSubmit = (event) => {
console.log('The form submitted with input: ' + userInput);
setSelectedValue(() => userInput);
event.preventDefault(); // Prevent default form submission behavior
}
return (
<div className="container p-3 bg-warning">
<i className='text-danger fw-bold'>Select component</i>
<form onSubmit={handleSubmit} className=' row g-3'>
<label className="col-sm col-form-label">
Choose your country:</label>
<select value={userInput} onChange={handleChange}
className='form-select col-md'>
{props.cityArray.map((city, index) =>
<option key={city.name + index} value={`${city.name} , ${city.country}`}>
{city.name}, {city.country}</option>
)}
</select>
<div className="col-sm col-form-label">
<input type="submit" value="Submit" className="btn btn-primary mb-3" />
</div>
</form>
{
selectedValue &&
<div> You selected {selectedValue} </div>
}
</div>
);
}
A solution would be to use React Context
. What is React Context? It is a state in React and it functions like a useState
hook. The big difference is, while creating a context, it can be wrapped around a component or components and all those components will have access to the state, without the need to pass the props up or down. I will write a short example, how it would look like:
Main.js
:
import { createContext, useState } from 'react';
const CityContext = createContext();
export function Main() {
const [cityObj, setCityObj] = useState();
return (
<CityContext.Provider value={{ cityObj, setCityObj }}>
<Search />
<Body />
</CityContext.Provider>
);
}
In the code above, we created a context with the createContext
hook and wrapped it around our Search
and Body
component. Now ALL the components inside the Context Provider have access to cityObj
and setCityObj
. How can we use and change the context?
Search.js
let { cityObj, setCityObj } = useContext(CityContext);
const changeCityState = () => {
setCityObj({exampleObj: "example"});
}
return (
<div>
{cityObj}
</div>
);
In the code Above we use the useContext
hook and pass the CityContext as a parameter to access the value
of the context provider. Now we can use the context the same as a "normal" state. If you change the state, EVERY component will be rerenderd, where the state is being used! I recommend to read more on (https://react.dev/reference/react/createContext).