How to search field CARDS in React using React Hooks - javascript

I couldn't find how to make the field (input) work to filter the searches into cards:
can someone help me to operate the input field ?
**Notes.js**
import React, { useEffect, useState } from 'react'
import NoteCard from '../components/NoteCard'
import Masonry from 'react-masonry-css'
import Notess from '../components/CardList'
export default function Notes({details }) {
const [searchField, setSearchField] = useState("");
const [notes, setNotes] = useState([]);
const filteredNote = notes.filter(
note => {
return (
note.title.toLowerCase().includes(searchField.toLowerCase())
);});
const handleChange = e => {
setSearchField(e.target.value);
};
return (
<Container>
<input type='search' onChange = {handleChange} />
<Notess filter={filteredNote} />
</Container>
) }
someone can help me to operate the input field ?

Related

React, problem with my Todo list app, my search engine only finds notes on the page I'm on

Hello I am making an application to practice React, my notes app has a pagination which works perfectly, the problem is in the search engine, which only looks for notes from the page I'm on, for example, if I'm on page 2 and I look for a note on page 2, it shows it, however if the note is on a different page, it doesn't show it, it doesn't find it.
I know where the problem is but I'm not sure how to solve it, I'm a bit new to React and I was asking for your help.
I was able to do my pagination with the package react-paginate here is the documentation https://www.npmjs.com/package/react-paginate
My code:
Component App.js
import { useState, useEffect } from "react";
import { nanoid } from 'nanoid';
import './App.css';
import Search from "./components/Search";
import Header from "./components/Header";
import Pagination from "./components/Pagination";
const App = () => {
const [notes, setNotes] = useState([]);
const [searchText, setSearchText] = useState('');
const [darkMode, setDarkMode] = useState(false);
const [showNote, setShowNote] = useState(true); //eslint-disable-line
useEffect(() => {
const saveNotes = JSON.parse(localStorage.getItem('notes-data'));
if (saveNotes){
setNotes(saveNotes);
}
}, []);
useEffect(() => {
localStorage.setItem('notes-data', JSON.stringify(notes))
},[notes])
const addNote = (inputText, text) => {
const date = new Date();
const newNote = {
id: nanoid(),
title: inputText,
text: text,
date: date.toLocaleString()
}
const newNotes = [newNote, ...notes];
setNotes(newNotes)
}
const deleteNote = (id) => {
var response = window.confirm("Are you sure?");
if (response){
const notesUpdated = notes.filter((note) => note.id !== id)
setNotes(notesUpdated);
}
}
return (
<div className={darkMode ? 'dark-mode' : ''}>
<div className="container">
<Header
handleToggleTheme={setDarkMode}
/>
<Search
handleSearchNote={setSearchText}
setShowNote={setShowNote}
/>
<Pagination
data={notes}
handleAddNote={addNote}
handleDeleteNote={deleteNote}
searchText={searchText}
/>
</div>
</div>
)
}
export default App;
Component Pagination.js
import React, { useEffect, useState } from 'react'
import ReactPaginate from 'react-paginate';
import '../styles/Pagination.css';
import NoteList from './NoteList';
import { MdSkipPrevious, MdSkipNext } from 'react-icons/md';
const Pagination = (props) => {
const { data, searchText, handleAddNote, handleDeleteNote } = props;
// We start with an empty list of items.
const [currentItems, setCurrentItems] = useState([]);
const [pageCount, setPageCount] = useState(0);
// Here we use item offsets; we could also use page offsets
// following the API or data you're working with.
const [itemOffset, setItemOffset] = useState(0);
const itemsPerPage = 9;
useEffect(() => {
// Fetch items from another resources.
const endOffset = itemOffset + itemsPerPage;
console.log(`Loading items from ${itemOffset} to ${endOffset}`);
setCurrentItems(data.slice(itemOffset, endOffset));
setPageCount(Math.ceil(data.length / itemsPerPage));
}, [itemOffset, itemsPerPage, data]);
// Invoke when user click to request another page.
const handlePageClick = (event) => {
const newOffset = (event.selected * itemsPerPage) % data.length;
console.log(
`User requested page number ${event.selected}, which is offset ${newOffset}`
);
setItemOffset(newOffset);
};
return (
<>
<NoteList
notes={currentItems.filter((noteText) =>
noteText.title.toLowerCase().includes(searchText)
)}
handleAddNote={handleAddNote}
handleDeleteNote={handleDeleteNote}
/>
<div className="pagination-wrapper">
<ReactPaginate
breakLabel="..."
nextLabel={<MdSkipNext
className='icons'
/>}
onPageChange={handlePageClick}
pageRangeDisplayed={3}
pageCount={pageCount}
previousLabel={<MdSkipPrevious
className='icons'
/>}
renderOnZeroPageCount={null}
containerClassName="pagination"
pageLinkClassName="page-num"
previousLinkClassName="page-num"
nextLinkClassName="page-num"
activeLinkClassName="activee boxx"
/>
</div>
</>
);
}
export default Pagination;
Component NoteList.js
import React from 'react'
import Note from './Note'
import '../styles/NoteList.css'
import AddNote from './AddNote'
const NoteList = ({ notes, handleAddNote, handleDeleteNote }) => {
return (
<>
<div className="add-notes-wrapper">
<AddNote
handleAddNote={handleAddNote}
/>
</div>
<div className='notes-list'>
{notes.map((note =>
<Note
key={note.id}
id={note.id}
title={note.title}
text={note.text}
date={note.date}
handleDeleteNote={handleDeleteNote}
/>
))}
</div>
</>
)
}
export default NoteList;
Component Search.js
//import React, { useState } from 'react'
import {MdSearch, MdAdd} from 'react-icons/md'
import '../styles/Search.css'
const Search = ({ handleSearchNote, setShowNote }) => {
const handleShowAddNote = () => {
if (setShowNote){
let addNote = document.querySelector('.new');
addNote.classList.add('wobble-horizontal-top')
addNote.style.display='flex';
document.querySelector('.notes-list').style.display='none';
document.querySelector('.pagination').style.display='none';
}
}
return (
<div className='search'>
<div className="input-wrapper">
<MdSearch
className='icon search-icon'
/>
<input
type="text"
placeholder='What note are you looking for?'
onChange={(event) => handleSearchNote(event.target.value) }
/>
</div>
<div className="btn-wrapper-search">
<button
className='btn-addNote'
onClick={handleShowAddNote}>
Nueva Nota
</button>
<MdAdd
className='icon add-icon'
/>
</div>
</div>
)
}
export default Search
The problem is in the component Pagination.js because I'm filtering the notes on each page with the currentItems variable, if I did it with the data variable it would work, but then it would show all the notes, and I don't want that, I currently want to show 9 notes per page.
greetings and thanks in advance.
Edit:
#Newbie I'm doing what you said, but I don't know if you mean this, in my Pagination.js component I did:
useEffect(() => {
const filterNotes=data.filter((noteText) =>
noteText.title.toLowerCase().includes(searchText)
)
setItemOffset(0);
}, [data, searchText])
It doesn't work, do I have to pass a prop to my components additionally?
greetings.
As I suggested to you, search all the notes with searchText in your App.js and pass the results into the Pagination component and it will solve your problem.
Codesandbox: https://codesandbox.io/s/youthful-thompson-xugs0c
Edit
All changes are as per what we talked about in the email.
Codesandbox: https://codesandbox.io/s/green-fast-3k76wx
Search and pagination do not play well together, one of the common solutions is to jump to page 1 each time the filter term changes.
So use an useEffect on searchText to filter data and reset itemOffset to 0, then redo pagination as if the data changed.
The user will jump to page 1 at each keystroke of the search, then he can navigate pages (if there are more than one). This will lead to a less confusing UX.

React useEffect hook does not call after recoil atom updated

I'm using recoiljs as my state manager for my react project, and one of my components doesn't call it's useEffect when a recoil atom changes from another file. Here is my main component that reads from an atom.
import React, {useState, useEffect} from 'react'
import '../css/MeadDeadline.css'
import {getNearestDate} from '../chromeAPI/retrieveDeadlineJSON'
import DeadlineList from '../atoms/deadlinelist'
import {useRecoilValue} from 'recoil'
export default function MainDeadline() {
// get the date and the stuff from chrome storage
const [school, setSchool] = useState("");
const [date, setDate] = useState("");
let [deadlinelist, setDeadlineList] = useRecoilValue(DeadlineList);
useEffect(() => {
const nearest = getNearestDate(deadlinelist);
const len = nearest.length;
if (len === 0){
setSchool("No schools registered");
setDate("");
} else if (len === 1){
setSchool(nearest[0].school);
setDate(nearest[0].date);
} else {
// we need to render a lot of stuff
console.log("error");
}
}, [deadlinelist]);
return (
<>
<div className="MainDeadline">
<div className='school'>{school}</div>
<div classNmae='date'>{date}</div>
</div>
</>
)
}
Here is my atom file
import {atom} from 'recoil'
const DeadlineList = atom({
key: "deadlinelist",
default: []
});
export default DeadlineList;
and here is the form that I'm submitting in
import React, {useState} from 'react'
import '../css/InputForm.css'
import checkList from '../utils/checkList'
import checkDate from '../utils/checkDate'
import {storeNewDeadline} from '../chromeAPI/storeNewDeadline'
import {useRecoilState} from 'recoil'
import DeadlineList from '../atoms/deadlinelist'
import SchoolList from '../atoms/schoollist'
export default function InputForm () {
const [inputschool, setInputSchool] = useState('');
const [inputdate, setInputDate] = useState('');
const [invalidschool, setInvalidSchool] = useState(false);
const [invaliddate, setInvalidDate] = useState(false);
const [badschool, setBadSchool] = useState('');
const [baddate, setBadDate] = useState('');
const [schoollist, setSchoolList] = useRecoilState(SchoolList);
const [deadlinelist, setDeadlineList] = useRecoilState(DeadlineList);
const validateForm = () => {
// check to make sure its not in the list
const valschool = checkList(schoollist, inputschool);
if (!valschool){
setInvalidSchool(true);
setBadSchool(inputschool);
} else {
setInvalidSchool(false);
setBadSchool("");
}
// check to make sure the date hasnt been reached yet
const valdate = checkDate(inputdate);
if (!valdate){ // add MSIN1DAY becauase the day value is 0 indexed so conflicts with Date() and date input
setInvalidDate(true);
setBadDate(inputdate);
}
else {
setInvalidDate(false);
setBadDate("");
}
return !invalidschool && !invaliddate; // want both to be valid
}
const handleSubmit = async(event) => {
event.preventDefault();
// validate the form
if (validateForm()){
storeNewDeadline(inputschool, inputdate);
// change schoollist state
let slist = schoollist;
slist.push(inputschool);
setSchoolList(slist);
// change deadlinelist state
let dlist = deadlinelist;
dlist.push({
"school": inputschool,
"date": inputdate
});
setDeadlineList(dlist);
console.log(deadlinelist, schoollist);
}
}
const handleChange = (event, fieldname) => {
switch (fieldname) {
case "inputschool":
setInputSchool(event.target.value);
break;
case "inputdate":
setInputDate(event.target.value);
break;
default:
break;
}
}
return (
<form className='InputForm' onSubmit={handleSubmit}>
<h3>Enter New School</h3>
<div id='inputname' className='Inputer'>
<p>School Name</p>
<input
type='text'
onChange={e => {handleChange(e, 'inputschool')}}
value={inputschool}
required
/>
{invalidschool ? <p>{badschool} is already registered</p> : null}
</div>
<div id='inputdate' className='Inputer'>
<p>Deadline Date</p>
<input
type='date'
onChange={e => {handleChange(e, 'inputdate')}}
value={inputdate}
required
/>
{invaliddate ? <p>{baddate} is invalid</p> : null}
</div>
<div id='inputsubmit' className='Inputer'>
<p>Submit</p>
<input type='submit' required></input>
</div>
</form>
)
}
If you want to just see file for file the github is here
The main component is src/components/MainDeadline.jsx , src/atoms/deadlinelist , src/components/InputForm.jsx
My main problem is when the user inputs something in the form, it's supposed to update the state, but the main component doesn't update.
Please tell me if I can improve my code in any way this is my first react project.
When dealing with arrays in state hooks, you need to clone the array as you do the set function.
Also, instead of this:
let [deadlinelist, setDeadlineList] = useRecoilValue(DeadlineList);
I would do this:
const [deadlinelist, setDeadlineList] = useRecoilState(DeadlineList);

Why does state only update after invoking twice using React Hooks?

I'm stuck trying to understand why my state won't update until I change the value in the text input twice (calling the handleChange function). What am I doing wrong here?
import React, {useEffect, useState} from "react";
export default function Typeahead(props){
const {list} = props;
const [colorList] = useState(list.map(element => element.toLowerCase()));
const [color,setColor] = useState();
const [showResults, setShowResults]= useState(false);
const [results,setResults]= useState();
let handleChange = (e) =>{
setShowResults(true);
setColor(e.target.value.toLowerCase());
const match = (colorList) =>{
return colorList.startsWith(color,0);
};
const matches = colorList.filter(match);
setResults((matches));
console.log(results);
console.log(showResults);
};
useEffect(() => {
//setResults(list.map(elements => elements.toLowerCase()));
}, [results]);
return(
<div>
<input type= "text" onChange={handleChange}/>
{showResults ?
<div>
{results.map((options) => {
return (
<option key={options} value={options}> {options}</option>
)
})}
</div>
: null }
</div>
);
}

Initial value for useState isn't updating when using Virtual Keyboard

Beginner here. Trying to get react-simple-keyboard working with Gatsby & React.
I initialise my form with some state (firstName: "Johnn"). This should be the initial state. I want the user to be able to modify this name, and save the modified version in state.
I initialise my state here:
const [inputs, setInputs] = useState({
firstName: "Johnn"
})
When I click on the field and press a button on the virtual keyboard (a letter, say), it deletes the content of the whole field and puts the letter there, instead of adding the letter to whats already in there. Also: Clicking on the field and pressing backspace (on the react-simple-keyboard) does not do anything. Why is this?
import React, { useRef, useState, useContext, useEffect } from "react"
import styled from "styled-components"
import ReactDOM from "react-dom"
import Keyboard from "react-simple-keyboard"
import "react-simple-keyboard/build/css/index.css"
import Layout from "#components/layout"
import { useForm } from "react-hook-form"
import { Flex, Box } from "rebass/styled-components"
import Input from "#atoms/Input"
import {
GlobalDispatchContext,
GlobalStateContext,
} from "../context/GlobalContextProvider"
function App() {
const dispatch = useContext(GlobalDispatchContext)
const state = useContext(GlobalStateContext)
const [inputs, setInputs] = useState({
firstName: "Johnn",
// firstName: state.customers[state.currentCustomer].firstName,
})
const [layoutName, setLayoutName] = useState("default")
const [inputName, setInputName] = useState("default")
const [isShiftPressed, setShiftPressed] = useState(false)
const [isCaps, setCaps] = useState(false)
const [isKeyboardVisible, setKeyboardVisible] = useState(false)
const { register, handleSubmit, errors } = useForm()
const keyboard = useRef()
const onChangeAll = newInputs => {
/**
* Here we spread the inputs into a new object
* If we modify the same object, react will not trigger a re-render
*/
setInputs({ ...newInputs })
}
const handleShift = () => {
const newLayoutName = layoutName === "default" ? "shift" : "default"
setLayoutName(newLayoutName)
}
const onKeyPress = button => {
if (isShiftPressed === true && !isCaps) {
setShiftPressed(false)
handleShift()
}
if (button === "{lock}") {
setCaps(true)
}
if (button === "{shift}" || button === "{lock}") {
setShiftPressed(true)
handleShift()
}
}
const onChangeInput = event => {
const inputVal = event.target.value
setInputs({
...inputs,
[inputName]: inputVal,
})
keyboard.current.setInput(inputVal)
}
const getInputValue = inputName => {
return inputs[inputName] || ""
}
return (
<Layout>
<Flex flexDirection="column" style={{ height: "100%" }}>
<form onSubmit={handleSubmit(onSubmit)}>
<Input
id="firstName"
name="firstName"
value={getInputValue("firstName")}
onFocus={() => {
setInputName("firstName")
}}
placeholder={"First Name"}
onChange={onChangeInput}
/>
</form>
<Keyboard
keyboardRef={r => (keyboard.current = r)}
inputName={inputName}
layoutName={layoutName}
onChangeAll={onChangeAll}
onKeyPress={onKeyPress}
/>
</Flex>
</Layout>
)
}
export default App
You might need to use useEffect hook set the initial keyboard value, and on subsequent changes and also remove keyboard.current.setInput(inputVal).
const {firstName} = input;
useEffect(() => {
keyboard.current.setInput(firstName);
}, [firstName]);
This will make sure that the initial and subsequent changes of firstName is set in keyboard instance.
Code Sandbox: https://codesandbox.io/s/distracted-aryabhata-j3whs?file=/src/index.js

Can I consolidate multiple functions that set state based on callbacks from React child components?

I'm using React hooks to set state. There is a parent component that has multiple child components. The parent component has the state, and passes functions to the children components to update its state as callbacks.
The child components are the same, they just receive different function callbacks to update the related state in the parent.
My question is, can I write one handleChange function in the parent that will allow me to use this function callback structure to set multiple state values in the parent?
Parent component:
import React, { useState } from 'react'
import Control from './Control'
const Sort = () => {
const [controlUpValues, setControlUpValues] = useState([])
const [controlDownValues, setControlDownValues] = useState([])
const handleControlUpChange = values => {
setControlUpValues(values)
}
const handleControlDownChange = values => {
setControlDownValues(values)
}
return
<>
<Control
setControlItems={handleControlUpChange}
/>
<Control
setControlItems={handleControlDownChange}
/>
</>
)
}
export default Sort
Child component:
import React, { useState } from 'react'
import { Button, TextField } from '#material-ui/core'
function Control({ setControlItems }) {
const [controlInputValues, setControlInputValues] = useState([])
const [inputRef, setInputRef] = useState([])
const [inputValues, setInputValues] = useState([])
const handleValueChange = () => setInputValues(inputRef.value)
const addValuesToItems = () => {
setControlItems(inputValues)
}
return (
<div>
<TextField
inputRef={ref => setInputRef(ref)}
value={controlInputValues ? controlInputValues : ''}
onChange={handleValueChange}
/>
<Button
onClick={addValuesToItems}
>
Add
</Button>
</div>
)
}
export default Control
You can have an object containing the functions to update the state :
Parent
import React, { useState } from 'react'
import Control from './Control'
const Sort = ({ classes }) => {
const [controlBoostValues, setControlBoostValues] = useState([])
const [controlBuryValues, setControlBuryValues] = useState([])
const functions = {
boost: setControlBoostValues,
bury: setControlBuryValues
}
const handleChange = (key, values) => functions[key](values);
return
<>
<Control
setControlItems={handleChange}
/>
<Control
setControlItems={handleChange}
/>
</>
)
}
Child :
import React, { useState } from 'react'
import { Button, TextField } from '#material-ui/core'
function Control({ setControlItems }) {
const [controlInputValues, setControlInputValues] = useState([])
const [inputRef, setInputRef] = useState([])
const [inputValues, setInputValues] = useState([])
const handleValueChange = () => setInputValues(inputRef.value)
const addValuesToItems = () => {
setControlItems("boost" , inputValues)
}
return (
<div>
<TextField
inputRef={ref => setInputRef(ref)}
value={controlInputValues ? controlInputValues : ''}
onChange={handleValueChange}
/>
<Button
onClick={addValuesToItems}
>
Add
</Button>
</div>
)
}
export default Control

Categories

Resources