How can I call a function that belongs to a sibling component either with props or context?
I've been trying to to this using props and context but I'm getting confused because I'm not passing variables but an actual function with parameters
I want to call the function handleSelectedUser that is inside the List component when I click the delete button in the Card component
Here it is in action in Replit
here is my code
App component:
function App() {
const [currentUser, setCurrentUser] = useState(null)
return (
<main>
<List
setCurrentUser={setCurrentUser} />
<Card
currentUser={currentUser}
setCurrentUser={setCurrentUser}
/>
</main> );
}
List Component
function List({setCurrentUser}) {
const [users, setUsers] = useState([])
useEffect(() => {
getUsers()
}, [])
async function getUsers(){
const apiUrl = "https://randomuser.me/api?results=40"
const result = await fetch(apiUrl)
const data = await result.json()
setUsers(data.results)
}
function handleDeleteSelectedUser(userToDelete){
users.filter(user => user.phone != userToDelete.phone)
}
function handleSelectedUser(selectedUser){
setCurrentUser(selectedUser)
}
return(
<>
<div>
<h1>List</h1>
{
users.map((user) =>
<li onClick={() => handleSelectedUser(user)} key={user.phone}>
{user.name.first} {user.name.last}</li>)
}
</div>
</>
)
}
Card Component
function Card({currentUser, setCurrentUser}){
function handleDelete(user){
//Call handleSelectedUser in <List />
setCurrentUser(null)
}
return (
<div className="container">
{currentUser &&
<>
<h2>{currentUser.name.first}'s Details</h2>
<p>{currentUser.phone}</p>
<button onClick={() => handleDelete(currentUser)}>Delete</button>
</>
}
</div>
)
}
If you're trying to "listen" for changes in a sibling component, a common approach is to use a shared prop as a hook dependency in the sibling.
For example:
function List({ currentUser, setCurrentUser }) {
const [users, setUsers] = useState([]);
useEffect(() => {
getUsers();
}, []);
async function getUsers() {
const apiUrl = "https://randomuser.me/api?results=40";
const result = await fetch(apiUrl);
const data = await result.json();
setUsers(data.results);
}
function handleDeleteSelectedUser(userToDelete) {
users.filter((user) => user.phone != userToDelete.phone);
}
function handleSelectedUser(selectedUser) {
setCurrentUser(selectedUser);
}
useEffect(() => {
// do something when currentUser changes
handleSelectedUser(currentUser);
}, [currentUser]);
return (
<>
<div>
<h1>List</h1>
{users.map((user) => (
<li onClick={() => handleSelectedUser(user)} key={user.phone}>
{user.name.first} {user.name.last}
</li>
))}
</div>
</>
);
}
Related
import React, { useEffect, useState } from "react";
import Loading from "./Loading";
function App() {
const url = "https://course-api.com/react-tabs-project";
const [loading, setLoading] = useState(true);
const [data, setData] = useState([]);
async function setCompany(companyName) {
await getData();
const newData = data.filter((info) => info.company === companyName);
setData(newData);
}
async function getData() {
try {
const response = await fetch(url);
const data = await response.json();
setData(data);
setLoading(false);
} catch (err) {
setLoading(false);
console.error(`ERROR ==> ${err}`);
}
}
useEffect(() => {
getData();
}, []);
if (loading) {
return <Loading></Loading>; // simple loading screen
}
return (
<main>
<div className="top-wrapper">
<h2>Experience</h2>
<div className="underline"></div>
</div>
{data.map((item) => {
const { id, order, title, dates, duties, company } = item;
return (
<article key={id}>
<h3>{title}</h3>
<span className="company">{company}</span>
<p>{dates}</p>
<ul>
{duties.map((duty, index) => {
return <li key={index}>{duty}</li>;
})}
</ul>
<button>MORE INFO</button>
</article>
);
})}
<div className="nav-buttons">
<button
onClick={() => {
setCompany("TOMMY");
}}
className="nav-btn"
>
TOMMY
</button>
<button
onClick={() => {
setCompany("BIGDROP");
}}
className="nav-btn"
>
BIGDROP
</button>
<button
onClick={() => {
setCompany("CUKER");
}}
className="nav-btn"
>
CUKER
</button>
</div>
</main>
);
}
export default App;
Sooo... basically I'm trying to filter the array returned by Fetch and have it display only the category I want (I called it "company instead of category in my code") depending on which button I click as shown in the "nav-buttons" div down in the code.
The first time I click on a button it works fine, but the second time it doesn't show anything as if it's filtering from an already filtered array which return no results obviously.
update these two methods with these two lines:
async function setCompany(companyName) {
const response=await getData(); //THIS ONE
const newData = response.filter((info) => info.company === companyName);
setData(newData);
}
async function getData() {
try {
const response = await fetch(url);
const data = await response.json();
setData(data);
setLoading(false);
return data;// And THIS ONE
} catch (err) {
setLoading(false);
console.error(`ERROR ==> ${err}`);
}
}
// Get the oportunity to learn about promises, and you will save so much time. ;)
import React, { useEffect, useState } from "react";
function App() {
const url = "https://course-api.com/react-tabs-project";
const [loading, setLoading] = useState(true);
const [data, setData] = useState([]);
const [companyName, setCompanyName] = useState("");
async function setCompany(companyName) {
getData();
const newData = setData(newData);
}
function getData(companyName) {
setCompanyName(companyName);
fetch(url)
.then((res) => res.json())
.then((info) => {
console.log(info);
return companyName
? info.filter((info) => info.company == companyName)
: info;
})
.then((res) => {
console.log(res);
return setData(res);
})
.catch((err) => {
setLoading(false);
console.error(`ERROR ==> ${err}`);
});
}
useEffect(() => {
getData();
}, []);
return (
<main>
<div className="top-wrapper">
<h2>Experience</h2>
<div className="underline"></div>
</div>
{data.map((item) => {
const { id, order, title, dates, duties, company } = item;
return (
<article key={id}>
<h3>{title}</h3>
<span className="company">{company}</span>
<p>{dates}</p>
<ul>
{duties.map((duty, index) => {
return <li key={index}>{duty}</li>;
})}
</ul>
<button>MORE INFO</button>
</article>
);
})}
<div className="nav-buttons">
<button
onClick={() => getData("TOMMY")}
className="nav-btn"
>
TOMMY
</button>
<button
onClick={() => getData("BIGDROP")}
className="nav-btn"
>
BIGDROP
</button>
<button
onClick={() => getData("CUKER")}
className="nav-btn"
>
CUKER
</button>
</div>
</main>
);
}
export default App;
you don't need to call the same API on each filter as it returns same data if I'm not wrong.
you can filter the data with the derived state, by storing the selected company in state i.e., on each render it calculates based on the selected company.
use the filtered data to render finally.
Here is the full e.g.
import React, { useEffect, useState } from "react";
import Loading from "./Loading";
function App() {
const url = "https://course-api.com/react-tabs-project";
const [loading, setLoading] = useState(true);
const [data, setData] = useState([]);
const [selectedCompany, setSelectedCompany] = useState(""); // store the company on click
const filteredData = selectedCompany ? data.filter(info=> info.company === selectedCompany) : data; // filter data based on selected company
async function getData() {
try {
const response = await fetch(url);
const data = await response.json();
setData(data);
setLoading(false);
} catch (err) {
setLoading(false);
console.error(`ERROR ==> ${err}`);
}
}
useEffect(() => {
getData();
}, []);
if (loading) {
return <Loading></Loading>; // simple loading screen
}
return (
<main>
<div className="top-wrapper">
<h2>Experience</h2>
<div className="underline"></div>
</div>
{filteredData.map((item) => {
const { id, order, title, dates, duties, company } = item;
return (
<article key={id}>
<h3>{title}</h3>
<span className="company">{company}</span>
<p>{dates}</p>
<ul>
{duties.map((duty, index) => {
return <li key={index}>{duty}</li>;
})}
</ul>
<button>MORE INFO</button>
</article>
);
})}
<div className="nav-buttons">
<button
onClick={() => {
setSelectedCompany("TOMMY");
}}
className="nav-btn"
>
TOMMY
</button>
<button
onClick={() => {
setSelectedCompany("BIGDROP");
}}
className="nav-btn"
>
BIGDROP
</button>
<button
onClick={() => {
setSelectedCompany("CUKER");
}}
className="nav-btn"
>
CUKER
</button>
</div>
</main>
);
}
export default App;
try putting a check case before filter to insure that your array isn't empty.
async function setCompany(companyName) {
await getData();
{data ?
const newData = data.filter((info) => info.company === companyName);
:
null}
setData(newData);
}
I think part of your issue is when your calling get data on button click your state isn't set before running the filter logic. I would look over your functional logic and ask yourself is this the best way to do this and am i trying to filter before or after my response.
I'm working on implementing a braintree payment method in my react/mui app. I've found a way that works, but it's in a class component. How can I convert this info a proper functional component?
const BraintreeDropInPaymentMethod = () => {
class Store extends React.Component {
instance;
state = {
clientToken: '<BRAIN TREE KEY>'
};
async componentDidMount() {
const response = await fetch("server.test/client_token");
const clientToken = await response.json();
this.setState({
clientToken,
});
}
async buy() {
const { nonce } = await this.instance.requestPaymentMethod();
await fetch(`server.test/purchase/${nonce}`);
}
render() {
if (!this.state.clientToken) {
return (
<div>
<h1>Loading...</h1>
</div>
);
} else {
return (
<div>
<DropIn
options={{ authorization: this.state.clientToken }}
onInstance={(instance) => (this.instance = instance)}
/>
<Button
variant='contained'
onClick={this.buy.bind(this)}
>
Create Account
</Button>
<Button
variant='outlined'
sx={{ marginLeft: 3 }}
color='warning'
onClick={(e) => handleCancelAccountCreation(e)}
href='/store-front'
>
Cancel
</Button>
</div>
);
}
}
}
const [user, setUser] = useState({})
const handleCancelAccountCreation = (event) => {
setUser({})
document.getElementById('signInBtn').hidden = false
}
return (
<Store/>
)
}
this is my attempt, but I'm coming up short on how I should handle componentDidMount(). I know how to handle useState in some situations, except for this one. Also, how can I handle the 'instance' section in a functional format? thanks.
const BraintreeDropInPaymentMethod = () => {
const [token, setToken] = useState('<BRAIN TREE KEY>')
const [user, setUser] = useState({})
const contactServer = async () => {
const res = await fetch('server.test/client_token')
const clientToken = await res.json()
console.log(clientToken)
setToken(token)
}
const buy = async () => {
const { nonce } = await this.instance.requestPaymentMethod()
await fetch(`server.test/purchase/${nonce}`)
}
const handleCancelAccountCreation = (event) => {
setUser({})
document.getElementById('signInBtn').hidden = false
}
const createAccountOptions = () => {
if (!token) {
return (
<div>
<h1>Loading...</h1>
</div>
) else {
return (
<div>
<DropIn
options={ authorization: {setToken})
onInstance={(instance) => (this.instance = instance)}
/>
<Button
variant="contained'
onClick={buy}
>
Create Account
</Button
variant='outlined'
sx={{ marginLeft: 3 }}
color='warning'
onClick={(e) => handleCancelAccountCreation(e)}
href='/store-front'
>
<Button>
Cancel
</Button>
</div>
)
}
}
}
return(
<>
<createAccountOptions/>
</>
)
}
The functional equivalent of componentDidMount() is the useEffect hook.
In this case you would change this:
async componentDidMount() {
const response = await fetch("server.test/client_token");
const clientToken = await response.json();
this.setState({
clientToken,
});
}
Into something like this:
useEffect(() => {
fetchData();
}, []);
const fetchData = async () => {
const response = await fetch("server.test/client_token");
const clientToken = await response.json();
setState((old) => clientToken);
};
Using the useEffect hook with an empty array as a dependency makes the function in it only run once as the component mounts.
I'm trying to implement search functionality on the Pokemon API, I have tried different methods but I cannot make it work for some reason.
My idea was to make a function that handles the changes on the event and then pass that to a hook(useState) and maybe make a get request and rerender?
I have this method for getting all Pokemons from the API. Should I make a new designed to filter the request?
export async function getAllPokemon(url) {
return new Promise((resolve) => {
fetch(url).then(res => res.json())
.then(data => {
resolve(data)
})
});
}
function App() {
....
const [filter, setFilter] = useState("");
function handleChange(event) {
setOption(event.target.value)
console.log(option)
}
const initialURL = `https://pokeapi.co/api/v2/pokemon?limit=50`
const handleSearchChange = (e) => {
setFilter(e.target.value);
};
useEffect(() => {
async function fetchData() {
let response = await getAllPokemon(initialURL)
await loadPokemon(response.results);
setLoading(false);
}
fetchData();
}, [option])
const loadPokemon = async (data) => {
let _pokemonData = await Promise.all(data.map(async pokemon => {
let pokemonRecord = await getPokemon(pokemon)
return pokemonRecord
}))
setPokemonData(_pokemonData);
}
return (
<>
<div >
<div>
<input
type="text"
id="header-search"
placeholder="Search Pokemon"
name="s"
onChange={handleSearchChange}
/>
<button >Search</button>
</div>
</div>
<div>
{loading ? <h1 style={{ textAlign: 'center' }}>Loading...</h1> : (
<>
<div className="grid-container">
{pokemonData.map((pokemon) => {
return <Card pokemon={pokemon} />
})}
</div>
</>
)}
</div>
</>
);
}
export default App;
You need to filter the array of Pokemon's that you are getting in your onChange. So something like
onChange = e = > {
setPokemons(pokemons.filter(pokemon => pokemon.indexOf(e.target.value) > -1))
}
So, this will toggle your local state variable that you are rendering on every key that is typed, which in turn will filter your array.
I'm trying to use a search bar component in my React project to search/filter through an api list of movies by title. Right now my search term is console logging, but i'm trying to filter the movie list to only show the titles that match the term. I'm having issues with updating my movies state with the term and displaying the new array.
App
import SearchBar from "../Search/SearchBar"
export default function Movies() {
const [movies, setMovies] = useState([]);
async function getMovies() {
const movieData = await fetchMovies();
console.log(movieData);
setMovies(
movieData.data.data.sort((a, b) => a.title.localeCompare(b.title))
);
}
useEffect(() => {
getMovies();
}, []);
async function onSearchSubmit(term) {
console.log(term)
let fill = []
movies.filter((movie) => {
if(movie.title === term) {
fill.push(movie.title)
}
setMovies(fill)
})
}
return (
<>
<Nav
movies={movies}
setMovies={setMovies}/>
<SearchBar
onSubmit={onSearchSubmit}/>
{movies ? (
<div>
<div>
{movies.map((m, idx) => {
return <div key={idx}>{m.title}</div>;
})}{" "}
</div>
</div>
) : (
"loading..."
)}
</>
);
}
Search Bar component
import React,{useState} from 'react';
const SearchBar = ({onSubmit}) => {
const [term, setTerm] = useState("")
function onFormSubmit(event){
event.preventDefault()
onSubmit(term)
}
return (
<div className="ui segment">
<form onSubmit={onFormSubmit} className="ui form">
<div className="field">
<label>Movie Search</label>
<input
type="text"
value={term}
onChange={(e) => setTerm( e.target.value)}
/>
</div>
</form>
</div>
);
}
export default SearchBar;
First of all additional state is needed to record the loaded moves list:
const movies = useRef([]);
const [filteredMovies, setFilteredMovies] = useState([]);
It is better to declare handlers with useCallback and avoid the mixture of declarative and imperative styles. For example:
const onSearchSubmit = useCallback(async (term) => {
if (term) {
const _ = movies.current.filter(({ title }) => (title === term));
setFilteredMovies(_);
} else {
setFilteredMovies(movies.current);
}
}, [movies]);
https://jsfiddle.net/pq9xkewz/2/
I want to create a simple layout where the user can choose a dynamic dropdown that retrieves data based on an API call using React.js and Django as backend, but however, I want to pre-render category from the database and let the user choose which Industry, and change the layout data accordingly
async function fetchFeed(domain) {
return api.get(`http://localhost:8002/api/v1/xxxx/list/?domain=${domain}`);
}
async function fetchDomain() {
return api.get('http://localhost:8002/api/v1/xxxx/domain/'); # return all domains
}
export default function Board () {
const [isModalOpen, setModalIsOpen] = useState(false);
const [users, setUsers] = useState([]);
const [responseData, setResponseData] = useState([])
const [domains, setDomains] = useState([]);
// fetches data
const fetchData = (domain) => {
fetchFeed(domain)
.then((response)=>{
setResponseData(response.data.results)
})
.catch((error) => {
console.log(error)
})
}
const handleOnClick = async (data) => {
try {
setUsers(data);
// Now that the data has been fetched, open the modal
setModalIsOpen(true);
} catch (err) {
console.error("failed", err);
}
};
useEffect(() => {
fetchData();
}, []);
return (
<div className="container content">
<select>options</select>
{responseData.map((data) => (
<div className="col" key={data.t_id}>
<div className="row">{data.tactic_name}</div>
{data.data.map((item) => (
<div className="row" key={item._id} onClick={() => handleOnClick(item)}><span>{item.title}</span></div>
))}
</div>
))}
{isModalOpen && <Modal onRequestClose={() => setModalIsOpen(false)} data={users}/> }
</div>
);
}