The contests array has a length of two, and I should see two divs with the "test" text in them, however nothing is displaying. I can log to the console before returning in the map, and it logs twice as expected yet nothing is returned. Any ideas?
return (
<Row>
<Col>
<h3>Open Contests</h3>
{contests.map((contest, i)=>{
return (
<div key={i}>
<p>test</p>
</div>
)
})}
</Col>
</Row>
)
Edit: here is the whole component
import React, {useState, useEffect} from 'react'
import {Row, Col} from 'react-bootstrap';
const OpenContests = () => {
const [contestRows, setContestRows] = useState([]);
const [contests, setContests] = useState([]);
//fetch open contests on initial render
useEffect(()=>{
const fetchOpenContests = async () => {
const response = await fetch('/contests/open');
const data = await response.json();
setContestRows(data)
}
fetchOpenContests();
},[])
const checkRow = (row) => {
let arr = contests;
let index = contests.findIndex(contest=> contest.contestID === row.intContestID);
if(index > -1){
let league = {
leagueID: row.intLeagueID,
leagueName: row.strLeagueName
}
arr[index].contestLeagues = [...arr[index].contestLeagues, league];
setContests(arr);
}else{
arr.push({
contestID: row.intContestID,
contestType: row.strContestType,
contestLeagues: [
{
leagueID: row.intLeagueID,
leagueName: row.strLeagueName
}
],
entry: row.decEntryFee,
bankroll: row.decInitialBankRoll,
prizepool: row.decPrizePool,
start: row.dtmStart,
end: row.dtmEnd,
minPlayers: null,
maxPlayers: null
})
}
}
//run when contest rows array changes
useEffect(()=>{
if(contestRows.length > 0){
contestRows.forEach(row=> {
checkRow(row)
})
}
},[contestRows])
return (
<Row>
<Col>
<h3>Open Contests</h3>
{contests.map((contest, i)=>{
return (
<div key={i}>
<p>test</p>
</div>
)
})}
</Col>
</Row>
)
}
export default OpenContests
Here is the contests array
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 having a big struggle with filtering an object of an array of objects by its ID in React. Let me explain:
The App is a Notes app, that stores every note you create with its Title, Text(name) and created date. The key is the ID.
Now I'm trying to create a popup modal every time I click on a note, which I managed to do ok, except for one thing: when the modal appears, it doesn't show only the selected note but all the notes list. I've tried with different array methods to filter the note I need, but didn't succeed.
This is the App.js file:
import React, { useState } from 'react';
import './App.css';
import Form from './components/Form';
import List from './components/List';
import { v4 as uuidv4 } from 'uuid';
import Modal from 'react-modal';
import ModalList from './components/ModalList';
Modal.setAppElement('#root');
function App() {
/*HOOKS */
const [list, setList] = useState([]);
const [modalList, setModalList] = useState([]);
//for modal:
let subtitle;
const [modalIsOpen, setIsOpen] = React.useState(false);
/*FUNCTIONS */
//add new notes
function handleAdd(title, name) {
if (name) {
const newList = list.concat({ title: title, name: name, id: uuidv4(), date: getCurrentDate() });
setList(newList);
console.log(newList);
const newModalList = list.concat({ title: title, name: name, id: uuidv4(), date: getCurrentDate() });
setModalList(newModalList);
}
else { alert("You should complete the notes field") }
}
//get the date for adding the note
function getCurrentDate() {
let newDate = new Date()
let date = newDate.getDate();
let month = newDate.getMonth() + 1;
let year = newDate.getFullYear();
let hours = newDate.getHours();
let minutes = newDate.getMinutes();
return `${month < 10 ? `0${month}` : `${month}`}/${date}/${year}, ${hours}:${minutes < 10 ? `0${minutes}` : `${minutes}`} hs.`
}
//deleting a note
function del(x) {
if (window.confirm("Do you really want to delete this item? The action is permanent.")) {
const newList = list.filter((item) => item.id !== x);
setList(newList);
}
}
//opening a modal
function openModal() {
setIsOpen(true);
}
//after opening a modal
function afterOpenModal() {
// references are now sync'd and can be accessed.
subtitle.style.color = '#f00';
}
//closing a modal
function closeModal() {
setIsOpen(false);
}
/*APP */
return (
<>
<div>
{/* MODAL */}
<Modal
isOpen={modalIsOpen}
onAfterOpen={afterOpenModal}
onRequestClose={closeModal}
style={customStyles}
contentLabel="Example Modal"
>
{modalList.map((item) => { return <ModalList key={item.id} item={item} quit={closeModal} /> })}
</Modal>
</div>
{/* FORM */}
<div className='App'>
<Form handleNew={handleAdd} />
</div>
{/* NOTES LIST */}
<div className='notes-list'>
{list.map((item) => { return <List key={item.id} item={item} quit={del} addModal={openModal} /> })}
</div>
</>
);
}
export default App;
And this is the ModalList.jsx file:
const ModalList = (props) => {
const { item, quit} = props;
/*LIST */
return (
<li ><button className='delete' onClick={()=>quit(item.id)}>x</button><p className='note-title'>{item.title}</p><p>{item.date}</p><p className='note-name'>{item.name}</p> </li>
);
}
export default ModalList;
I know I have to someway filter the object by its ID so that only appears what I clicked and not all the existing elements in the list, but I'm not finding the way.
Thanks a lot!
You are using Array.map here which is doing what it's supposed to do (listing the items), instead you should be using Array.filter which would return the ModalItem you need
{list.map((item) => { return <List key={item.id} item={item} quit={del} addModal={openModal} /> })}
openModal needs pass the item you clicked as a parameter and pass it to the callback.
Something like:
{list.map((item) => { return <List key={item.id} item={item} quit={del} addModal={() => openModal(item)} /> })}
Then openModal function needs to pass that parameter to the Modal component. To achieve that you can store it in your modalList for instance via setModalList([item])
I have connected to an api and have pulled some data into my project with the name of 'data'. This data is being rendered dynamically into a card component. I am now trying to arrange the order from highest price to lowest price on the click of a button with useState but cannot figure it out. Below is what i have so far:
import React, { useState } from "react";
import "./App.scss";
import { useQuery } from "#apollo/react-hooks";
import GET_PRODUCTS_IN_COLLECTION from "./gql/getCollection";
import ProductCard from "./components/ProductCard/ProductCard";
const App = (props) => {
const { data, loading, error } = useQuery(GET_PRODUCTS_IN_COLLECTION, {
variables: {
count: 10,
handle: "skateboard",
},
});
// console.log(data)
const [reversed, setReversed] = useState(false);
const [highLow, setHighLow] = useState(false);
const [lowHigh, setLowHigh] = useState(false);
const [remove, setRemove] = useState(false);
const reverseOrder = () => {
setReversed(!reversed);
};
const highToLow = () => {
setHighLow(!highLow);
};
const lowToHigh = () => {
setLowHigh(!lowHigh);
};
const removeLast = () => {
setRemove(!remove);
};
if (loading) {
// Data is still loading....
return <div className="App">Loading....</div>;
}
return (
<div className="App">
<header className="App-header"></header>
<main>
<div className="buttonGroup">
<button onClick={reverseOrder}>Reverse Product Order</button>
<button onClick={highToLow}>Price High to Low</button>
<button onClick={lowToHigh}>Price Low to High</button>
<button onClick={removeLast}>Display 9 products</button>
</div>
{/*
Your render components go here
*/}
<div className="ProductList">
{reversed
? data.collectionByHandle.products.edges
.slice()
.reverse()
.map((product) => {
return <ProductCard productData={product} />;
})
: highLow
? data.collectionByHandle.products.edges
.slice()
.sort((a,b) => (a.node.vendor - b.node.vendor))
.map((product) => {
return <ProductCard productData={product} />;
})
: lowHigh
? data.collectionByHandle.products.edges
.slice()
.map((product) => {
return <ProductCard productData={product} />;
})
.splice(1)
: remove
? data.collectionByHandle.products.edges
.slice()
.map((product) => {
return <ProductCard productData={product} />;
})
.splice(1)
: data.collectionByHandle.products.edges.map((product) => {
return <ProductCard productData={product} />;
})}
</div>
</main>
</div>
);
};
export default App;
image of array
You can change your code like the following example:
Some points to keep in mind :
Try to avoid if statment in JSX .
Put your events in seprated functions to make it easy for you to manage .
import React, { useState ,useEffect} from "react";
import "./App.scss";
import { useQuery } from "#apollo/react-hooks";
import GET_PRODUCTS_IN_COLLECTION from "./gql/getCollection";
import ProductCard from "./components/ProductCard/ProductCard";
const App = (props) => {
const [myData, setMyData] = useState(data);
useEffect (() => {
const { data, loading, error } = useQuery(GET_PRODUCTS_IN_COLLECTION,
{
variables: {
count: 10,
handle: "skateboard",
},
});
setMyData(data);
},[]);
const reverseOrder = () => {
let newData = myData.reverse();
setMyData([...newData]);
};
const highToLow = () => {
let newData = myData.sort((a, b) => b.node.vendor- a.node.vendor);
setMyData([...newData]);
};
const lowToHigh = () => {
let newData = myData.sort((a, b) => a.node.vendor- b.node.vendor);
setMyData([...newData]);
};
const removeLast = () => {
myData.splice(-1, 1);
setMyData([...myData]);
};
if (loading) {
// Data is still loading....
return <div className="App">Loading....</div>;
}
return (
<div className="App">
<header className="App-header"></header>
<main>
<div className="buttonGroup">
<button onClick={reverseOrder}>Reverse Product Order</button>
<button onClick={highToLow}>Price High to Low</button>
<button onClick={lowToHigh}>Price Low to High</button>
<button onClick={removeLast}>Display 9 products</button>
</div>
{
myData.map((product) => {
return <ProductCard productData={product} />;
});
}
</div>
</main>
</div>
);
};
export default App;
Assuming the values are alphanumerical javascript has built in function "sort" to do that. Even if they are not numerical there has to be a way to read their value that you can use!
Then its pretty straight forward (modified from w3schools):
const fruits = [2,1,"Banana", "Orange", "Apple", "Mango"];
fruits.sort();
will create array [1,2,Apple,Banana,Mango,Orange]
You should be able to do something along these lines in your program.
(just droping: if you want to reverse the order simply use reverse() method on array)
I don't know what your data looks like but this should work.
https://www.w3schools.com/js/js_array_sort.asp
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
}
So here I have search functionality. Everything works fine except when an Item has not been found in the array. I have tried something with objects.Keys but it is displaying it on the render not when the Book has not been found like it should. Should I use if statement or.
import React,{useState, useEffect, useRef, useContext}from 'react'
import {FaSearch} from 'react-icons/fa'
import {
Link, useHistory
} from "react-router-dom";
import { BookContext } from '../../context/books';
import SearchBooks from './SearchBooks';
const Search = () => {
const {data}= useContext(BookContext)
const [searchValue, setSearchValue] = React.useState('');
const history= useHistory()
const ref=useRef()
function filterBooks(book) {
console.log(book);
if (!searchValue.length ) return false;
return book.bookName?.toLowerCase().includes(searchValue.toLowerCase());
}
const handleSearch = (e) => {
if (ref.current && !ref.current.contains(e.target)) {
setSearchValue('')
}
};
useEffect(() => {
document.addEventListener('click', handleSearch);
return () => {
document.removeEventListener('click', handleSearch);
};
}, []);
return (
<div className='search__cont' ref={ref}>
{Object.keys(data).filter(filterBooks).length === 0 &&(
<div>
<h3>Book not found</h3>
</div>
)}
<SearchBooks searchValue={searchValue} setSearchValue={setSearchValue }/>
{Object.keys(data)
.map((key) => data[key])
.reduce((acc, curr) => acc.concat(curr), [])
.filter(filterBooks)
.map((book) => {
return (
<>
<div className='search__books'
onClick={() => {
history.push("/book/id", { book }); setSearchValue('')
}}
>
{" "}
{book.bookName}{" "}
</div>
</>
);
})}
</div>
)
}
export default Search
You're filtering the category names instead of the books (data is an object with category names as keys and books as values). You can use Object.values and Array.prototype.flat to get an array of all the books and then apply the filter.
const filteredBooks = Object.values(data).flat().filter(filterBooks)
const searchQueryPresent = searchValue.trim().length > 0
{
searchQueryPresent &&
(filteredBooks.length === 0 ? (
<div>No books found</div>
) : (
filteredBooks.map((book) => {
return <>{/* render books */}</>
})
))
}