How to pass data from child to parent and render content based on selected value in dropdown? - javascript

I am learning React as I am fetching data from Pokéapi to make a list component, card component, detail component and filter component. I am trying to make a filter so you can filter by pokémon type. Only the cards that also contain that type string should then render (Not there yet). So I am not sure if a) I should make a different call from API inside PokemonList depending on selected value or b) if I should compare the values and just change how the PokemonCard element is rendered inside PokemonList.js depending on the comparison. I managed to pass data from filter to the list component. I have then been trying to pass the type data from PokemonCard.js to the list component so that I can compare these two values but I find it hard to use callbacks to pass the type data from the card component, since I dont pass it through an event or something like that.
Which method should I use here to simplify the filtering? Make different API call or render PokemonCard element conditionally?
Is it a good idea to compare filter option to pokemon card's type in PokemonList.js? Then how can I pass that data from the card component since I don't pass it through click event?
Thankful for any ideas! I paste the code from list component that contains the cards, card component and filter component.
PokemonList component:
import { useState } from 'react';
import useSWR from 'swr';
import PokemonCard from './PokemonCard';
import PokemonFilter from './PokemonFilter';
import './PokemonList.css';
const PokemonList = () => {
const [index, setIndex] = useState(0);
const [type, setType] = useState('');
function selectedType(type) { // value from filter dropdown
setType(type)
console.log("handled")
console.log(type)
}
const url = `https://pokeapi.co/api/v2/pokemon?limit=9&offset=${index}`;
const fetcher = (...args) => fetch(...args).then((res) => res.json())
const { data: result, error } = useSWR(url, fetcher);
if (error) return <div>failed to load</div>
if (!result) return <div>loading...</div>
result.results.sort((a, b) => a.name < b.name ? -1 : 1);
return (
<section>
<PokemonFilter onSelectedType={selectedType} selectedPokemonType={type} />
<div className="pokemon-list">
<div className="pokemons">
{result.results.map((pokemon) => (
<PokemonCard key={pokemon.name} pokemon={pokemon} /> // callback needed??
))}
</div>
<div className="pagination">
<button
onClick={() => setIndex(index - 9)}
disabled={result.previous === null}
>
Previous
</button>
<button
onClick={() => setIndex(index + 9)}
disabled={result.next === null}
>
Next
</button>
</div>
</div>
</section>
)
}
export default PokemonList;
PokemonCard component:
import { Link } from "react-router-dom";
import useSWR from 'swr';
import './PokemonCard.css';
const PokemonCard = ({ pokemon }) => {
const { name } = pokemon;
const url = `https://pokeapi.co/api/v2/pokemon/${name}`;
const { data, error } = useSWR(url);
if (error) return <div>failed to load</div>
if (!data) return <div>loading...</div>
const { types, abilities } = data;
// types[0].type.name <---- value I want to pass to PokemonList.js
return (
<div className='pokemon-card'>
<div className='pokemon-card__content'>
<img
className='pokemon-card__image'
src={data.sprites.front_default}
alt={name}
/>
<div className='pokemon-card__info'>
<p className='pokemon-card__name'>Name: {name}</p>
<p className='pokemon-card__abilities'>Abilities: {abilities[0].ability.name}</p>
<p className='pokemon-card__categories'>Category: {types[0].type.name}</p>
</div>
</div>
<Link className='pokemon-card__link' to={{
pathname: `/${name}`,
state: data
}}>
View Details
</Link>
</div>
)
}
export default PokemonCard;
PokemonFilter component:
import './PokemonFilter.css';
import useSWR from 'swr';
const PokemonFilter = ({onSelectedType, selectedPokemonType}) => {
const url = `https://pokeapi.co/api/v2/type/`;
const fetcher = (...args) => fetch(...args).then((res) => res.json())
const { data: result, error } = useSWR(url, fetcher);
if (error) return <div>failed to load</div>
if (!result) return <div>loading...</div>
function filteredTypeHandler(e) {
console.log(e.target.value);
onSelectedType(e.target.value);
}
console.log(selectedPokemonType)
return(
<div className="pokemon-types__sidebar">
<h2>Filter Pokémon by type</h2>
<select
name="pokemon-type"
className="pokemon-types__filter"
onChange={filteredTypeHandler}
>
<option value="All">Filter By Type</option>
{result.results.map((type) => {
return (
<option key={type.name} value={type.name}> {type.name}</option>
)
})}
</select>
</div>
)
}
export default PokemonFilter;

Here is an example to improve, modify, ... I didn't test, it's just a visual example.
I don't know about useSWR sorry, I use axios in my example...
If you want to centralize all your API requests, you can create a useApi hook, on the internet you will find tutorials.
PokemonList.js
import React, { useState, useEffect } from 'react';
import axios from 'axios'; // or swr
import PokemonFilter from './PokemonFilter';
import PokemonCard from './PokemonCard';
export default function PokemonList() {
const [data, setData] = useState([]);
const [filter, setFilter] = useState('');
// Executed every first render
useEffect(() => {
getData();
}, []);
// Executed only when filter changes
useEffect(() => {
getDataByTypes(filter);
}, [filter]);
// Get data
const getData = async () => {
const uri = 'https://xxx';
try {
const response = await axios.get(uri);
setData(response.data...);
} catch (error) {
console.log(error);
}
};
// Get data by types
const getDataByTypes = async (filter) => {
const uri = `https://xxx/type/${filter}...`;
if (filter) {
try {
const response = await axios.get(uri);
setData(response.data...);
} catch (error) {
console.log(error);
}
}
};
return (
<div className="main">
<PokemonFilter filter={filter} setFilter={setFilter} />
<div className="container">
<div className="cards-container">
{data.map((d) => (
<PokemonCard key={d.name} data={d} />
))}
</div>
</div>
</div>
);
}
PokemonCard.js
import React, { useState, useEffect } from 'react';
import axios from 'axios';
export default function PokemonCard({ data }) {
const [pokemons, setPokemons] = useState();
useEffect(() => {
getPokemons(data);
}, [data]);
// Get Pokemons
const getPokemons = async (data) => {
const uri = `https://xxx/pokemon/${data.name}/`;
try {
const response = await axios.get(uri);
setPokemons(response.data...);
} catch (error) {
console.log(error);
}
};
return (
<div>
{pokemons && (
<div className="card">
<img src={pokemons.sprites.front_default} alt={pokemons.name} />
<p>{pokemons.name}</p>
<p>{pokemons.abilities[0].ability.name}</p>
<p>{pokemons.types[0].type.name}</p>
</div>
)}
</div>
);
}
PokemonFilter.js
import React, { useState, useEffect } from 'react';
import axios from 'axios';
export default function PokemonFilter({ filter, setFilter }) {
const [types, setTypes] = useState([]);
useEffect(() => {
getType();
}, []);
// Get Type
const getType = async () => {
const uri = 'https://xxx/type/';
try {
const response = await axios.get(uri);
setTypes(response.data.results....);
} catch (error) {
console.log(error);
}
};
const handleFilter = (e) => {
setFilter(e.target.value);
};
return (
<select onChange={handleFilter} value={filter}>
<option>Filter by type</option>
{types.map((type) => {
return (
<option key={type.name} value={type.name}>
{type.name}
</option>
);
})}
</select>
);
}

Related

How can I send the state (useState) of one file component to another file's component?

REACT.js:
Let say I have a home page with a search bar, and the search bar is a separate component file i'm calling.
The search bar file contains the useState, set to whatever the user selects. How do I pull that state from the search bar and give it to the original home page that
SearchBar is called in?
The SearchBar Code might look something like this..
import React, { useEffect, useState } from 'react'
import {DropdownButton, Dropdown} from 'react-bootstrap';
import axios from 'axios';
const StateSearch = () =>{
const [states, setStates] = useState([])
const [ stateChoice, setStateChoice] = useState("")
useEffect (()=>{
getStates();
},[])
const getStates = async () => {
let response = await axios.get('/states')
setStates(response.data)
}
const populateDropdown = () => {
return states.map((s)=>{
return (
<Dropdown.Item as="button" value={s.name}>{s.name}</Dropdown.Item>
)
})
}
const handleSubmit = (value) => {
setStateChoice(value);
}
return (
<div>
<DropdownButton
onClick={(e) => handleSubmit(e.target.value)}
id="state-dropdown-menu"
title="States"
>
{populateDropdown()}
</DropdownButton>
</div>
)
}
export default StateSearch;
and the home page looks like this
import React, { useContext, useState } from 'react'
import RenderJson from '../components/RenderJson';
import StateSearch from '../components/StateSearch';
import { AuthContext } from '../providers/AuthProvider';
const Home = () => {
const [stateChoice, setStateChoice] = useState('')
const auth = useContext(AuthContext)
console.log(stateChoice)
return(
<div>
<h1>Welcome!</h1>
<h2> Hey there! Glad to see you. Please login to save a route to your prefered locations, or use the finder below to search for your State</h2>
<StateSearch stateChoice={stateChoice} />
</div>
)
};
export default Home;
As you can see, these are two separate files, how do i send the selection the user makes on the search bar as props to the original home page? (or send the state, either one)
You just need to pass one callback into your child.
Homepage
<StateSearch stateChoice={stateChoice} sendSearchResult={value => {
// Your Selected value
}} />
Search bar
const StateSearch = ({ sendSearchResult }) => {
..... // Remaining Code
const handleSubmit = (value) => {
setStateChoice(value);
sendSearchResult(value);
}
You can lift the state up with function you pass via props.
const Home = () => {
const getChoice = (choice) => {
console.log(choice);
}
return <StateSearch stateChoice={stateChoice} giveChoice={getChoice} />
}
const StateSearch = (props) => {
const handleSubmit = (value) => {
props.giveChoice(value);
}
// Remaining code ...
}
Actually there is no need to have stateChoice state in StateSearch component if you are just sending the value up.
Hello and welcome to StackOverflow. I'd recommend using the below structure for an autocomplete search bar. There should be a stateless autocomplete UI component. It should be wrapped into a container that handles the search logic. And finally, pass the value to its parent when the user selects one.
// import { useState, useEffect } from 'react' --> with babel import
const { useState, useEffect } = React // --> with inline script tag
// Autocomplete.jsx
const Autocomplete = ({ onSearch, searchValue, onSelect, suggestionList }) => {
return (
<div>
<input
placeholder="Search!"
value={searchValue}
onChange={({target: { value }}) => onSearch(value)}
/>
<select
value="DEFAULT"
disabled={!suggestionList.length}
onChange={({target: {value}}) => onSelect(value)}
>
<option value="DEFAULT" disabled>Select!</option>
{suggestionList.map(({ id, value }) => (
<option key={id} value={value}>{value}</option>
))}
</select>
</div>
)
}
// SearchBarContainer.jsx
const SearchBarContainer = ({ onSelect }) => {
const [searchValue, setSearchValue] = useState('')
const [suggestionList, setSuggestionList] = useState([])
useEffect(() => {
if (searchValue) {
// some async logic that fetches suggestions based on the search value
setSuggestionList([
{ id: 1, value: `${searchValue} foo` },
{ id: 2, value: `${searchValue} bar` },
])
}
}, [searchValue, setSuggestionList])
return (
<Autocomplete
onSearch={setSearchValue}
searchValue={searchValue}
onSelect={onSelect}
suggestionList={suggestionList}
/>
)
}
// Home.jsx
const Home = ({ children }) => {
const [result, setResult] = useState('')
return (
<div>
<SearchBarContainer onSelect={setResult} />
result: {result}
</div>
)
}
ReactDOM.render(<Home />, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.9.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.9.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Just pass a setState to component
parent component:
const [state, setState] = useState({
selectedItem: ''
})
<StateSearch state={state} setState={setState} />
change parent state from child component:
const StateSearch = ({ state, setState }) => {
const handleStateChange = (args) => setState({…state, selectedItem:args})
return (...
<button onClick={() => handleStateChange("myItem")}/>
...)
}

Getting undefined props in functional react components

How to pass the {requests} prop to the RequestRow component after executing the setRequests? My understanding is that the requests get initialized as undefined in the beginning and before being set with the asynchronously called object, it gets passed to the RequestRow component as undefined, and the error occurs.
import React, { useState, useEffect } from 'react';
import 'semantic-ui-css/semantic.min.css';
import Layout from '../../../components/Layout';
import { Button } from 'semantic-ui-react';
import { Link } from '../../../routes';
import Campaign from '../../../blockchain/campaign';
import { Table } from 'semantic-ui-react';
import RequestRow from '../../../components/RequestRow';
const RequestsIndex = ({ address }) => {
const { Header, Row, HeaderCell, Body } = Table;
const campaign = Campaign(address);
const [requestCount, setRequestCount] = useState();
const [requests, setRequests] = useState([]);
const getRequests = async () => {
const count = await campaign.methods.getRequestsCount().call();
setRequestCount(count);
};
let r;
const req = async () => {
r = await Promise.all(
Array(parseInt(requestCount))
.fill()
.map((_element, index) => {
return campaign.methods.requests(index).call();
})
);
setRequests(r);
};
useEffect(() => {
getRequests();
if (requestCount) {
req();
}
}, [requestCount]);
return (
<Layout>
<h3>Requests List.</h3>
<Link route={`/campaigns/${address}/requests/new`}>
<a>
<Button primary>Add Request</Button>
</a>
</Link>
<Table>
<Header>
<Row>
<HeaderCell>ID</HeaderCell>
<HeaderCell>Description</HeaderCell>
<HeaderCell>Amount</HeaderCell>
<HeaderCell>Recipient</HeaderCell>
<HeaderCell>Approval Count</HeaderCell>
<HeaderCell>Approve</HeaderCell>
<HeaderCell>Finalize</HeaderCell>
</Row>
</Header>
<Body>
<Row>
<RequestRow requests={requests}></RequestRow>
</Row>
</Body>
</Table>
</Layout>
);
};
export async function getServerSideProps(context) {
const address = context.query.address;
return {
props: { address },
};
}
export default RequestsIndex;
The RequestRow component is shown below. It takes in the {requests} props, which unfortunately is undefined.
const RequestRow = ({ requests }) => {
return requests.map((request, index) => {
return (
<>
<div>Request!!!</div>
</>
);
});
};
export default RequestRow;
The snapshot of the error is shown below:
I think React is trying to render your component before your promises resolve. If that's the case, all you need to do is set a default value (an empty array in your case) for your requests.
const [requests, setRequests] = useState([]);
May the force be with you.

Cannot display Fetched data to the UI in React

it doesn't show an error and the project works just fine. I can log the data to the console as well. but it doesn't display in the UI. this is a tutorial project on youtube
I'm getting data from the API and passing that to the tours and tour components. and Tour component displays the fetched data.
App component
import React, { useState, useEffect } from "react";
import Loading from "./Loading";
import Tours from "./Tours";
// ATTENTION!!!!!!!!!!
// I SWITCHED TO PERMANENT DOMAIN
const url = "https://course-api.com/react-tours-project";
function App() {
const [loading, setLoading] = useState(true);
const [tours, setTours] = useState([]);
const fetchTours = async () => {
try {
const response = await fetch(url);
const tours = await response.json();
setLoading(false);
setTours(tours);
} catch (error) {
setLoading(true);
console.log(error);
}
};
useEffect(() => {
fetchTours();
}, []);
if (loading) {
return (
<main>
<Loading />
</main>
);
}
return (
<main>
<Tours tours={tours} />
</main>
);
}
export default App;
Tours component
import React from "react";
import Tour from "./Tour";
const Tours = ({ tours }) => {
return (
<section>
<div className="title">
<h2>Our Tours</h2>
<div className="underline"></div>
</div>
<div>
{tours.map((tour, index) => {
return <Tour key={tour.id} {...tours} />;
})}
</div>
</section>
);
};
export default Tours;
Tour Component
import React, { useState } from "react";
const Tour = ({ id, image, info, price, name }) => {
return (
<article className="single-tour">
<img src={image} alt={name} />
<footer>
<div className="tour-info">
<h4>{name}</h4>
<h4 className="tour-price">AUD{price}</h4>
</div>
<p>{info}</p>
<button className="delete-btn">Not Interested</button>
</footer>
</article>
);
};
export default Tour;
Try this code:
useEffect(async () => {
await fetchTours();
}, []);
I think your UI has not updated after the data arrived. You need to wait for your data is fetched.
Try to remove the setting of state in the function and move it to use effect. Have the API call only return the list instead of having it retrieving the list and setting the state.
const fetchTours = async () => {
const response = await fetch(url);
const tours = await response.json();
return tours;
};
useEffect(() => {
const fetchAndSetTourState = async () => {
const data = await fetchTours();
setTours(data);
setLoading(false);
}
fetchAndSetTourState();
}}, []);

How to pass fetched data as props to a component with react hooks?

I'm trying to create a component with data from a API, but i don't find a way to pass the data as props to my 'Episode' component.
Here is the code:
App.js
import React, { useState, useEffect } from 'react';
const url = 'https://swapi.co/api/films/'
function Episode(props){
return (
<div>
{props.title}
{props.release_date}
</div>
)
}
function App() {
const [content, setContent] = useState(null)
useEffect(async () => {
const response = await fetch(url)
const data = await response.json()
const [...movies] = data.results
setContent(movies)
}, [])
return (
<div>
{content && <Episode movie={content[0]}/>}
</div>
)
}
export default App;
your data is in a movie prop:
<Episode movie={content[0]}/>
So in your component you need to access props.movie:
function Episode(props){
return (
<div>
{props.movie.title}
{props.movie.release_date}
</div>
)
}

How make work a search filter in react for node array

Hi guys i make a search filter in react for an array i get via my node server, but i got this error:
×
TypeError: props.filteredCharacters.map is not a function in components/CharacterList/index.js:6
Here the 2 files:
import React, { useEffect, useState } from 'react'
import SearchBox from '../SearchBox'
import CharacterList from '../CharacterList'
const SearchDisney = () => {
const [inputs, setInputs] = useState('');
const [btn, setBtn] = useState(false);
const [apiResponse, setApiResponse] = useState([]);
const [searchCharacter, setSearchCharacter] = useState('');
useEffect(() => {
callAPI();
if (inputs.length > 2) {
setBtn(true)
} else if (btn) {
setBtn(false)
}
}, [inputs, btn])
const callAPI = () => {
fetch("http://localhost:9000/disneyCharacter")
.then(res => res.json())
.then(res => setApiResponse(res))
}
const handleInput = (e) => {
setSearchCharacter(e.target.value)
}
const filteredCharacters = () => {
apiResponse.filter((character) => {
return character.name.toLowerCase().includes(searchCharacter.toLowerCase())
})
}
return (
<div className="search-container">
<h1>Personnage Infos</h1>
<SearchBox handleInput={handleInput} />
<CharacterList filteredCharacters={filteredCharacters} />
</div>
)
}
export default React.memo(SearchDisney)
And the CharacterList:
import React from 'react'
import Character from '../Character'
const CharacterList = (props) => {
const characters = props.filteredCharacters.map((character, id) => {
return <Character key={id} name={character.name} username={character.username} yearCreation={character.yearCreation}/>
})
return (
<div>
{ characters }
</div>
)
}
export default CharacterList
i can display the array in the first file but now i want to make search filter and got this error, any advice to get ride of this error?
Looks like there are 2 things you need to fix here:
on the SearchDisney component, you are not returning anything from the filteredCharacters function. Here is the fix:
const filteredCharacters = () => {
//need to return this
return apiResponse.filter((character) => {
return character.name.toLowerCase().includes(searchCharacter.toLowerCase())
})
}
Addtionally, in order for CharacterList to recieve the filteredCharacters prop as an array - you have to call the filteredCharacters function which returns this array, for example, like this:
<div className="search-container">
<h1>Personnage Infos</h1>
<SearchBox handleInput={handleInput} />
//call the function here:
<CharacterList filteredCharacters={filteredCharacters()} />
</div>

Categories

Resources