I'm trying to create a search bar that filters out a set of data. The search function I made uses several states to filter results. When the search bar and results page are in the same class, the search function works but what I'm trying to do now is separate the search bar and display the search results on a separate page. Here's the state being set in the SearchBar class.
handleChange = (event) => {
this.setState({
names: event.target.value
})
}
The problem is I have no idea how to get the data stored in the SearchBar class to be displayed on the results page. Here's how I'm filtering the results on the results page.
const filteredData = data.filter(entry => (entry.name === (this.state.names))
This data is being filtered in the Search class but this.state.names is being stored in the SearchBar class. This SearchBar class is being displayed on my header where users can search for whatever they want and after they press search, the results page appears. So how can I take the data stored in the SearchBar class and use it in a different class?
UPDATE: I tried passing in the state to the Search class in the render function but that causes the entire page to just freeze.
render() {
return (
<Search names = {this.state.names} />
)
}
Not sure if I understood correctly but:
You can make a new component to store your data.
Then use this function (or similar) in onChange on that component
const filterData = (e) => {
const valueToCheck = e.target.value
let newArr = []
for(entry of data) {
// do the logic
//push the data you want into an array
newArr.push(entry)
}
setState(newArr)
}
SearchBar should call onSearchResults([...]) callback and then PageResult may accept those results, you need a component that orchestrate all.
const App = () =>{
const [results, setResults] = useState([]);
return (<>
<SearchBar onSearchChange={setResults}/>
{ results.length && <PageResult results={results}/> }
</>)
}
SearchBar will call props.onSearchChange(results) with the filtered data. App component will react to that change and send those results to PageResult component
Related
I'm writing a table component for my page in React.
I have a function loadData() that makes a request to an api. Using the api result to update the data state variable, using a new reference.
The problem here is that React doesn't trigger any re-render for the data variable.
const [data, setData] = useState([]);
const loadData = async () => {
try {
...
let response_json = await response.json();
setData(transformData(response_json.items));
...
}
const transformData = (data) => {
if (data === undefined || data === null) {
return [];
}
let new_data = [];
data.forEach((entry,index) => {
new_data.push(cloneElement(props.config.table_entry,{data:entry, key:index}, null));
});
return new_data;
}
I am using this code to change the table's page, making a request with parameters like pageNumber, pageSize, filters. So even with different data and different reference, still it doesn't trigger re-rendering.
This problem has challenged me for like one whole morning was that the data variable continued to updated on every request made but the webpage never re-rendered.
The answer lies here
data.forEach((entry,index) => {
new_data.push(cloneElement(props.config.table_entry,{data:entry, key:index}, null));
});
in the transformData function where it creates a new array of new data, BUT the key property of the component never changed because it was the index of its position in the array returned from the server.
Assigning the key to a unique id solved the problem.
I am getting data from firebase in react, but I am not able to pass on that data as the variables are defined internally. Following is what I am trying to do.
function getCommentNums(item){
const formRef = database.ref(
`/comments/${form_id}/${instanceId}/${item.id}`
);
console.log('formref = ', formRef)
formRef.on('value', async(snap)=>{
const commentsArr = (await snap.val()) ?? [];
console.log('commentArr=', commentsArr.length)
setCommentLen(commentsArr.length)
})
return someNum
}
then in main return statement getcommentnums is called inside accordion
{questions.map((item, index) => (
<Accordion
key={index}
id={
"question-" +
(noOfQuestionsPerPage * (page - 1) + 1 + index)
}
question={item}
questionNo={noOfQuestionsPerPage * (page - 1) + 1 + index}
//match vs item.id
commentNums = {getCommentNums(item)}
onBlur={handleClickSave}
onClickComments={onClickComments}
onChangeAnswer={onChangeAnswer}
answers={answers}
onClickLastValue={onClickLastValue}
disabled={form.is_submitted}
/>
))}
I am trying someNum to be commentsArr.length, which is supposed to be some integer. This function is going to be called in some child component to display value of commentNums. Multiple child components are going to be on one page and each would be calling above fn to get there respective commentNums.
I have tried using set state, but that just causes infinite loop.
Can someone show me how to send commentArr.length value forward?
While you call setCommentLen(commentsArr.length) to update the commentLen state variable, your rendering code still tries to render the return value of getCommentNums, which won't work.
The proper way to implement this is to:
Modify your loader function to no longer return any value, and only update the state.
function loadCommentCount(item){
const formRef = database.ref(`/comments/${form_id}/${instanceId}/${item.id}`);
formRef.on('value', async(snap)=>{
const commentsArr = (await snap.val()) ?? [];
setCommentLen(commentsArr.length)
})
}
Call this loader function outside of the rendering code, for example when the component is created, typically in a useState handler.
useState(() => {
questions.map((item, index) => (
loadCommentCount(item);
})
}, [questions])
Then render the value from the state.
commentNums = {commentCount}
Also see:
React Not Updating Render After SetState
How to return a value from Firebase to a react component?
Firebase response is too slow
My firebase realtime push key is changed when I save it in array in react native
React-native prevent functions from executing asynchronously?
Im building an app that users can search for certain pokemon cards by name but when running the app the div to show results is pre population random cards in the div when there is no user input in the search bar.
How can I stop data randomly populating until the user has input something into the search bar?
here is my code
import { useState, useEffect, useRef } from "react";
import CardOverview from "../components/CardOverview";
import styles from "./SearchCards.module.css";
import Select from "react-select";
import setOptions from "../data/sets";
import SpinnerOverride from "../utils/SpinnerOverride";
import SetOverview from "../components/SetOverview";
/**
* The `SearchCards` component represents a page in my app
* which allows the user to search for cards by their name
* and filter them by their set.
*/
export default function Search() {
// Track the `loading` state property of `SearchCards` page/component with the method `setLoading
// this is true by default.
const [loading, setLoading] = useState(false);
// Track the value of the `searchInput` state property with the method `setSearchInput` field
// An empty string by default
const [searchInput, setSearchInput] = useState("");
// Track the value of the selected `set`state property of cards with method `setSet`
// null by default
const [set, setSet] = useState(null);
// Track the `cards` state property that have been returned from the api with the `setCards` method
// An empty array by default
const [cards, setCards] = useState([]);
const isMounted = useRef(true);
// The base url for the api which will append extra info
// to based on the users input/selections.
const SEARCH_CARDS_BASE_URL = "https://api.pokemontcg.io/v2/cards";
useEffect(() => {
async function fetchCards() {
// update the `loading` state to `true`
setLoading(true);
// Transform the text in the search input field to lowercase
// and remove any leading and trailing whitespace
const cleanSearchInput = searchInput.toLowerCase().trim();
// Format the fetch url based on whether the search input
// and set filter values.
let url = `${SEARCH_CARDS_BASE_URL}?`;
if (set && set.value !== "all") {
// if `set` is not null, and the value is not 'all', then
// the user must have manually set the `set` using the set
// filter, so append it to the url
url += `q=set.id:${set.value}`;
if (cleanSearchInput === "") {
// If the user has not entered any input into the search
// input, display the first 50 cards from the api
} else {
// If the user has entered something in the search input, then
// search for all cards with that name
url += ` name:${cleanSearchInput}`;
isMounted.current = true;
}
} else {
// Same ideas for both situations listed above, just with slightly
// different string formatting due to the strange way the api wants
// developers to format query params.
if (cleanSearchInput === "") {
} else {
url += `q=name:${cleanSearchInput}`;
isMounted.current = true;
}
}
// Log out the final url which will be passed to the fetch
// function -- for debugging purposes only.
console.log(url);
// Fetch the data from the API
const resp = await fetch(url);
// Parse the response into json
const cards = await resp.json();
// Update the `cards` state to be an array of card objects
setCards(cards.data);
// Wait 4 seconds before setting the `loading` state to false.
// This gives react time to fully update the `cards` state.
console.log("before set time out");
setTimeout(() => {
setLoading(false);
console.log("during set timeout");
}, 3000);
console.log("after set timeout");
}
// Call the `fetchCards` function
fetchCards();
// if either the `searchInput` or `set` values change
// the `fetchCards` function will execute.
}, [searchInput, set]);
return (
<div>
<h1 className={styles.header}>Search Pokemon</h1>
<input
className={styles.searchInput}
type="text"
value={searchInput}
// When the text inside the search input field changes
// update the value of the `searchInput` state
onChange={(e) => setSearchInput(e.target.value)}
placeholder={"e.g. Pikachu"}
/>
<Select
className={styles.setFilter}
defaultValue={setOptions[0]}
// When the filter option in the filter dropdown changes
// update the the value of the `set` state
onChange={(newSet) => setSet(newSet)}
name="set"
// `setOptions` is an array of objects, where each object
// represents one set. See src/data/sets.js
options={setOptions}
classNamePrefix="select"
/>
{loading ? (
// If the `loading` state is true, display the loading spinner
<SpinnerOverride />
) : !loading && cards.length === 0 ? (
// If the `loading` state is false, and there are no cards
// in the array, then tell the user that no cards have been
// found
<SetOverview />
) : (
// If the app is not loading, and there are cards in the
// `cards` array, then render them out on the screen for the
// user to see.
<div className={styles.flexboxContainer}>
{cards.map((card, idx) => {
// Pass the data for each card returned from the
// api into the `CardOverview` component which will
// render the data (image, name, set, etc) to the screen
return (
<div key={idx}>
<CardOverview cardData={card} />
</div>
);
})}
</div>
)}
</div>
);
}
Just check whether the length of your search string is bigger than 0, if it is you render the cards div, otherwise, you don't.
Further Explanation as requested in comment:
<div className={styles.flexboxContainer}>
{searchInput.length > 0 ? cards.map((card, idx) => {
return (
<div key={idx}>
<CardOverview cardData={card} />
</div>
);
}) : null}
</div>
While #niklasbec's answer will work, I just wanted to add my cent.
you can inside fetchCards check for length of searchInput and return early if it's zero like this:
async function fetchCards(){
if(searchInput.length === 0){
return;
}
// rest of fetchCards
}
this way could make your JSX cleaner since you won't have nested ternary operators(loading ? <SpinnerOverride /> : searchInput.length === 0 ? cards.map(// rest of expression)
In the end, both ways achieve what you want, use the style you prefer.
I have a todo list fetching this endpoint
I have implemented some filters
Search for title
Toggling the todos completed and not
Multiple select filters for id
I am now implementing the reset filters, which is refetching the todos list, this the method resetFilters
const resetFilters = () => {
const fetchPosts = async () => {
setLoading(true);
const res = await axios.get(
"https://jsonplaceholder.typicode.com/todos/"
);
setIsCompleted(null);
setSearchValue("");
setTasks(res.data);
setFilteredData(res.data);
setLoading(false);
};
fetchPosts();
};
The reset filter method works fine except from not cancelling the text that i have for example put in the search input, switched the toggle back or removing the id number from the Multiple select in the filters column
How can i clear all these infos out in my method resetFilters ?
I have reproduced the demo here
Your input field is uncontrolled. I have edited your demo check it out here.
https://codesandbox.io/s/so-68247206-forked-vffwt?file=/src/App.js
I have added value prop to your SearchInput component so input will be controlled. Let me know if this helps
I'm trying to build a simple app that lets the user type a name of a movie in a search bar, and get a list of all the movies related to that name (from an external public API).
I have a problem with the actual state updating.
If a user will type "Star", the list will show just movies with "Sta". So if the user would like to see the actual list of "Star" movies, he'd need to type "Star " (with an extra char to update the previous state).
In other words, the search query is one char behind the State.
How should it be written in React Native?
state = {
query: "",
data: []
};
searchUpdate = e => {
let query = this.state.query;
this.setState({ query: e }, () => {
if (query.length > 2) {
this.searchQuery(query.toLowerCase());
}
});
};
searchQuery = async query => {
try {
const get = await fetch(`${API.URL}/?s=${query}&${API.KEY}`);
const get2 = await get.json();
const data = get2.Search; // .Search is to get the actual array from the json
this.setState({ data });
} catch (err) {
console.log(err);
}
};
You don't have to rely on state for the query, just get the value from the event in the change handler
searchUpdate = e => {
if(e.target.value.length > 2) {
this.searchQuery(e.target.value)
}
};
You could keep state updated as well if you need to in order to maintain the value of the input correctly, but you don't need it for the search.
However, to answer what you're problem is, you are getting the value of state.query from the previous state. The first line of your searchUpdate function is getting the value of your query from the current state, which doesn't yet contain the updated value that triggered the searchUpdate function.
I don't prefer to send api call every change of letters. You should send API just when user stop typing and this can achieved by debounce function from lodash
debounce-lodash
this is the best practise and best for user and server instead of sending 10 requests in long phases
the next thing You get the value from previous state you should do API call after changing state as
const changeStateQuery = query => {
this.setState({query}, () => {
//call api call after already changing state
})
}