How to make work search bar passing props through components? - javascript

I want to filter data and implement in search bar. In Hook/index.js component I am fetching and filtering data inside useEffects. Then I am passing props in App.js. Afterwards I have a Searchbar component, where I am listening to the input and here it must work. I get undefined.
Hook/index.js component
import React, { useState, useEffect } from "react";
import "./hook.scss";
export default () => {
const [data, setData] = useState([]);
const [error, setError] = useState(null);
const [search, setSearch] = useState("");
const fetchData = () => {
fetch("https://restcountries.eu/rest/v2/all")
.then((res) => res.json())
.then((result) => setData(result))
.catch((err) => console.log("error"));
};
useEffect(() => {
const searchResult =
data && data.filter((item) => item.name.toLowerCase().includes(search));
setSearch(searchResult);
}, []);
useEffect(() => {
fetchData();
}, []);
return [data, error];
};
App.js
import React, { useState }from "react";
import Header from "./components/Header";
import SearchBar from "./components/SearchBar";
import Flag from "./components/Flag";
import useCountries from "./Hooks";
import CountryList from "./components/CountryList";
import "./App.scss";
export default function App() {
const [data, error] = useCountries();
return (
<div className="App">
<SearchBar /> // {/*this throws an error <SearchBar data={data}/> */}
<Header />
{data &&
data.map((country) => (
<div className="CountryList" key={country.name}>
<Flag flag={country.flag} />
<CountryList
population={country.population}
name={country.name}
region={country.region}
/>
{country.languages.map((language, languageIndex) => (
<CountryList key={languageIndex} language={language.name} />
))}
</div>
))}
<useCountries />
</div>
);
return [data, error]
}
Searchbar component
import React, {useState} from "react";
import "./SearchBar.scss";
export default function SearchBar({data}) {
const [search, setSearch] = useState("");
function handleChange(e) {
setSearch(e.target.value);
}
return (
<div className="SearchBar">
<input
className="input"
type="text"
placeholder="search country ..."
value={data}
onChange={handleChange}
/>
{data && data.filter((item) => item.name.toLowerCase().includes(search))}
</div>
);
};

You are sending data variable to input instead of search variable.
In JS filter return array and DOM cannot display array since it is not html or jsx so you need to convert array to jsx with map. with map you can return array or jsx
<div className="SearchBar">
<input
className="input"
type="text"
placeholder="search country ..."
value={search} // change here
onChange={handleChange}
/>
<ul>{(data || []).filter((item) => item.name.toLowerCase().includes(search)).map(e=>(<li key={e.name}>{e.name}</li>))}</ul> /change here
</div>

Your new .filter() Array contains Objects inside it! You need to .map() it before return as Objects are not valid as a React child.
{ data?.filter((item) => item.name.toLowerCase().includes(search)).map((element =>
<>
/* Your code goes here! */
</>) }
Explanation:
Array.prototype.filter() returns a new Array and in your case your Array is filled with Objects, like this:
{data && data.filter((item) => item.name.toLowerCase().includes(search))}
// The code above returns an Array just like below.
const array = [ {name: 'Brazil' /*...others properties*/}, {name: 'USA' /*...others properties*/}, {name: 'England' /*...others properties*/} ];
When you return array, React reject to mount your Objects, cus it can't understand what to do. That's why you map it, to have access to each Object inside it.

Related

useCallback not giving the required effect i need in my program

In the following code; as per my knowledege about useCallback hook 'rerendering has happened!' message should be displayed only when i am typing something inside the input field and shouldn't be printed when I am clicking on the Toggle button. But the message does print for the 2nd case. Why?
my App.js file
import {useState, useCallback} from 'react'
import List from './List'
function App() {
const [a, seta] = useState('')
const [Color, setColor] = useState("#12ab34")
const message = useCallback(() => {
console.log('rerendering has happened!')
return [a+'SAMSUNG', a+'MOTO', a+'NOKIA']
}, [a])
return (
<div className="App">
<input value={a} onChange={e => seta(e.target.value)} />
<button onClick={() => Color === "#12ab34" ? setColor("#abb111") : setColor("#12ab34") }>toggle</button>
<List message={message()} Color={Color}/>
</div>
);
}
export default App;
my List.js file
import React from 'react'
export default function List({message, Color}){
return (<>
<ul>
<li style={{color: Color}}>{message[0]}</li>
<li style={{color: Color}}>{message[1]}</li>
<li style={{color: Color}}>{message[2]}</li>
</ul>
</>)
}
Your function is not created multiple times don't worry. The log statement
'rerendering has happened!'
is running multiple times. It is because your function message() runs multiple times.
Look at <List message={message()} Color={Color}/>.
What you want to do is memoize the value returned from your function, and do not call it unnecessarily.
import { useState, useCallback, useMemo } from "react";
import List from "./List";
function App() {
const [a, seta] = useState("");
const [Color, setColor] = useState("#12ab34");
console.log({ a });
const message = useCallback(() => {
console.log("rerendering has happened!");
return [a + "SAMSUNG", a + "MOTO", a + "NOKIA"];
}, [a]);
const val = useMemo(() => message(), [message]);
return (
<div className="App">
<input value={a} onChange={(e) => seta(e.target.value)} />
<button
onClick={() =>
Color === "#12ab34" ? setColor("#abb111") : setColor("#12ab34")
}
>
toggle
</button>
<List message={val} Color={Color} />
</div>
);
}
export default App;
Link
That being said, the above approach is still counter intuitive to me. The purpose of your function is to calculate a value,. And in your <List... statement, you simple call the function to get the value. So basically you do not need the function anywhere, and just the value. Using useMemo will do the job here.
import { useState, useCallback, useMemo } from "react";
import List from "./List";
function App() {
const [a, seta] = useState("");
const [Color, setColor] = useState("#12ab34");
console.log({ a });
const message = useMemo(() => {
console.log("rerendering has happened!");
return [a + "SAMSUNG", a + "MOTO", a + "NOKIA"];
}, [a]);
return (
<div className="App">
<input value={a} onChange={(e) => seta(e.target.value)} />
<button
onClick={() =>
Color === "#12ab34" ? setColor("#abb111") : setColor("#12ab34")
}
>
toggle
</button>
<List message={message} Color={Color} />
</div>
);
}
export default App;
Link
When you use UseCallback the function will be called on each render the difference is a memorized version of it will be called (new instance will NOT be created).
It is useful when you're passing functions as props.
In your case, you should consider using UseMemo instead which returns a memorized value.

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")}/>
...)
}

React: Passing value to a functional component results in undefined value

I'm trying to pass values one by one to a functional component from array.map function. Unfortunately the component is not redering the value.
This is what I'm getting. There are room names stored in DB that should be printed here.
Homescreen.js
import React, { useState, useEffect } from "react";
import axios from "axios";
import Room from "../components/Room";
export default function Homescreen() {
const [rooms, setRooms] = useState([]);
const [loading, setLoading] = useState();
const [error, setError] = useState();
useEffect(async () => {
try {
setLoading(true);
const data = (await axios.get("/api/rooms/getallrooms")).data;
setRooms(data);
setLoading(false);
setError(false);
} catch (error) {
setLoading(false);
setError(true);
console.log(error);
}
}, []);
return (
<div>
{loading ? (
<h1>Loading...</h1>
) : error ? (
<h1>Error fetching details from API</h1>
) : (
rooms.map((room) => {
return (<div>
<Room key={room._id} room={room}></Room>
</div>
)
})
)}
</div>
);
}
Room.js (Funcitonal component that should print room names):
import React from "react";
function Room(room){
console.log(room.name)
return(
<div>
<h1>Room name: {room.name}.</h1>
</div>
)
}
export default Room;
The data is fetched correctly from db because if, instead of passing the value to component I print directly into my main screen, the values are printed.
In otherwords, in Homescreen.js, doing <p>{room.name}</p> instead of <Room key={room._id} room={room}></Room> print room names correctly.
So I reckon the problem is coming when I'm passing the values as props.
Any help is much appreciated. Thanks.
The parameter passed to a function component is the props object which contains the passed props, so you just need to grab props.room from there:
function Room(props){
console.log(props.room.name)
return(
<div>
<h1>Room name: {props.room.name}.</h1>
</div>
)
}
Or, with object destructuring:
function Room({ room }){
console.log(room.name)
return(
<div>
<h1>Room name: {room.name}.</h1>
</div>
)
}

useFetch custom hook not working properly

I am working on react website.
I have created one custom data fetching hook 'usePostFetch' as follows:
import React, { useState, useEffect } from "react";
//axios
import axios from "axios";
const usePostFetch = () => {
const [postData, setPostData] = useState([]);
const [error, setError] = useState(null);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
const getData = async () => {
setIsLoading(true);
try {
const res = await axios.get("http://localhost:8000/Sell");
const data = await res.data;
setPostData(data);
setIsLoading(false);
} catch (error) {
console.log("Error from fetch: " + error);
setError(error.message);
setIsLoading(false);
}
};
getData();
}, []);
const values = [
...new Set(
postData.map((post) => {
return post.productType;
})
),
];
return { postData, values, error, isLoading };
};
export default usePostFetch;
I have a product page that renders when I click any of the links on the home page with a link "/product/:productId".productId is the id of clicked link product.
Product Page:
import React, { useEffect, useState } from "react";
//react router dom
import { useParams } from "react-router";
//Hooks
import usePostFetch from "../../Hooks/usePostFetch";
//styles
import { Wrapper, Info, Discription } from "./Product.styles";
//Server
const Server = "http://localhost:8000";
const Product = () => {
const { productId } = useParams();
const { postData, isLoading, error } = usePostFetch();
const [data, setData] = useState({});
console.log(postData, isLoading, error);
useEffect( () => {
const fetchData = async () => {
var value = await postData.filter((post) => {
return post._id === productId;
});
console.log(value);
setData(value);
};
fetchData();
}, [postData]);
return (
<Wrapper>
<Info>
{isLoading && <h1> Loading.... </h1>}
{error && <p>ERROR </p>}
{console.log(data)}
<img
src={`${Server}/productImages/${data[0].productImage}`}
alt={`${data[0].productName}`}
/>
<div className="data">
<h1>{data[0].productName}</h1>
<h3>{data[0].productPrice}</h3>
</div>
</Info>
</Wrapper>
);
};
export default Product;
But when I go to that link I got data in console like this:
Because of these empty arrays, I got errors like this:
What can I do or what is wrong with my code?
It appears you are reading state that doesn't exist yet. The initial data state is an empty object:
const [data, setData] = useState({});
And on the initial render you are attempting to read from a 0 property, which OFC is undefined still.
data[0] --> OK, undefined
data[0].productName --> NOT OK, throws error trying to access from undefined
You can conditionally render the data content when you know it's populated:
<Wrapper>
<Info>
{isLoading && <h1> Loading.... </h1>}
{error && <p>ERROR </p>}
{console.log(data)}
{data[0] && (
<img
src={`${Server}/productImages/${data[0].productImage}`}
alt={`${data[0].productName}`}
/>
<div className="data">
<h1>{data[0].productName}</h1>
<h3>{data[0].productPrice}</h3>
</div>
)
</Info>
</Wrapper>
Or you can just use the Optional Chaining operator to defend against null/undefined property accesses:
<Wrapper>
<Info>
{isLoading && <h1> Loading.... </h1>}
{error && <p>ERROR </p>}
{console.log(data)}
<img
src={`${Server}/productImages/${data[0]?.productImage}`}
alt={`${data[0]?.productName}`}
/>
<div className="data">
<h1>{data[0]?.productName}</h1>
<h3>{data[0]?.productPrice}</h3>
</div>
</Info>
</Wrapper>
It also seems that you are really expecting data to be an array, so you will want your initial state to maintain a state/type invariant, so it should also be declared as an array.
const [data, setData] = useState([]);

Rendering info from the Reddit API using React

I am trying to build a basic web app where a user can search for subreddits using the Reddit API.
However, while I can console.log the data I need from the API I cannot seem to figure out how to display it.
import React, { useState, useEffect } from "react";
import Amplify, { API, graphqlOperation } from "aws-amplify";
import aws_exports from "./aws-exports";
import { withAuthenticator, AmplifySignOut } from '#aws-amplify/ui-react';
import awsconfig from "./aws-exports";
import './App.css'
import axios from 'axios'
import CharacterGrid from './components/CharacterGrid'
import SearchBar from './components/SearchBar'
Amplify.configure(awsconfig);
const App = () => {
const [items, setItems] = useState([])
const [isLoading, setIsLoading] = useState(true)
const [query, setQuery] = useState('')
useEffect(() => {
const fetchItems = async () => {
setIsLoading(true)
const result = await axios(
`https://www.reddit.com/subreddits/search.json?q=${query}`
)
setItems(result.data)
setIsLoading(false)
}
fetchItems()
}, [query])
return (
<div className='container'>
<AmplifySignOut/>
<SearchBar style={{ marginTop: "6%" }} getQuery={(q) => setQuery(q)} />
<CharacterGrid isLoading={isLoading} items={items} />
</div>
)
}
export default withAuthenticator(App)
The child component CharacterGrid looks like this:
import React from 'react'
import CharacterItem from './CharacterItem'
const CharacterGrid = ({items, isLoading}) => {
return
isLoading
? (<h1>Loading ...</h1>)
: (
<section className='cards'>
<p>{items.data}</p> //this does not work
<p>{items.children}</p> //this does not work either
</section>
)
}
export default CharacterGrid
All I want to do is show the names of the subreddits that are returned from the API for the query string the user enters. Where am I going wrong? I have also tried converting to JSON, and mapping through the responses using .map() but I keep getting errors no matter what I try. Where am I going wrong?
However, while I can console.log the data I need from the API I cannot seem to figure out how to display it.
Because Reddit API returns an array of subreddits you should use map() function to iterate over the array and convert each item into React element.
items.map(i => (
<li key={i.data.display_name_prefixed}>
{i.data.display_name}
</li>
))
All I want to do is show the names of the subreddits that are returned from the API for the query string the user enters.
You need to inspect the data schema yourself and scrape the response properly.
Here is working example:
const { useState, useEffect } = React;
const App = () => {
const [items, setItems] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [query, setQuery] = useState("");
useEffect(() => {
setIsLoading(true);
if (query.length >= 3) {
axios(`https://www.reddit.com/subreddits/search.json?q=${query}`)
.then(response => setItems(response.data.data.children))
.catch(error => console.log("Error", error));
}
setIsLoading(false);
}, [query]);
return (
<div>
<input type="text" value={query} onChange={e => setQuery(e.target.value)} />
<CharacterGrid items={items} isLoading={isLoading} />
</div>
);
}
const CharacterGrid = ({ items, isLoading }) => {
return isLoading ? (
<h1>Loading ...</h1>
) : (
<ul>
{items.map(i => (
<li key={i.data.display_name_prefixed}>
{i.data.display_name} ({i.data.display_name_prefixed})
</li>
))}
</ul>
);
};
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.2/axios.min.js"></script>
<div id="root"></div>
In order to loop through the elements and e.g: print the title, you could do the following.
items.data.children.map(child => <p> child.data.title </p>)
According to the response provided by reddit,
reddit response

Categories

Resources