Issues with passing state between two separate components using React Hooks - javascript

So my issue is that I’m basically trying to pass the data that I have fetched from an API on my Home page, which is fetched and stored in the ‘geo’ variable upon pressing the submit button, and pass it onto the Maps page (accessed by pressing the Maps button after a postcode has been submitted), which will then use the latitude and longitude from ’geo’ (geo.result.latitude/longitude) to fetch from another API upon loading and display that data on the page. However, I’m having an issue with understanding how to pass state (or the values within the geo variable) between the two components/pages (Home and Maps) using hooks. I’m reasonably new to React, and very new to Hooks, so my understanding is very basic at the moment. Any help would be much appreciated :)
Home.js
import React, { useState, useEffect, useCallback } from 'react'
import { Link } from 'react-router-dom'
const Home = () => {
const [postCode, setPostcode] = useState({
pCode: ''
})
const [geo, setGeo] = useState([])
const fetchRequest = useCallback((e) => {
e.preventDefault()
fetch(`https://api.postcodes.io/postcodes/${postCode.pCode}`)
.then(res => res.json())
.then(res => setGeo(res))
console.log(geo)
}, [{ ...postCode }])
const handleChange = (e) => {
setPostcode({ ...postCode, pCode: e.target.value })
console.log(postCode)
}
return <section >
<div className='container'>
<form className="form" onSubmit={fetchRequest}>
<input className="input" type="text" placeholder="Text input" onChange={handleChange} />
< button>submit</button>
<Link className='button' to={{
pathname: '/maps'
}}>Map
</Link>
</form>
</div>
</section>
}
export default Home
Maps.js
const Maps = () => {
const [events, setEvents] = useState([])
const fetchRequest = useCallback(() => {
fetch(`https://api.list.co.uk/v1/events?near=${viewport.latitude},${viewport.longitude}/10`, {
headers: {
'Authorization': `Bearer ${API_KEY}`
}
})
.then(res => res.json())
.then(res => setEvents(res))
}, [{ ...viewport }])
useEffect(() => {
fetch(`https://api.list.co.uk/v1/events?near=${geo.result.latitude},${geo.result.longitude}/10`, {
headers: {
'Authorization': `Bearer ${API_KEY}`
}
})
.then(res => res.json())
.then(res => setEvents(res))
return () => console.log('Unmounting component')
}, [])
const [viewport, setViewport] = useState({
width: '100vw',
height: '100vh',
latitude: 51.45523,
longitude: -2.59665,
zoom: 13.5
})
if (events.length === 0) {
return <div>Loading...</div>
}
return <div>
<ReactMapGL mapboxApiAccessToken={TOKEN}
mapStyle="mapbox://styles/dredizzle/ck3owxclr138a1cqnzupab2hc"
{...viewport}
onViewportChange={viewport => {
setViewport(viewport)
}}
onClick={
fetchRequest
}
>
{events.map(event => (
<Popup
key={event.event_id}
latitude={event.schedules[0].place.lat}
longitude={event.schedules[0].place.lng}
>
</Popup>
))}
{/* <Popup latitude={51.45523} longitude={-2.59665}>
<div>event here</div>
</Popup> */}
<GeolocateControl
positionOptions={{ enableHighAccuracy: true }}
trackUserLocation={false}
/>
</ReactMapGL>
</div>
}
export default Maps

In the Maps component, I think you're overwriting the value of events when you repeat this line:
const [events, setEvents] = useState([])
You should just be able to reference this.state.events within the Maps component, and use this.setState() within Maps to add data to the events array from within the component.

Related

How to stop a React App breaking on refreshing when using UseEffect

import {useState, useEffect } from 'react'
import axios from 'axios'
const Singlecountry = ({searchedCountries, setWeather, weather}) => {
const weatherName = searchedCountries[0].capital
const iconname = () => {
if (weather === undefined) {
return null
}
weather.map(w => w.weather[0].icon)
}
console.log(iconname)
useEffect(() => {
axios.get(`https://api.openweathermap.org/data/2.5/weather?q=${weatherName}&appid=${process.env.REACT_APP_API_KEY}`)
.then(response => {
const apiResponse = response.data;
console.log(apiResponse)
console.log(`Current temperature in ${apiResponse.name} is ${apiResponse.main.temp - 273.15}℃`);
setWeather([apiResponse])
}).catch(error => {
console.log(error);
})
}, [])
return(
<div>
capital: {searchedCountries.map(c => <p>{c.capital}</p>)}
area: {searchedCountries.map(c => <p>{c.area}</p>)}
<h2>Languages</h2>
<ul>
{
searchedCountries.map(c =>
<ul>
{Object.values(c.languages).map(l => <li>{l}</li>)}
</ul>
)
}
</ul>
{searchedCountries.map(c => <img src={Object.values(c.flags)[0]} alt="" /> )}
<h3>Weather</h3>
<p>temperature is {weather.map(w => w.main.temp - 273.15)} degrees Celsius</p>
<p>wind is {weather.map(w => w.wind.speed)} miles per hour</p>
<img src={`http://openweathermap.org/img/wn/${iconname}.png`} alt="" />
</div>
)
}
const Countries = ({ searchedCountries, handleClick, show, setWeather, setCountries, weather}) => {
if (weather === undefined) {
return null
}
if (searchedCountries.length >= 10) {
return (
<div>
<p>too many countries to list, please narrow your search</p>
</div>
)
}
if (searchedCountries.length === 1) {
return (
<Singlecountry searchedCountries={searchedCountries} setWeather={setWeather} weather={weather}/>
)
}
if (show === true) {
return (
<Singlecountry searchedCountries={searchedCountries} setWeather={setWeather} />
)
}
return (
<ul>
{searchedCountries.map(c => <li>{c.name.common}<button onClick={handleClick} >show</button></li>)}
</ul>
)
}
const App = () => {
const [countries, setCountries] = useState([])
const [newSearch, setNewSearch] = useState('')
const [show, setShow] = useState(false)
const [weather, setWeather] = useState('')
const handleSearchChange = (event) => {
setNewSearch(event.target.value)
}
const handleClick = () => {
setShow(!show)
}
const searchedCountries =
countries.filter(c => c.name.common.includes(newSearch))
useEffect(() => {
axios
.get('https://restcountries.com/v3.1/all')
.then(response => {
setCountries(response.data)
})
}, [])
return (
<div>
<div><p>find countries</p><input value={newSearch} onChange={handleSearchChange} /></div>
<div>
<h2>countries</h2>
<Countries searchedCountries={searchedCountries} handleClick={handleClick} show={show} setCountries={setCountries} setWeather={setWeather} weather={weather}/>
</div>
</div>
)
}
export default App
The following code is designed to display information on countries when the user types in the countries' name in the search bar, including capital city, temperature and its weather.
The app fetches country data from a Countries API and when the user searches for a specific country, the weather its then fetched from a Weather API.
However, when the app is refreshed, the app breaks when searching for an individual country's weather.
Does anyone know why this is and how to solve it?
Thanks
It looks like you're using axios inside useEffect which can cause and infinite loop and crash your app. I recommend creating a separate function for your data fetching and then call the function in the useEffect like so:
const fetchCountries = useCallback(() => {
axios
.get('https://restcountries.com/v3.1/all')
.then(response => {
setCountries(response.data)
})
}, [])
useEffect(() => {
fetchCountries()
}, [fetchCountries])
The key is the dependency array in useEffect which will only update if there is a change in the list of countries from fetchCountries function, thus preventing the infinite loop.

How can I update the setDegrees based on the city I write with React?

I'm trying to learn react.
Based on what I write in the input field, the temperature should change consequently, but I can't find which is the best method to do it.
My logic tells me to put the setDegrees under the inputChange function so when I type a new city the weather also change, but I can't do it since the data i'm trying to get aren't global.
import React, {useState, useEffect} from 'react'
import axios from 'axios';
import City from './components/City';
function App() {
const [city, setCity] = useState("Rome")
const [degrees, setDegrees] = useState("")
useEffect(() => {
const options = {
url: 'https://weatherapi-com.p.rapidapi.com/current.json',
params: {q: city},
headers: {
'X-RapidAPI-Host': 'weatherapi-com.p.rapidapi.com',
'X-RapidAPI-Key': '---'
}
};
axios.request(options).then(res => {
setDegrees(res.data.current.temp_c)
}).catch( error => console.log(error))
},[])
const inputChange = e => {
setCity (e.target.value)
}
return (
<div>
<input type="text" placeholder='Type the city' onChange={inputChange}/>
<City
city = {city}
degrees = {degrees}
/>
</div>
)
}
export default App
You are missing a logic where you re-fetch results on city change, there are plenty ways to approach it, here is a simple example (lazy fetch):
function App() {
const [city, setCity] = useState("Rome");
const [degrees, setDegrees] = useState("");
const fetchDegrees = (city) => {
const options = {
url: "https://weatherapi-com.p.rapidapi.com/current.json",
params: { q: city },
headers: {
"X-RapidAPI-Host": "weatherapi-com.p.rapidapi.com",
"X-RapidAPI-Key": "---",
},
};
axios
.request(options)
.then((res) => {
setDegrees(res.data.current.temp_c);
})
.catch((error) => console.log(error));
};
useEffect(() => {
fetchDegrees();
}, []);
const inputChange = (e) => {
setCity(e.target.value);
};
return (
<div>
<input type="text" placeholder="Type the city" onChange={inputChange} />
<button onClick={() => fetchDegrees(city)}>Search city</button>
<City city={city} degrees={degrees} />
</div>
);
}

refresh antd table on datasource change

I have a page that contains a searchbar component and an antd table component and I want my antd table to refresh whenever the datasource is changed or when I search in the searchbar. For now my table only updates when I refresh the page so I added a window.location.reload() to my onSearch function in order for it to work as intended but I want it to update without having to refresh the whole page. Is there a way to do that?
Here's my table.js
export default function Table() {
const [dataSource, setdataSource] = useState([]);
const [loading,setLoading] = useState(false);
useEffect(() => {
setLoading(true);
fetch(`${apiEndpoint}/api/managed-list`)
.then((response) => response.json())
.then((data) => {
setdataSource(data);
console.log(data)
})
.catch((err) => {
console.log(err);
})
.finally(() => {
setLoading(false);
});
}, []);
return (
<div>
<Table
bordered={true}
loading={loading}
columns={columns}
dataSource={dataSource}
pagination={true}
/>
</div>
)}
here's my searchbar.js
function SearchBar() {
const [searchedText, setSearchedText] = useState("");
let navigate = useNavigate();
const onSearch = () => {
navigate(`/search?q=${searchedText}`)
//find out a solution to update without refreshing
// window.location.reload()
console.log(searchedText)
fetch(`${apiEndpoint}/api/managed-list`,{
method: 'POST',
body: JSON.stringify({
content:searchedText
}), headers:{"Content-type":"application/json; charset=UTF-8",'Accept': 'application/json'}
}).then(response => response.json()).then(message=>console.log(message))
};
return (
<div>
<Input.Search
placeholder="Enter Company Name or keyword..."
onChange={(e) => {
setSearchedText(e.target.value);
}}
onSearch={onSearch}
onPressEnter={onSearch}
enterButton ></Input.Search>
</div>
);
}
export default SearchBar;

Stop react causing an infinite loop using useEffect hook

I am very new to react and node, I have managed to create an API for a simple todo list. I have fetched the data from the api and presenting it on the screen.
If I leave the dependency array empty on the useEffect() hook it will only render once and doesn't loop. But If I add a new Todo it will not update the list unless I refresh. So I put the todos state into the dependency array, this will then show the new item when I add it but if I look at the network tab in the dev tools its hitting the api in an infinite loop. What am I doing wrong ?
here is the code:
App
import React, { useState, useEffect } from "react";
import Todo from "./components/Todo";
import Heading from "./components/Heading";
import NewTodoForm from "./components/NewTodoForm";
const App = () => {
const [todos, setTodos] = useState([]);
useEffect(() => {
const getTodos = async () => {
const res = await fetch("http://localhost:3001/api/todos");
const data = await res.json();
setTodos(data);
};
getTodos();
}, []);
return (
<div className="container">
<Heading todos={todos} />
<section className="todos-container">
<ul className="todos">
{todos.map((todo) => (
<Todo key={todo._id} todo={todo} />
))}
</ul>
</section>
<section className="todo-form">
<NewTodoForm />
</section>
</div>
);
};
export default App;
Heading
import React from "react";
const Heading = ({ todos }) => (
<header>
<h1>Todos</h1>
<p>
{todos.length} {todos.length === 1 ? "Item" : "Items"}
</p>
</header>
);
export default Heading;
Todo
import React, { useState } from "react";
const Todo = ({ todo }) => (
<li>
{todo.name}
<input type="checkbox" />
</li>
);
export default Todo;
NewTodoForm
import React, { useState } from "react";
import { Plus } from "react-feather";
const NewTodoForm = () => {
const [formData, setFormData] = useState({
name: "",
completed: false,
});
const { name } = formData;
const handleOnChange = (e) => {
setFormData({
...formData,
[e.target.name]: e.target.value,
});
};
const handleSubmit = async (e) => {
e.preventDefault();
await fetch("http://localhost:3001/api/todos", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify(formData),
});
setFormData({
name: "",
completed: false,
});
};
return (
<form onSubmit={handleSubmit}>
<div className="form-control">
<Plus className="plus" />
<input
name="name"
type="text"
placeholder="Add New Item"
onChange={handleOnChange}
value={name}
/>
<button>Add</button>
</div>
</form>
);
};
export default NewTodoForm;
If I comment all the components out and only have the App component it still infinite loops when I add todos to the dependency array of the useEffect() hook.
So instead of giving that as a dependency write the function outside the useEffect so that you can call that function after you add a todo
Example:
const getTodos = async () => {
const res = await fetch("http://localhost:3001/api/todos");
const data = await res.json();
setTodos(data);
};
useEffect(() => {
getTodos();
}, []);
So getTodos will only run once initially and runs again only on the onSubmit or onClick of your Todo, So, just call getTodos function onSubmit or onClick

Semantic UI React: Cannot take values from REST API for dropdown

I am trying to use the Dropdown element of Semantic UI React. It is meant to work with a REST API that allows to get a list of movies. React is configured to fetch data from the appropriate REST API application (this already works for other elements of the frontend).
I would like to get the list of movie names as options. Please have a look at the following JS snippet.
import React, { useState, useEffect } from "react";
import { Dropdown } from "semantic-ui-react";
export const MovieDropdown = () => {
const [movie, setMovie] = useState("");
const [movieOptions, setMovieOptions] = useState([]);
useEffect(() => {
fetch("/movies")
.then((response) => response.json())
.then((data) =>
setMovieOptions(
data.map((x) => {
return { key: x.name, text: x.name, value: x.name };
})
)
);
}, []);
return (
<Dropdown
placeholder="Select Movie"
search
selection
options={movieOptions}
onChange={(e) => setMovie(e.target.value)}
/>
);
};
export default MovieDropdown;
I could not figure it out from https://react.semantic-ui.com/modules/dropdown/#usage-remote.
Your code looks good. Change a small thing and it will work:
onChange={e => setMovie(e.target.value)} // you cannot use event in setState. furthermore checkout the second param of the onChange-Event
to
onChange={(e, {value}) => setMovie(value)}
checkout fixing-react-warning-synthetic-events-in-setstate
here's the full working code
import React, { useState, useEffect } from "react";
import { Dropdown } from "semantic-ui-react";
export const MovieDropdown = () => {
const [movie, setMovie] = useState("");
const [movieOptions, setMovieOptions] = useState([]);
useEffect(() => {
fetch("/movies")
.then((response) => response.json())
.then((data) =>
setMovieOptions(
data.map((x) => {
return { key: x.name, text: x.name, value: x.name };
})
)
);
}, []);
return (
<Dropdown
placeholder="Select Movie"
search
selection
options={movieOptions}
onChange={(e, {value}) => setMovie(value)}
/>
);
};
export default MovieDropdown;

Categories

Resources