How would I only show data when searched in input? - reactjs

I'm learning react and am making a simple app that allows users to search for countries from an api.
I have successfully fetched the api and set it to some state and created a filter component for searches in a text input.
However I would like to show nothing until the user types in the search input which would then show the countries matching the letters typed.
What is the most used way of doing this?
const App = () => {
const [countries, setCountry] = useState([])
const [search, setSearch] = useState('')
useEffect(() => {
console.log('effect')
axios
.get(`https://restcountries.eu/rest/v2/all`)
.then(response => {
console.log(response,'promise fulfiled')
setCountry(response.data)
})
}, [])
const handleSearch = (e) => {
setSearch(e.target.value)
}
const filter = countries.filter(country =>
country.name.toLowerCase().includes(search.toLowerCase())
);
return (
<div className="App">
Search Country:
<input type='text' onChange={handleSearch}/>
<Country filter={filter} />
</div>
);
}
const Country = ({filter}) => {
return(
<ul style={{listStyle: 'none'}}>
{
filter.map(country =>
<li key={country.name}>
{country.name}
</li>)
}
</ul>
)
This is the current code, however it shows all countries from the api. I would like to show no countries until user types in input box.
Thanks.

Just have a conditional render for <Country />.
Only display the list of countries if search is supplied.
{ search && <Country filter={filter} /> }
Demo

Related

Search bar not displaying results in React

I am fetching the data of all products from an API call. All these products are objects. I am then checking if the value of search sting is present in all the productsindividually. If yes, that product is added to a different array. All the elements'titles in this different array are displayed as a dropdown menu.
Where am I going wrong? Plz help
Code on React:
import Head from "next/head";
const App = () => {
var resultfoundarray=[];
const [Searchquery,setSearchquery] = useState("");
const [AllProducts,setAllProducts] = useState([]);
const allproducts = () =>{
fetch('https://fakestoreapi.com/products')
.then(res=>res.json())
.then(json=>{console.log(json);
setAllProducts(json);
console.log(AllProducts);
})
}
const search = () =>{
allproducts();
AllProducts.forEach(prod => {
if(Searchquery in prod){
resultfoundarray.push(prod.title);
}
});
}
return(
<>
<StrictMode>
<Head>
<link rel="stylesheet" href="./css/general.css"></link>
</Head>
<div>
<div className="searchbardiv">
<div>
<input type="text" placeholder="Search" onChange={e=>setSearchquery(e.target.value) ></input>
<span><button type="submit" onClick={ e => search()}>Search</button></span>
<div>
<select>
{resultfoundarray.map((prodtitle) => {
<option>
{prodtitle}
</option>
})}
</div>
</div>
</StrictMode>
</>
)
}
export default App;
The main problem is that the fetch is running in the background, updating AllProducts asynchronously. In your search function, you trigger the fetch and access AllProducts immediately after starting the fetch, before the results came back.
You should trigger the fetch when the component mounts:
useEffect(() => allProducts(), []);
And then react to state changes in AllProducts
useEffect(() => AllProducts?.forEach(prod => {
if (Searchquery in prod) {
resultfoundarray.push(prod.title);
}
}, [AllProducts]);
There are more minor issues (e.g. it would be better to use useState for managing resultfoundarray too), but I guess you'll figure that out.
The full code would look like this (just copied & pasted, there is no guarantee it works):
import React from "react";
const App = () => {
var resultfoundarray = [];
const [Searchquery, setSearchquery] = useState("");
const [AllProducts, setAllProducts] = useState([]);
const allproducts = () => {
fetch("https://fakestoreapi.com/products")
.then((res) => res.json())
.then((json) => {
console.log(json);
setAllProducts(json);
console.log(AllProducts);
});
};
// load all products when component mounts
useEffect(() => allproducts(), []);
// update results found after products are loaded
// or the when search query changes
useEffect(
() =>
AllProducts?.forEach((prod) => {
if (Searchquery in prod) {
resultfoundarray.push(prod.title);
}
}),
[AllProducts, SearchQuery]
);
return (
<>
<StrictMode>
<Head>
<link rel="stylesheet" href="./css/general.css"></link>
</Head>
<div className="searchbardiv">
<div>
<input
type="text"
placeholder="Search"
onChange={(e) => setSearchquery(e.target.value)}
></input>
<span>
<button type="submit" onClick={(e) => search()}>
Search
</button>
</span>
<div>
<select>
{resultfoundarray.map((prodtitle) => {
<option>{prodtitle}</option>;
})}{" "}
</select>
</div>
</div>
</div>
</StrictMode>
</>
);
};
export default App;
There are issues as others have pointed out. But the issue you are not seeing any result being displayed is because of this condition
if(Searchquery in prod){
You are searching by the product property name instead of the product title. To correct that, the condition should be
if (prod.title.includes(Searchquery ))

Using state and props between React components

This is my project for business card app.
I have a problem with using state and props between components.
Component tree looks like this.
Editor <- CardEditForm <- ImageFileInput
The url state is in the Editor component. There is a function to update state and give it as props to child components. When I upload an image on ImageFileInput component, url data goes up until the editor component and then using setUrl to url be updated. And then I gave url to CardEditorForm component.
The problem is this, In cardEditorForm, when it comes to using url props, I can't get the updated url. Only gets the initial state. I really need to get an updated url. I also tried to use setTimeout() to get the updated url. But it doesn't work either. What can I do?..
It's my first time to ask a question on stack overflow. Thank you for helping the newb.
Here is the code.
editor.jsx
const Editor = ({ cards, deleteCard, createOrUpdateCard }) => {
const [url, setUrl] = useState('');
const updateUrl = (src) => {
setUrl(src);
};
return (
<section className={styles.editor}>
<h1 className={styles.title}>Card Maker</h1>
{Object.keys(cards).map((key) => (
<CardEditForm
key={key}
card={cards[key]}
onDelete={deleteCard}
onUpdate={createOrUpdateCard}
updateUrl={updateUrl}
url={url}
/>
))}
<CardAddForm onAdd={createOrUpdateCard} updateUrl={updateUrl} url={url} />
</section>
);
};
card_edit_form.jsx
const CardEditForm = ({ card, onDelete, onUpdate, updateUrl, url }) => {
// ...
const changeUrl = () => {
setTimeout(() => {
const newCard = {
...card,
fileURL: url,
};
onUpdate(newCard);
}, 4000);
};
return (
<form className={styles.form}>
// ...
<div className={styles.fileInput}>
<ImageFileInput updateCard={changeUrl} updateUrl={updateUrl} />
</div>
// ...
</form>
);
};
export default CardEditForm;
image_file_input.jsx
const ImageFileInput = ({ updateUrl, updateCard }) => {
const [image, setImage] = useState('');
const upload = new Upload();
const onUpload = (e) => {
e.preventDefault();
upload.uploadImage(image).then((data) => updateUrl(data));
updateCard(e);
};
return (
<div>
<input type="file" onChange={(e) => setImage(e.target.files[0])} />
<button name="fileURL" onClick={onUpload}>
image
</button>
</div>
);
};

How to get single value in react-select dropdown

I am trying to create a multi-select dropdown indicator (the second element shown here) using react-select.
The purpose is to show all blog post categories on a blog page, and then to only render the blog posts that are selected in the dropdown indicator.
The tags are extracted from their posts based on a GraphQL query and stored in useState variables "tags" and "renderedPosts".
How do I simply get a value from the dropdown when a category is added or removed? Reading the react-select API, I get this:
getValue () => ReadonlyArray<...>
I don't know how to use that, VS Code simply screams when I try add an arrow function as an attribute in the Select.
I understand there is supposed to be a "value" by default on the Select but if I try to use it I get undefined.
I don't know if mapping to the default value is a problem or if it has anything to do with the ContextProvider (which was necessary). There are other attributes I cannot get to work either, like maxMenuHeight (which is supposed to take a number).
Allow me to share my code (you can probably ignore the useEffect):
export default function Blog({ data }) {
const { posts } = data.blog
const [tags, setTags] = useState([])
const [renderedPosts, setRenderedPosts] = useState([])
// Context necessary to render default options in react-select
const TagsContext = createContext();
// setting all tags (for dropdown-indicator) and rendered posts below the dropdown (initially these two will be the same)
useEffect(() => {
const arr = [];
data.blog.posts.map(post => {
post.frontmatter.tags.map(tag => {
if (!arr.some(index => index.value === tag)) {
arr.push({ value: tag, label: tag })
}
})
});
setTags([arr]);
setRenderedPosts([arr]);
}, []);
function changeRenderedPosts(value???){
setRenderedPosts(value???)
}
return (
<Layout>
<div>
<h1>My blog posts</h1>
<TagsContext.Provider value={{ tags }}>
<Select
defaultValue={tags.map(tag => tag) }
isMulti
name="tags"
options={tags}
className="basic-multi-select"
classNamePrefix="select"
// HOW DO I PASS THE VALUE OF THE ADDED/DELETED OPTION?
onChange={() => changeRenderedPosts(value???)}
maxMenuHeight= {1}
/>
</TagsContext.Provider>
// posts to be rendered based on renderedPosts value
{posts.map(post => {
EDIT: The closest I have now come to a solution is the following:
function changeRenderedTags(options){
console.log(options) //logs the remaining options
setRenderedTags(options) //blocks the change of the dropdown elements
}
return (
<Layout>
<div>
<h1>My blog posts</h1>
<TagsContext.Provider value={{ tags }}>
<Select
...
onChange={(tagOptions) => changeRenderedTags(tagOptions)}
I click to delete one option from the dropdown and I get the other two options in "tagOptions". But then if I try to change "renderedTags", the update of the state is blocked. I find this inexplicable as "setRenderedTags" has nothing to do with the rendering of the dropdown or its data!
with isMulti option true, you get array of options(here it's tags) from onChange callback. so I guess you could just set new tags and render filtered posts depending on the selected tags like below?
const renderedPosts = posts.filter(post => post.tags.some(tag => tags.includes(tag)))
...
onChange={selectedTags => {
setTags(selectedTags ? selectedTags.map(option => option.value) : [])
}}
...
I finally solved it - I don't know if it is the best solution but it really works! Sharing it here in case anyone else is in the exact same situation, or if you are just curious. Constructive criticism is welcome.
export default function Blog({ data }) {
const { posts } = data.blog
const [tags, setTags] = useState([])
const [renderedTags, setRenderedTags] = useState([])
const TagsContext = createContext();
useEffect(() => {
const arr = [];
posts.map(post => {
post.frontmatter.tags.map(tag => {
if (!arr.some(index => index.value === tag)) {
arr.push({ value: tag, label: tag })
}
})
});
setTags([...arr]);
}, [posts]);
useEffect(() => {
setRenderedTags([...tags]);
}, [tags])
return (
<Layout>
<div>
<h1>My blog posts</h1>
<TagsContext.Provider value={{ tags }}>
<Select
defaultValue={tags}
isMulti
name="tags"
options={tags}
className="basic-multi-select"
classNamePrefix="select"
onChange={(tagOptions) => setRenderedTags(tagOptions ? tagOptions.map(option => option) : [])}
value={renderedTags}
/>
</TagsContext.Provider>
{posts.map(post =>
(post.frontmatter.tags.some(i => renderedTags.find(j => j.value === i))) ?
<article key={post.id}>
<Link to={post.fields.slug}>
<h2>{post.frontmatter.title}</h2>
<p>{post.frontmatter.introduction}</p>
</Link>
<small>
{post.frontmatter.author}, {post.frontmatter.date}
</small>
<div dangerouslySetInnerHTML={{ __html: post.html }} />
</article>
: null
)}
</div>
</Layout>
)
}

How to set initial state in React to display nothing before fetching data on demand?

I'd like to display data fetched from API. The API url is based on user input. Once a user inputs and clicks submit, it is supposed to display the fetched data. But before user click submit, it should display nothing.
The problem I have is, when rendering the a user's data in lists in html, the map function doesn't exist on property{}, before user input anything. What should i set as the initial state?
The transformedData works fine. console.log(transformedData) prints
I'd like to display every title as a list. Example user input can be 0x147412d494731cbb91dbb5d7019464a536de04dc
import { useState } from "react";
// example user: 0x147412d494731cbb91dbb5d7019464a536de04dc
function App() {
const [data, setData] = useState({});
const [enteredWallet, setEnteredWallet] = useState("");
const [owner, setOwner] = useState("");
const walletChangeHandler = (event) => {
setEnteredWallet(event.target.value);
};
const submittedHandler = (event) => {
event.preventDefault();
setOwner(enteredWallet);
fetchNFTHandler();
};
function fetchNFTHandler() {
fetch(
`https://api.opensea.io/api/v1/assets?owner=${owner}&order_direction=desc&offset=0&limit=10`
)
.then((res) => {
return res.json();
})
.then((data) => {
const transformedData = data.assets.map((element, index) => {
return {
title: element.name,
id: index,
};
});
setData(transformedData);
console.log(transformedData);
});
}
return (
<div className="App">
<header className="App-header">
<h3>Show me assets in this wallet</h3>
<form onSubmit={submittedHandler}>
<input
placeholder="wallet address"
value={enteredWallet}
onChange={walletChangeHandler}
/>
<button>Submit</button>
</form>
<div>
console.log(data)
{/* {data.map((element) => (<li key={element.id}>
{element.title}</li>))}
*/}.
{/* here is the problem. before user input, this map function doesn't work */}
</div>
</header>
</div>
);
}
export default App;

Creating a history page with React Hooks

I am trying to create a history page with react hooks that keeps track of the users most recent searches they don't have to be persistent through refreshes only from this session.
my search component looks like this this is a simple app that does not need a UI just a simple navigation on the search page it will show the results and on the history page I would like to be able to show the previous searches from this session
I am trying to keep track of the debouncedTerm so I can display it in a new component
import React, { useState, useEffect } from 'react';
import axios from 'axios';
const Search = () => {
const history = [];
const [term, setTerm] = useState('');
const [debouncedTerm, setDebouncedTerm] = useState(term);
const [results, setResults] = useState([]);
useEffect(() => {
const timerId = setTimeout(() => {
setDebouncedTerm(term);
}, 1000);
return () => {
clearTimeout(timerId);
};
}, [term]);
useEffect(() => {
const search = async () => {
const { data } = await axios.get('http://hn.algolia.com/api/v1/search?', {
params: {
query: debouncedTerm,
},
});
setResults(data.hits);
};
if (debouncedTerm) {
search();
}
}, [debouncedTerm]);
const renderedResults = results.map((result) => {
return (
<div key={result.objectID} className="item">
<div className="right floated content">
<a className="ui button" href={result.url}>
Go
</a>
</div>
<div className="content">
<div className="header">{result.title}</div>
</div>
</div>
);
});
return (
<div>
<div className="ui form">
<div className="field">
<label>Hacker News Search:</label>
<input
value={term}
onChange={(e) => setTerm(e.target.value)}
className="input"
/>
</div>
</div>
<div className="ui celled list">{renderedResults}</div>
</div>
);
};
export default Search;
Your code looks like it's going in the right direction but you have a constant const history = []; and you must keep in mind that this will not work, because you will have a new constant re-declared in every render. You must use React setState like so:
const [history, setHistory] = useState([]);
You can read more about it in the React documentation.
edit:
In order to add new elements to the existing history you have to append it like this:
setHistory(oldHistory => [...oldHistory, newHistoryElement]);

Resources