I am having a problem filtering data from an API based on their regularPrice. So the error I am having is kinda stupid. It is 'regular price is not defined no-undef The error is showing on the line where I am passing values to the ContextPrivider. I might be blind. I would really appreciate some help. Thank you very much.
Book Context
import React, {useState, useEffect} from 'react'
import URL from '../utilis/URL';
const BookContext = React.createContext();
export default function BooksProvider({ children }) {
const [data, setData] = useState([])
const [filters, setFilters]= useState({
regularPrice:"",
length:""
})
/*fetching data */
const fetchData = async () => {
const response = await fetch(URL);
const result = await response.json();
console.log(result)
setData(result);
};
useEffect(()=>{
fetchData();
},[])
const updateFilters = e => {
const type = e.target.type;
const filter = e.target.name;
const value = e.target.value;
let filterValue;
if (type === "checkbox") {
filterValue = e.target.checked;
} else if (type === "radio") {
value === "all" ? (filterValue = value) : (filterValue = parseInt(value));
} else {
filterValue = value;
}
setFilters({ ...filters, [filter]: filterValue });
};
/* filtering price books */
React.useLayoutEffect(() => {
let newBooks = [...data].sort((a, b) => a.regularPrice - b.regularPrice);
const { regularPrice } = filters;
if (regularPrice !== "all") {
newBooks = newBooks.filter(item => {
if (regularPrice === 0) {
return item.regularPrice <10;
} else if (regularPrice === 10) {
return item.regularPrice > 10 && item.regularPrice < 20;
} else {
return item.regularPrice > 20;
}
});
}
}, [filters, data]);
return (
<BookContext.Provider value={{ data, filters, regularPrice, updateFilters, handleSelectCategory, setCurrentSelectedCategory, currentSelectedCategory }}>
{children}
</BookContext.Provider>
);
}
export {BookContext, BooksProvider}
Filters
import React, { useContext } from 'react'
import { BookContext } from '../../context/books'
const Filters = () => {
const {filters:{regularPrice, updateFilters}}= useContext(BookContext)
return (
<div>
<p>Regular Price</p>
<label>
<input
type="radio"
name="regularPrice"
id="all"
value="all"
checked={regularPrice === "all"}
onChange={updateFilters}
/>
all
</label>
<label>
<input
type="radio"
name="regularPrice"
value="0"
checked={regularPrice === 0}
onChange={updateFilters}
/>
$0 - $10
</label>
<label>
<input
type="radio"
name="regularPrice"
value="10"
checked={regularPrice === 10}
onChange={updateFilters}
/>
$10 - $20
</label>
<label>
<input
type="radio"
name="regularPrice"
value="20"
checked={regularPrice === 20}
onChange={updateFilters}
/>
Over $20
</label>
</div>
)
}
export default Filters
As the error says, regularPrice isn't defined in BooksProvider. You don't have to expose regularPrice since you can get it from filters but if you really want to, destructure it from filters.
const { regularPrice } = filters
return (
<BookContext.Provider
value={{
data,
filters,
regularPrice,
updateFilters,
handleSelectCategory,
setCurrentSelectedCategory,
currentSelectedCategory,
}}
>
{children}
</BookContext.Provider>
)
Related
I have a list of students that will display onto the web browser depending on what you filter by name/tag. If those filter fields become empty, the page re-fetches all the students from an API and displays them.
The tags are stored in an array using useState for each Student object.
Example Problem: After adding a tag to a student, then somehow filtering the students, and then finally clearing the filter fields, all the students will be displayed again but WITHOUT their tags.
Expected Outcome: I need the student to keep their tags, at least for a current session on the website.
Question: How can I solve this? Should I use localStorage? or a Database such as MongoDB? or something else?
Students.jsx
import { useState } from 'react';
import styles from "../views/Home.module.css";
import { v4 as uuidv4 } from 'uuid';
import AddIcon from '#mui/icons-material/Add';
import RemoveIcon from '#mui/icons-material/Remove';
const Students = ({student}) => {
const [isShown, setIsShown] = useState(true);
const [tags, setTags] = useState([]);
const findAverageGrade = arr => {
let sum = 0;
for (let i = 0; i < arr.length; i++) {
sum += parseInt(arr[i]);
}
return sum / arr.length;
}
const addTag = (event) => {
if (event.key === 'Enter') {
setTags([...tags, event.target.value])
event.target.value = "";
}
}
return (
<div key={student.email} className={styles.studentItem}>
<img className={styles.studentImage} src={student.pic} />
<div className={styles.studentInfoContainer}>
<div className={styles.studentHeader}>
<p className={styles.studentName}>{student.firstName.toUpperCase()} {student.lastName.toUpperCase()}</p>
<button className={styles.expandBtn} onClick={() => {
setIsShown(!isShown);
}}>
{ isShown ? <AddIcon className={styles.expandBtn} /> : <RemoveIcon className={styles.expandBtn} /> }
</button>
</div>
<ul className={styles.studentDetail}>
<li>Email: {student.email}</li>
<li>Company: {student.company}</li>
<li>Skill: {student.skill}</li>
<li>Average: {findAverageGrade(student.grades)}%</li>
{!isShown ? <div>
<table className={styles.gradesTable}>
<tbody>
{student.grades.map((grade) => (
<tr key={uuidv4()}>
<td>Test</td>
<td>{grade}%</td>
</tr>
))}
</tbody>
</table>
</div>
: null }
<div className={styles.tagOutput}>
{tags.map(tag => (<p className={styles.tag}>{tag}</p>))}
</div>
<input id="tag-input" className={styles.addTagInput} type="text" placeholder="Add a tag" onKeyPress={(e) => addTag(e)}/>
</ul>
</div>
</div>
)
}
export default Students;
Home.jsx
import axios from 'axios';
import { useState, useEffect } from 'react';
import Students from '../components/Students';
import styles from "./Home.module.css";
const Home = () => {
const [students, setStudents] = useState([]);
const [nameFilteredStudents, setNameFilteredStudents] = useState([]);
const [tagFilteredStudents, setTagFilteredStudents] = useState([]);
const fetchStudents = async () => {
const response = await axios.get(`https://api.hatchways.io/assessment/students`);
setStudents(response.data.students);
setNameFilteredStudents(response.data.students);
console.log(response.data.students);
}
const filterStudentName = async (searchName) => {
const searchNameFiltered = searchName.toLowerCase();
console.log(searchNameFiltered);
if (searchNameFiltered === "") {
fetchStudents();
return;
}
var newArray = await students.filter((student) => {
return student.firstName.toLowerCase().includes(searchNameFiltered)
|| student.lastName.toLowerCase().includes(searchNameFiltered);
})
await setNameFilteredStudents(newArray);
}
const filterStudentTag = async (searchTag) => {
const searchTagFiltered = searchTag.toLowerCase();
console.log(searchTagFiltered)
console.log(students.filter((student) => {
console.log(student);
}))
// var newArray = await students.filter((student) => {
// return student.firstName.toLowerCase().includes(searchNameFiltered)
// || student.lastName.toLowerCase().includes(searchNameFiltered);
// })
}
useEffect(() => {
fetchStudents();
}, [])
return(
<>
<div>
<input className={styles.searchInput} type="text" placeholder="Search by name" onChange={(event) => filterStudentName(event.target.value) }/>
<input className={styles.searchInput} type="text" placeholder="Search by tag" onChange={(event) => filterStudentTag(event.target.value) }/>
{nameFilteredStudents.map((student) => (
<Students key={student.id} student={student} />
))}
</div>
</>
)
}
export default Home;
Since you are passing the students prop to the child component, any time the students change the component will be re-rendered. Also since the filter is in the parent component, the child component will re-render because you are calling fetchStudents() in the filter function. You can toy with changing how you filter the students.
I'm working on a React Notes Application and my App.js contains all the necessary functions props which are passed down to several components.
As a result I'm doing prop drilling a lot where I'm passing down around 10-20 props/functions in the components where it isn't needed.
I tried using useContext Hook but I guess it doesn't work with callback functions in the value parameter.
App.js
const App = () => {
const [ notes, setNotes ] = useState([]);
const [ category, setCategory ] = useState(['Notes']);
const [ searchText, setSearchText ] = useState('');
const [ alert, setAlert ] = useState({
show:false,
msg:'',
type:''
});
const [isEditing, setIsEditing] = useState(false);
const [editId, setEditId] = useState(null);
useEffect(()=>{
keepTheme();
})
// retreive saved notes
useEffect(()=>{
const savedNotes = JSON.parse(localStorage.getItem('react-notes-data'));
if (savedNotes) {
setNotes(savedNotes)
}
}, []);
// save notes to local storage
useEffect(() => {
localStorage.setItem('react-notes-data', JSON.stringify(notes))
setNotesCopy([...notes]);
}, [notes]);
// save button will add new note
const addNote = (text) => {
const date = new Date();
const newNote = {
id: nanoid(),
text: text,
date: date.toLocaleDateString(),
category: category,
}
const newNotes = [...notes, newNote]
setNotes(newNotes);
}
const deleteNote = (id) => {
showAlert(true, 'Note deleted', 'warning');
const newNotes = notes.filter(note => note.id !== id);
setNotes(newNotes);
}
// hardcoded values for categories
const allCategories = ['Notes', 'Misc', 'Todo', 'Lecture Notes', 'Recipe'];
// copy notes for filtering through
const [notesCopy, setNotesCopy] = useState([...notes]);
const handleSidebar = (category) => {
setNotesCopy(category==='Notes'?[...notes]:
notes.filter(note=>note.category===category));
}
// function to call alert
const showAlert = (show=false, msg='', type='') => {
setAlert({show, msg, type});
}
return (
<div>
<div className="container">
<Sidebar
allCategories={allCategories}
handleSidebar={handleSidebar}
notesCopy={notesCopy}
key={notes.id}
/>
<Header notes={notes} alert={alert} removeAlert={showAlert} />
<Search handleSearchNote={setSearchText} />
<NotesList
notesCopy={notesCopy.filter(note=>
note.text.toLowerCase().includes(searchText) ||
note.category.toString().toLowerCase().includes(searchText)
)}
handleAddNote={addNote}
deleteNote={deleteNote}
category={category}
setCategory={setCategory}
allCategories={allCategories}
showAlert={showAlert}
notes={notes}
setNotes={setNotes}
editId={editId}
setEditId={setEditId}
isEditing={isEditing}
setIsEditing={setIsEditing}
/>
</div>
</div>
)
}
NotesList.js
const NotesList = (
{ notesCopy, handleAddNote, deleteNote, category, setCategory, showHideClassName, allCategories, showAlert, isEditing, setIsEditing, notes, setNotes, editId, setEditId }
) => {
const [ noteText, setNoteText ] = useState('');
const textareaRef = useRef();
// function to set edit notes
const editItem = (id) => {
const specificItem = notes.find(note=>note.id === id);
setNoteText(specificItem.text);
setIsEditing(true);
setEditId(id);
textareaRef.current.focus();
}
return (
<div key={allCategories} className="notes-list">
{notesCopy.map(note => {
return (
<Note
key={note.id}
{...note}
deleteNote={deleteNote}
category={note.category}
isEditing={isEditing}
editId={editId}
editItem={editItem}
/>)
})}
<AddNote
handleAddNote={handleAddNote}
category={category}
setCategory={setCategory}
showHideClassName={showHideClassName}
allCategories={allCategories}
showAlert={showAlert}
isEditing={isEditing}
setIsEditing={setIsEditing}
notes={notes}
setNotes={setNotes}
editId={editId}
setEditId={setEditId}
noteText={noteText}
setNoteText={setNoteText}
textareaRef={textareaRef}
/>
</div>
)
}
AddNote.js
const AddNote = ({ notes, setNotes, handleAddNote, category, setCategory, showHideClassName, allCategories, showAlert, isEditing, setIsEditing, editId, setEditId, noteText, setNoteText, textareaRef }) => {
const [ show, setShow ] = useState(false);
const [ modalText, setModalText ] = useState('');
const charCount = 200;
const handleChange = (event) => {
if (charCount - event.target.value.length >= 0) {
setNoteText(event.target.value);
}
}
const handleSaveClick = () => {
if (noteText.trim().length === 0) {
setModalText('Text cannot be blank!');
setShow(true);
}
if (category === '') {
setModalText('Please select a label');
setShow(true);
}
if (noteText.trim().length > 0 && category!=='') {
showAlert(true, 'Note added', 'success');
handleAddNote(noteText);
setNoteText('');
setShow(false);
}
if (noteText.trim().length > 0 && category!=='' && isEditing) {
setNotes(notes.map(note=>{
if (note.id === editId) {
return ({...note, text:noteText, category:category})
}
return note
}));
setEditId(null);
setIsEditing(false);
showAlert(true, 'Note Changed', 'success');
}
}
const handleCategory = ( event ) => {
let { value } = event.target;
setCategory(value);
}
showHideClassName = show ? "modal display-block" : "modal display-none";
return (
<div className="note new">
<textarea
cols="10"
rows="8"
className='placeholder-dark'
placeholder="Type to add a note.."
onChange={handleChange}
value={noteText}
autoFocus
ref={textareaRef}
>
</textarea>
<div className="note-footer">
<small
className='remaining'
style={{color:(charCount - noteText.length == 0) && '#c60000'}}>
{charCount - noteText.length} remaining</small>
<div className='select'>
<select
name={category}
className="select"
onChange={(e)=>handleCategory(e)}
required
title='Select a label for your note'
defaultValue="Notes"
>
<option value="Notes" disabled selected>Categories</option>
{allCategories.map(item => {
return <option key={item} value={item}>{item}</option>
})}
</select>
</div>
<button className='save' onClick={handleSaveClick} title='Save note'>
<h4>{isEditing ? 'Edit':'Save'}</h4>
</button>
</div>
{/* Modal */}
<main>
<div className={showHideClassName}>
<section className="modal-main">
<p className='modal-text'>{modalText}</p>
<button type="button" className='modal-close-btn'
onClick={()=>setShow(false)}><p>Close</p>
</button>
</section>
</div>
</main>
</div>
)
}
I want the functions passed from App.js to NotesList.js to be in AddNote.js without them being passed in NotesList.js basically minimizing the props destructuring in NotesList.js
Context API does work with function. What you need to do is pass your function to Provider inside value :
<MyContext.Provider value={{notes: notesData, handler: myFunction}} >
For example:
// notes-context.js
import React, { useContext, createContext } from 'react';
const Context = createContext({});
export const NotesProvider = ({children}) => {
const [notes, setNote] = useState([]);
const addNote = setNote(...); // your logic
const removeNote = setNote(...); // your logic
return (
<Context.Provider value={{notes, addNote, removeNote}}>
{children}
</Context.Provider>
)
}
export const useNotes = () => useContext(Context);
Add Provider to your App.js like so:
// App.js
import NoteProvider from './notes-context';
export default App = () => {
return (
<NoteProvider>
<div>... Your App</div>
</NoteProvider>
)
}
Then call UseNote in your NoteList.js to use the function:
// NoteList.js
import {useNotes} from './note-context.js';
export const NoteList = () => {
const {notes, addNotes, removeNotes} = useNotes();
// do your stuff. You can now use functions addNotes and removeNotes without passing them down the props
}
I've issues with the checkbox. I want to get the value and name field data in array format to do further processes.
Here's Checkbox:
<input type="checkbox" id="Honda" name="A1" value="Honda" onClick={CheckHandler} />
<label htmlFor="Honda" >Honda</label>
Now, I want to get the name and value field data in JSON or in an array
Like this:
{ name:"A1", value:"Honda" } //I want this.
So, I've coded like this:
import React, { Fragment, useState, useEffect } from "react";
export default function App() {
const [cars, setCars] = useState([]);
const CheckHandler = (e) => {
const value = e.target.value;
const name = e.target.name;
// setCars((prev) =>
// cars.includes(value)
// ? prev.filter((cur) => cur !== value)
// : [...prev, {[e.target.value]:`${e.target.name}`}]
// );
};
useEffect(() => {
console.log(cars);
}, [cars]);
return (
<Fragment>
<input type="checkbox" id="Honda" name="A1" value="Honda" onClick={CheckHandler}/>
<label htmlFor="Honda">Honda</label>
<br/>
<input type="checkbox" id="Mustang" name="A8" value="Mustang" onClick={CheckHandler}/>
<label htmlFor="Mustang">Mustang</label>
<br />
<input type="checkbox" id="Jeep" name="A12" value="Jeep" onClick={CheckHandler}/>
<label htmlFor="Jeep">Jeep</label>
<br />
</Fragment>
);
}
MY PROBLEM: whenever I Tick on checkbox It works fine, But when I unchecked its not returning sets from remaining items. IT's returning from all items. why ??
Any one Knows Answer ??
Sandbox https://codesandbox.io/s/late-water-piszn
Hi I fiddled a bit of your code and
const checkHandler = (e) => {
const value = e.target.value;
const name = e.target.name;
setCars((prev) =>
cars.find(ch => ch[value] === name)
? prev.filter((cur) => cur[value] !== name)
: [...prev, { [e.target.value]: `${e.target.name}` }]
);
};
update your method like this and it's working.
Here is the updated function I made for you answer
const CheckHandler = (e) => {
console.log(cars);
const value = e.target.value;
const name = e.target.name;
var found = false;
for (var i = 0; i < cars.length; i++) {
if (cars[i][value] === name) {
found = true;
break;
}
}
setCars((prev) =>
found
? prev.filter((cur) => cur[value] !== name)
: [...prev, { [value]: name }]
);
};
Here is the sandbox
Most tutorials I found on this subject were outdated. So here I am.
What I expect to do with this app is, input text into the field and filter the results based on what you input. Currently I'm stuck and I've been through so many array methods such as filter, indexOf etc. I'm sure I am overthinking the issue so I need help. Here's the code I have currently:
import React, { useState, useEffect } from "react";
import axios from "axios";
const ITEMS_API_URL = "https://restcountries.eu/rest/v2/all";
function Autocomplete() {
const [countryArr, setCountryArr] = useState([]);
useEffect(() => {
axios.get(ITEMS_API_URL).then((res) => {
setCountryArr(() => {
let arr = res.data;
arr.splice(10, arr.length);
return arr;
});
console.log(countryArr);
});
}, []);
const onChange = e => {
const inputValue = e.target.value
const filteredSuggestions = countryArr.find(arr => arr.name == inputValue)
setCountryArr(filteredSuggestions)
}
return (
<div className="wrapper">
<div className="control">
<input type="text" className="input" onChange={onChange} />
</div>
<div className="list is-hoverable" />
{countryArr.map((country) => {
return (
<ul key={country.numericCode}>
{country.name}
</ul>
)
})}
</div>
);
}
export default Autocomplete;
You should not change to actual data source (countryArr) otherwise it reset and store last filtered on that. so I create state variable filteredCountryArr for filter and setting up filtered valued on that.
import React, { useState, useEffect } from "react";
import axios from "axios";
const ITEMS_API_URL = "https://restcountries.eu/rest/v2/all";
function Autocomplete() {
const [countryArr, setCountryArr] = useState([]);
const [filteredCountryArr, setFilteredCountryArr] = useState([]);
useEffect(() => {
axios.get(ITEMS_API_URL).then(res => {
setCountryArr(() => {
let arr = res.data;
arr.splice(10, arr.length);
return arr;
});
console.log(countryArr);
});
}, []);
const onChange = e => {
const inputValue = e.target.value;
const filteredSuggestions = countryArr.find(arr => arr.name == inputValue);
setFilteredCountryArr(filteredSuggestions);
};
return (
<div className="wrapper">
<div className="control">
<input type="text" className="input" onChange={onChange} />
</div>
<div className="list is-hoverable" />
{filteredCountryArr && filteredCountryArr.length > 0 && filteredCountryArr.map(country => {
return <ul key={country.numericCode}>{country.name}</ul>;
})}
</div>
);
}
export default Autocomplete;
Good day, I am trying to implement filter on my fetched data using checkbox.I am first trying it out on gender. When I click on "male" check button, a list of male users is logged on my console, but nothing is displayed on my browser. Same applies to when I click on "female" or "others".
Again, how do I merge the genderFilter function with the search function?
Your views are highly appreciated.
My code is below:
import React, { useState, useEffect } from "react";
import './App.css';
import Profiles from "./components/Profiles";
import Footer from "./components/Footer";
import { USER_PER_PAGE } from "../src/utils/constants";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import { faSearch } from "#fortawesome/free-solid-svg-icons";
const App = () => {
const [error, setError] = useState(null);
const [isLoaded, setIsLoaded] = useState(false);
const [items, setItems] = useState([]);
const [page, setPage] = useState(1);
const [totalPages, setTotalPages] = useState(0);
const [query, setQuery] = useState("");
const search = (i) => {
// setTotalPages(i.length / USER_PER_PAGE)
return i.filter(res => res.FirstName.toLowerCase().indexOf(query) > -1 ||
res.LastName.toLowerCase().indexOf(query.toLowerCase()) > -1);
};
const genderFilter = (arr, gender) => {
if (gender === "male") {
console.log(arr.filter(res => res.Gender.toLowerCase() === "male"));
return arr.filter(res => res.Gender.toLowerCase() === "male");
} else if (gender === "female") {
console.log(arr.filter(res => res.Gender.toLowerCase() === "female"));
return arr.filter(res => res.Gender.toLowerCase() === "female");
} else if (gender === "others") {
console.log(arr.filter(res => res.Gender.toLowerCase() !== "male" && res.Gender.toLowerCase() !== "female"));
return arr.filter(res => res.Gender.toLowerCase() !== "male" && res.Gender.toLowerCase() !== "female"));
} else {
console.log(arr)
return arr;
}
};
// To select how many persons you can view per page
const startIndex = (page - 1) * USER_PER_PAGE;
// To filter with search bar. This working very well.
// const selectedProfiles = search(items).slice(startIndex, startIndex + USER_PER_PAGE);
// To filter with checkbox
const selectedProfiles = genderFilter(items).slice(startIndex, startIndex + USER_PER_PAGE);
useEffect(() => {
fetch("https://api.enye.tech/v1/challenge/records")
.then(res => res.json())
.then(
(result) => {
setIsLoaded(true);
setItems(result.records.profiles);
setTotalPages(Math.ceil(result.records.profiles.length / USER_PER_PAGE));
console.log(result.records.profiles.length)
},
(error) => {
setIsLoaded(true);
setError(error);
}
)
}, [])
const handleClick = (num) => {
setPage(num)
}
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <div>Loading...</div>;
} else {
return (
<div className="py-5 main">
<div className="topnav">
<div className="search-container">
<form action="#">
<input
type="search"
placeholder="Search.."
name="search"
value={query}
onChange={(e) => setQuery(e.target.value)} />
<button type="submit">
<FontAwesomeIcon icon={faSearch} />
</button>
</form>
</div>
<div className="filter">
<div className="form-check">
<span>
<input
className="form-check-input"
type="checkbox"
// checked="checked"
value="male"
onChange={() => genderFilter(items,"male")}
id="male" />
<label
className="form-check-label"
htmlFor="male">
Male
</label>
</span>
<span>
<input
className="form-check-input"
type="checkbox"
value="female"
onChange={() => genderFilter(items,"female")}
id="female" />
<label
className="form-check-label"
htmlFor="female">
Female
</label>
</span>
<span>
<input
className="form-check-input"
type="checkbox"
value="others"
onChange={() => genderFilter(items, "others")}
id="others" />
<label
className="form-check-label"
htmlFor="others">
Others
</label>
</span>
</div>
</div>
</div>
<div className="regions-grid py-5">
{selectedProfiles.map(i => (
<Profiles profile={i} key={i.Email} />
))}
</div>
<Footer
totalPages={totalPages}
handleClick={handleClick}
selectedProfiles={selectedProfiles}
/>
</div>
);
}
}
export default App;
Your component won't update if you don't set a new state. You should have something like:
const [filteredItems, setFilteredItems] = useState([]);
const [items, setItems] = useState([]);
const genderFilter = (gender) => {
if (gender === "male") {
const newFilter = items.filter(res => res.Gender.toLowerCase() === "male");
return [...filteredItems, ...newFilter]
} else if (gender === "female") {
const newFilter = items.filter(res => res.Gender.toLowerCase() === "female");
return [...filteredItems, ...newFilter]
} else if (gender === "others") {
const newFilter = items.filter(res => res.Gender.toLowerCase() !== "male" && res.Gender.toLowerCase() !== "female"));
return [...filteredItems, ...newFilter]
} else {
console.log(arr)
return arr;
}
};
//on your events:
onChange={() => setFilteredItems(genderFilter("male"))}