React: useState/useEffect - How to collect 10 items in realtime - javascript

I am trying to get 10 usernames back when I type a keystroke.
For now it only gives me a Username back if the username is exactly in the jsonplaceholder list. So when i type "Karia" nothing comes back until I type "Karianne"
So for example if the Input gets the first letter "A", I want to give back the first 10 Items which are matching from the jsonplaceholder list. If I put in an "R", after that, so its "Ar" I want to get back the first 10 matching "Ar" usernames, like Aron, Ariel, Aria, etc.
Does someone have an Idea?
export const Search = () => {
const [user, setUser] = useState(null);
const [searchQuery, setSearchQuery] = useState("")
useEffect(() => {
if (searchQuery.length > 0) {
const fetchFunc = async () => {
const response = await fetch(`https://jsonplaceholder.typicode.com/users?username=${searchQuery}`)
const resJSON = await response.json();
setUser(resJSON[0]);
};
fetchFunc();
}
}, [searchQuery]);
return (
<Flex className={styles.search__container}>
<Flex className={styles.search__elements}>
<InputGroup className={styles.search__input}>
<InputRightElement
className={styles.search__inputElements}
children={<RiSearchLine className={styles.search__icon} />} />
<Input
className={styles.search__users}
type='search'
placeholder='Search'
onChange={event => setSearchQuery(event.target.value)}
/>
{user ? (
<div>
<h3>{user['name']}</h3>
<h3>{user['username']}</h3>
<h3>{user['email']}</h3>
</div>
) : (
<p>No user found</p>
)}
</InputGroup>
</Flex>
</Flex>
)
}

The main limitation here is on the API side. Your API should supply you with the similar names. If it doesn't, then you can't do much.

Maybe the API provides a way to retrieve users with partial names (but I doubt it would be the case).
Otherwise, you can retrieve all users once,and simply display the one that fits your requirements, every time you write something.
To sum up:
When your component is mounted (at the beginning), fetch all users, and store them. You will perform this API call only once.
Then, every time you write something, just take the 10 users that have a name that include what you wrote.

Ideally, this should be handled by the API, and you should be concerned about when to trigger or re-trigger the API call.
API gets you a list of names, you slice a set of 10 from the list of names and set it in users, and then simply map them in your UI.
Also, you could maybe further optimise the function debouncing the API call.

Related

Clearing a Material UI search filter TextField and returning to default

I am new to ReactJS and pairing it with Material UI is really causing me some roadblocks. I have created a reusable search filter component for my data tables and it worked exactly the way I wanted, but now I want to add a button to clear the field and show the unfiltered results, as well as return the InputSearch component back to its default state so it will display the label inside the field again, not up in the field’s border as these Material UI TextFields do then they are focused or have a current value. This is where I am hitting my roadblock. I have tried multiple solutions I found online, like using the inputRef/useCallback method to change the values, but it didn’t seem to work…or maybe I misunderstood and did it wrong. I was also recommended to put my search values to state. As happens with state my searches are now always one render behind (I.E. , results matching ‘US’ for ‘USA’ , ‘USA’ for ‘USAF’, etc…). Then when I run the handleFilterReset function to set the filter values back to an empty string, nothing happens. I just want my search filter to work instantly (like it did before I moved the value to state [commented out]) and be able to be cleared, resetting the table back to its default display.
Can someone please help me figure this out? Suggestions are appreciated, but code snippets are much more helpful since I am really new to React and especially Material UI.
dataTable.js
const [inputValue, setInputValue] = useState('')
const [searchFn, setSearchFn,] = useState({ fn: items => { return items; } });
// Searching Data
const handleSearch = e => {
setInputValue(e.target.value) // value displayed in input field
let query = (e.target.value).toString().toLowerCase();
setSearchFn({
fn: items => {
if (query === "")
return items;
else
return items.filter(x =>
(x.tankName !== null && x.tankName.toLowerCase().includes(query)) ||
(x.dimensions !== null && x.dimensions.toLowerCase().includes(query))
)
}
})
}
// Clearing Filters
const handleFilterReset = () => {
setInputValue('');
setSearchFn({fn: items => {return items;}})
};
// Search and filter Inputs
<div>
<InputSearch
value={inputValue}
onChange={handleSearch}
/>
<Button
text="Reset"
onClick={handleFilterReset}
/>
</div>
InputSearch.js
export default function InputSearch(props) {
const { inputRef, name, value, error=null, onChange, ...other } = props;
return (
<TextField
label="Search..."
name={name}
value={value}
onChange={onChange}
{...other}
{...(error && {error:true, helperText:error})}
>
</TextField>
)
}
You need to pass the value to InputSearch
Heres an example:
https://codesandbox.io/s/morning-brook-durbvd?file=/demo.tsx
React has a pretty good introduction on its site.
https://reactjs.org/docs/components-and-props.html
The code has been updated with a solution to this issue. I created a display value for the input that I passed to state, which was set to a blank string when the reset is pressed as well as passing an unfiltered data set.

React State Hook initialised to [] still shows type as undefined

Code:
const [input, setInput] = useState('');
const [studentList, setStudentList] = useState([]);
useEffect(() => {
console.log(studentList)
console.log(studentList.type)
}, [studentList]);
return (
<div id="add-students-input-div">
<input
type="text"
id='add-students-input'
value={input}
placeholder='Enter a student to the database'
onChange={(event) => {
setInput(event.target.value)
}}
/>
<div id="add-students-button" onClick={() => {
setStudentList([document.getElementById('add-students-input').value, ...studentList])
setInput('')
}}>
<p>Add</p>
</div>
</div>
)
Problem:
The print statement for studentList is returning the array but the print statement for studentList.type is undefined at all times, even after elements are added to the array.
How can I ensure that studentList.type returns Array.
studentList in your code will ever be an array, empty when you initialize the state. If you want to check if there is something in the array you have to use studentList.length
Altough previous contributors solved your problem by eliminating it in other place, I would like to answer this:
How can I ensure that studentList.type returns Array
If you want to make sure your variable is an array, you may use isArray() static method of Array:
Array.isArray(studentList) // returns true if array or false if not
As mentioned in the comments, arrays do not have a type property.
Your studentList state value is always an array; there is no need to check its type.
One thing you do appear to be doing incorrectly is updating studentList when you click your button (<div>). In short, you really shouldn't need to use DOM methods in React.
To update your array with the value from your input state, use this...
const handleClick = () => {
setStudentList((prev) => [input, ...prev]);
setInput("");
};
and
<div id="add-students-button" onClick={handleClick}>
<p>Add</p>
</div>
See useState - Functional updates for information on how I'm using setStudentList()

base64 encoded On-chain NFT not displaying on the Fronted

I'm trying to display all the minted tokens on the front page of my dapp, so I created an array in solidity and pushed the tokenId to it, then tried using a spread operator, and mapping it to the tokenURI's. Unfortunately, I haven't been able to get the image to display it.
for (let i = 1; i <= totalSupply; i++) { //this is listing an array of minted tokens
const tokenURI = await contract.methods.tokenURI(i - 1).call()
const imageURI = await tokenURI.image
// how should we handle the state on the front end
this.setState({
NFTz: [...this.state.NFTz, imageURI] // spread operator
}) // pretty sure the array could be done on the front end
}
console.log(this.state.imageURI)
console.log(this.state.NFTz)
The console returns:
index.js:1437 Warning: Each child in a list should have a unique "key" prop.
Check the render method of `App`. See fb.me/react-warning-keys for more information.
in div (at App.js:136)
in App (at src/index.js:7)
It's rendered it onto MDBcards with:
<div className="row textCenter">
{this.state.NFTz.map((key, imageURI) => {
return (
<div>
<div>
<MDBCard className="token img" style={{ maxWidth: '22rem' }}>
<MDBCardImage src={imageURI} position='top' height='250rem' style={{ marginRight: '4px' }} />
<MDBCardBody>
<MDBCardTitle></MDBCardTitle>
<MDBCardText></MDBCardText>
<MDBBtn href={imageURI}>Download</MDBBtn>
</MDBCardBody>
</MDBCard>
</div>
</div>
PS: Err, probably more efficient to create an array on the front end, instead of in solidity.
Thank you and God Bless

Why am I getting an empty array and incorrect data fetching in console?

I have two buttons, the movies and tvshows button. When I click on either I change the option to the opposite one as shown on the handleMovieClick and handleTVShowsClick methods. Movies and TVshows may have the same id, that is why the option is important for fetching.
I am printing to the console.
(1) array of movie/tv ids
(2) the option selected
(3) each individual object fetched based on the id and option
When I use the value to calculate the array of movie ids available the first time, I first get an empty array and then the expected result. That is the array of movie ids, option is movie and each individual promise objects is fulfilled.
Here's the kicker, when I click on the tvshows button, I am getting the following two things consoled.
(1) same movie array of ids not updated, option changes to tv, however I get a bunch of promises with "status_message: "The resource you requested could not be found" because I am basically trying to retrieve promises with movie ids, but tvshow option.
Then,
(2) Then the result is changed and I everything as expected. That is option stays the same, changed in above step, the array gets updated, and the individual promise objects are all fulfilled.
What is happening?
I get that it is happening because of the code in my JSX where I am making use of the array ids. I think I need to only call the array.map() part after the useEffect has run and the so array is updated but how to do this and why is useEffect not running the first time?
Here is the code.
const Results = () => {
const options = ['movie', 'tv'];
const { value } = useParams(); // from router
const [ page, setPage ] = useState(1);
const [ option, setOption ] = useState(options[0]);
const [ array, setArray ] = useState([]);
const handleMovieClick = () => {setOption('movie')}
const handleTVShowClick = () => {setOption('tv')}
useEffect(() => {
console.log('HERE');
fetchArrayByPage(value, page, option).then(res => setArray(res));
}, [value, page, option])
console.log(array);
console.log(option);
return (
<div className="results-container">
<div>
<ul className='categories'>
<button onClick={handleMovieClick}>Movies<span>{0}</span></button>
<button onClick={handleTVShowClick}>TV Shows<span>{0}</span></button>
</ul>
</div>
{
<div>
<div className='results-list'>
{array.map((arr, index) => {
console.log(fetchID(arr, option));
return <Card key={index} id={arr} option={option}></Card>})}
</div>
<div className='pages'>
</div>
</div>
}
</div>
);
}
useEffect's callback is fired after the render and fetchArrayByPage seems to be async. So clicking on "TV shows" results in:
option is changed to "tv" (array stays the same)
console.log(array); console.log(option);
render with new option and old array
console.log('HERE'); fetchArrayByPage(...)
some time passes
setArray(res)
render with new option and new array
You should call fetchArrayByPage from your handle*Click handlers:
const handleTVShowClick = () => {
fetchArrayByPage(value, page, "tv").then(res => {
setOption('tv');
setArray(res);
});
}

How to filter an array without losing data?

I have a function to filter an array of todos. The array is filtered correctly but when I type some thing else it removes other array elements and doesn't go back.
I've seen a few solutions, but they say "you have to write code in the render function" and I don't use this method because I render the array elsewhere with the help of props. filteredTodos has all local storage data.
How can I filter this data without losing data?
// *** Form.js ***
const filterTodos = (e) => {
const filtTod = filteredTodos.filter((todo) => {
return todo.text.toLowerCase().indexOf(e.target.value.toLowerCase()) !== -1
});
setFilteredTodos([...filtTod]);
};
// it's my render place *** TodoList.js ***
return (
<div className="todo-container">
<ul className="todo-list">
{filteredTodos.map((todo) => {
return (
<Todo
id={todo.id}
key={todo.id}
text={todo.text}
completed={todo.completed}
setTodos={setTodos}
todos={todos}
/>
)
})}
</ul>
</div>
);
const [inputText, setInputText] = useState("");
const [todos, setTodos] = useState([]); // Todos is all array items.
const [status, setStatus] = useState("all");
const [filteredTodos, setFilteredTodos] = useState([]); // the state is for my filter list
The idea with filtering data like an array of todos is to maintain a consistent "reference" or "source" array that represents all of the todos that exist in your app. This reference array may change when the user adds or removes a todo item, and may be persisted to the backend (or local storage, in your case) in some capacity or other. This array is the source of truth.
When a user enters a search/filtering term, filter on the reference/source array of all todos every time rather than the already-filtered result set.
In your code, todos represents the source array and filteredTodos represents the results of a single search. By using filteredTodos.filter, the results either keep diminishing after each filtering query, or there never were any results to work with to begin with -- either way, it's incorrect.
Think of filteredTodos as a temporary snapshot of todos used to show a subset view of todos to match the user's search term.
The fix is to use todos.filter rather than filteredTodos.filter so that you're always searching on the full data set.

Categories