I have 2 arrays that I used the map() function to add to an array, but the problem I'm having is they are being grouped by name and grouped by review but I want them to return name + review independently of each other when I click submit on my form. Here's an example of what's happening:
I want it so David's review ("great movie") is separate from Daniel's review ("my favorite").
I've tried all sorts of things to no avail. Here is my code:
import { Button, Form, Input } from "reactstrap";
import Stars from "./stars";
export default function ReviewForm() {
const [reviewinput, setReviewInput] = useState("");
const [reviewarray, setReviewArray] = useState([]);
const [nameinput, setNameInput] = useState("");
const [namearray, setNameArray] = useState([])
const [starinput, setStarInput] = useState();
const [stararr, setStarArr] = useState(0)
const onChange = (e) => {
setReviewInput(e.target.value);
};
const onChangeName = (e) => {
setNameInput(e.target.value);
};
const onSubmit = (e) => {
e.preventDefault();
console.log('submitted');
if (reviewinput) {
reviewarray.push(reviewinput);
setReviewArray(reviewarray);
}
if (nameinput) {
namearray.push(nameinput);
setNameArray(namearray);
}
if (starinput) {
stararr.push(starinput);
setStarArr(stararr);
}
setReviewInput('');
setNameInput('');
setStarInput(0)
};
console.log(reviewarray);
return (
<div className="form-container">
<Stars setStar={setStarArr} />
<Form onSubmit={onSubmit}>
<Input
className="form-control" type="text"
placeholder="Enter your name"
value={nameinput}
onChange={onChangeName}
/>
<Input
className="form-control"
type="textarea"
placeholder="Enter your review"
value={reviewinput}
onChange={onChange}
/>
<br></br>
<Button type="submit" className="btn btn-primary">
Submit
</Button>
<br></br>
<br></br>
<div className="card-header border boorder-dark">
<h5>Reviews</h5>
</div>
<div className="card-body border border-secondary">
{namearray.map((name, i) => <p key={i}>{name}</p>)}
<br></br>
{reviewarray.map((review, i) => <p key={i}>{review}</p>)}
<p>I rate it this movie {stararr} stars!</p>
</div>
</Form>
</div>
);
}
// STAR COMPONENT \\
import React, { useState } from "react";
import { FaStar} from 'react-icons/fa'
const Stars = ({ setStar }) => {
const [rating, setRating] = useState(0);
const [hover, setHover] = useState(null);
const handleClick = (ratingValue) => {
setRating(ratingValue);
setStar(ratingValue);
};
return (
<div>
{[...Array(5)].map((star, i) => {
const ratingValue = i + 1;
return (
<label key={i}>
<input
type="radio"
name="rating"
value={ratingValue}
onClick={() => handleClick(ratingValue)}
/>
<FaStar
className="star"
color={ratingValue <= (hover || rating) ? "gold" : "lightgray"}
size={20}
onMouseEnter={() => setHover(ratingValue)}
onMouseLeave={() => setHover(null)}
/>
</label>
);
})}
<p>I rate this movie {rating + " stars"}</p>
</div>
);
};
export default Stars;```
It would be simpler to do this using an object instead of combining two arrays.
Make each review an object that contains a review, name, and your stars like so:
{
name: 'a',
review: 'good',
stars: 5
}
This way you could just use one array and push that object instead.
The reason your stars wasn't updating to 0 is because in your ./stars file you made a new stars state when you could have just re-used the one from your main component. Other than that, your code was fine.
updated code:
main file
import React, { useState } from "react";
import { Button, Form, Input } from "reactstrap";
import Stars from "./stars";
export default function ReviewForm() {
const [reviewinput, setReviewInput] = useState("");
const [reviewarray, setReviewArray] = useState([]);
const [nameinput, setNameInput] = useState("");
const [stararr, setStarArr] = useState(0);
const onChange = (e) => {
setReviewInput(e.target.value);
};
const onChangeName = (e) => {
setNameInput(e.target.value);
};
const onSubmit = (e) => {
e.preventDefault();
console.log("submitted");
const review = {};
if (reviewinput) {
review.review = reviewinput;
}
if (nameinput) {
review.name = nameinput;
}
review.stars = stararr;
setReviewArray([...reviewarray, review]);
setReviewInput("");
setNameInput("");
setStarArr(0);
const form = e.target
form.reset() /* to reset radio buttons to initial */
};
console.log(reviewarray);
return (
<div className="form-container">
<Form onSubmit={onSubmit}>
<Stars setStar={setStarArr} stararr={stararr} />
<Input
className="form-control"
type="text"
placeholder="Enter your name"
value={nameinput}
onChange={onChangeName}
required
/>
<Input
className="form-control"
type="textarea"
placeholder="Enter your review"
value={reviewinput}
onChange={onChange}
required
/>
<br></br>
<Button type="submit" className="btn btn-primary">
Submit
</Button>
<br></br>
<br></br>
<div className="card-header border boorder-dark">
<h5>Reviews</h5>
</div>
<div className="card-body border border-secondary">
<br></br>
{reviewarray.map(({ review, name, stars }, i) => (
<div key={i}>
<p>name: {name}</p>
<p>review: {review}</p>
<p>stars: {stars}</p>
</div>
))}
<p>I rate it this movie {stararr} stars!</p>
</div>
</Form>
</div>
);
}
star component
import React, { useState } from "react";
import { FaStar } from "react-icons/fa";
const Stars = ({ setStar, stararr }) => {
const [hover, setHover] = useState(null);
const handleClick = (ratingValue) => {
setStar(ratingValue);
};
return (
<div>
{[...Array(5)].map((star, i) => {
const stararr = i + 1;
return (
<label key={i}>
<input
type="radio"
name="rating"
value={stararr}
onClick={() => handleClick(stararr)}
/>
<FaStar
className="star"
color={stararr <= (hover || stararr) ? "gold" : "lightgray"}
size={20}
onMouseEnter={() => setHover(stararr)}
onMouseLeave={() => setHover(null)}
/>
</label>
);
})}
<p>I rate this movie {stararr + " stars"}</p>
</div>
);
};
export default Stars;
Edit: To incorporate the stars input as well
For your stars component I just replaced wherever you had ratings to your original stars value.
Like Bas bas told you, I think it's better to combine the name and the review in the same item in the reviews array.
I also wrote it a bit shorter and understandable:
import { Button, Form, Input } from "reactstrap";
import Stars from "./stars";
export default function ReviewForm() {
const [nameInput, setNameInput] = useState("");
const [reviewInput, setReviewInput] = useState("");
const [starInput, setStarInput] = useState(0);
const [reviews, setReviews] = useState([]);
const onSubmit = (e) => {
e.preventDefault();
if (!nameInput || !nameInput.length || !reviewInput || !reviewInput.length || isNaN(starInput)) {
return;
}
setReviews((prev) => [
...prev,
{
name: nameInput,
review: reviewInput,
rating: starInput
}
]);
clearForm();
};
const clearForm = () => {
setNameInput('');
setReviewInput('');
setStarInput(0);
};
return (
<div className="form-container">
<Stars setStar={setStarInput} />
<Form onSubmit={onSubmit}>
<Input
className="form-control" type="text"
placeholder="Enter your name"
value={nameInput}
onChange={(e) => setNameInput(e.target.value)}
/>
<Input
className="form-control"
type="textarea"
placeholder="Enter your review"
value={reviewInput}
onChange={(e) => setReviewInput(e.target.value)}
/>
<br></br>
<Button type="submit" className="btn btn-primary">
Submit
</Button>
<br></br>
<br></br>
<div className="card-header border boorder-dark">
<h5>Reviews</h5>
</div>
<div className="card-body border border-secondary">
{reviews.map(({ name, review }, i) =>
<p key={i}>{name} - {review}</p>
)}
</div>
</Form>
</div>
);
}
There are many options to achieve this. What you want to do is to zip() (at least Python developers would use this term), so that means you want to group together the first, second, third, ... element of the two given arrays. Then you could map() over the resulting array and display the values as you please.
For your example you could just use something like the following using map():
const names = ["Thomas", "Peter", "Tom", "Mark", "John"];
const reviews = [
"Well, this was shit.",
"Love me some sharks",
"Sharknadoooo!",
"It's a terrible joy.",
"I've seen a peanut stand, I've heard a rubber band, I've seen a needle wink it's eye, but I ain't never seen a Shark fly",
];
const result = names.map((name, idx) => ({ name: name, review: reviews[idx] }));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
In a more general sense with any number of arrays you could use a generator function although you could do it without one. But this is a quite convenient and simple way to achieve what you want which generates the values as you need them when using for ... of.
const names = ["Thomas", "Peter", "Tom", "Mark", "John"]
const reviews = ["Well, this was shit.", "Love me some sharks", "Sharknadoooo!", "It's a terrible joy.", "I've seen a peanut stand, I've heard a rubber band, I've seen a needle wink it's eye, but I ain't never seen a Shark fly"]
/**
* Zips any number of arrays. It will always zip() the largest array returning undefined for shorter arrays.
* #param {...Array<any>} arrays
*/
function* zip(...arrays){
const maxLength = arrays.reduce((max, curIterable) => curIterable.length > max ? curIterable.length: max, 0);
for (let i = 0; i < maxLength; i++) {
yield arrays.map(array => array[i]);
}
}
// put them in a array
const test = [...zip(names, reviews)]
console.log(test);
// or lazy generate the values
for (const [name, review] of zip(names, reviews)) {
console.log(`${name}: ${review}`);
}
.as-console-wrapper { max-height: 100% !important; top: 0; }
Related
Hello everyone and thank you for reading this! Here is my problem that i can't solve:
My application has the following functionality:
There are 2 inputs, then a button, when clicked, 2 more inputs appear and a button to send data from all inputs to the console, however, in the additional field, one input is required. This is where my problem arises: now, if I called additional inputs and filled in all the data, they are transferred to the console, if I didn’t fill in the required field, an error message goes to the console, BUT. I also need, in the event that I did NOT call additional inputs, the data of 2 basic inputs was transferred to the console. At the moment I can't figure it out.
import React, { useState } from "react";
import ReactDOM from "react-dom/client";
import produce from "immer";
const FunctionalBlock = ({
id,
idx,
isDeleted,
toggleBlockState,
additionalValue,
additionalTitle,
setNewBlock,
index,
}) => {
return (
<div
style={{
display: "flex",
maxWidth: "300px",
justifyContent: "space-between",
}}
>
{!isDeleted ? (
<React.Fragment>
<strong>{idx}</strong>
<input
type="text"
value={additionalTitle}
onChange={(e) => {
const additionalTitle = e.target.value;
setNewBlock((currentForm) =>
produce(currentForm, (v) => {
v[index].additionalTitle = additionalTitle;
})
);
}}
/>
<input
type="text"
value={additionalValue}
onChange={(e) => {
const additionalValue = e.target.value;
setNewBlock((currentForm) =>
produce(currentForm, (v) => {
v[index].additionalValue = additionalValue;
})
);
}}
/>
<button onClick={toggleBlockState}>now delete me</button>
</React.Fragment>
) : (
<button onClick={toggleBlockState}>REVIVE BLOCK</button>
)}
</div>
);
};
const Application = () => {
const [newBlock, setNewBlock] = useState([]);
const [firstInput, setFirstInput] = useState("");
const [secondInput, setSecondInput] = useState("");
const getNewBlock = (idx) => ({
id: Date.now(),
idx,
isDeleted: false,
additionalValue: "",
additionalTitle: "",
});
const toggleIsDeletedById = (id, block) => {
if (id !== block.id) return block;
return {
...block,
isDeleted: !block.isDeleted,
};
};
const createOnClick = () => {
const block = getNewBlock(newBlock.length + 1);
setNewBlock([...newBlock, block]);
};
const toggleBlockStateById = (id) => {
setNewBlock(newBlock.map((block) => toggleIsDeletedById(id, block)));
};
const showInputData = () => {
newBlock.map((item) => {
if (item.additionalTitle.length < 3) {
console.log("it is less than 3");
} else if (!item.additionalTitle && !item.additionalValue) {
console.log(firstInput, secondInput);
} else {
console.log(
firstInput,
secondInput,
item.additionalTitle,
item.additionalValue
);
}
});
};
return (
<div>
<div>
<input
type="text"
value={firstInput}
onChange={(e) => {
setFirstInput(e.target.value);
}}
/>
<input
type="text"
value={secondInput}
onChange={(e) => {
setSecondInput(e.target.value);
}}
/>
</div>
<div>
<button onClick={createOnClick}>ADD NEW INPUTS</button>
</div>
<div>
{newBlock.map((block, index) => (
<FunctionalBlock
key={index}
{...block}
toggleBlockState={() => toggleBlockStateById(block.id)}
setNewBlock={setNewBlock}
index={index}
/>
))}
</div>
<button onClick={showInputData}>send data</button>
</div>
);
};
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Application />);
Here is this code on sandbox for those who decided to help me. Thank you!
https://codesandbox.io/s/vigilant-booth-xnef6t
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.
In this React code what I'm trying to do is getting all items from the list that match with what type in the text input that's in the setManName function (also there is one in setModeName function). It works, but when I delete the text input and start over, the items disappear and will not appear anymore, not showing on the screen unless I reload the page again and start over again. I am using inludes() method, which works fine, but once I delete a letter or whole word and start over again it doesn't work. What's the problem here? Should I be using a different approach? Like another useEffect or something?
import React, { useState, useEffect } from "react";
import "./style.css";
export default function App() {
const [items, setItems] = useState([])
const [openFilterCt, setOpenFilterCt] = useState(false)
const [term1, setTerm1] = useState()
const [term2, setTerm2] = useState()
useEffect(() => {
fetch("https://private-anon-af560a53c6-carsapi1.apiary-mock.com/cars")
.then(res => res.json())
.then(data => {
setItems(data)
})
}, [])
function setManName(e) {
setTerm1(e.target.value);
let u = items.filter(item => {
**return item.make.includes(e.target.value)**
})
setItems(u)
}
function setModName(e) {
setTerm2(e.target.value);
let u = items.filter(item => {
**return item.model.includes(e.target.value)**
})
setItems(u)
}
function hi() {
setOpenFilterCt(!openFilterCt)
}
return (
<div>
<h1>React Search & Filter</h1>
<div>
<h3 onClick={hi}>Filter</h3>
<div className={openFilterCt ? "show" : "hide"}>
<label>
Name of manufacturer: <input type="text" value={term1} onChange={setManName} />
</label>
<br />
<label>
Name of model: <input type="text" value={term2} onChange={setModName} />
</label>
</div>
</div>
{items.slice(0, 50).map((a, index) => {
return (
<div key={index} style={{border: "1px solid black", margin: "10px", padding: "5px"}}>
<p>Manufacturer: {a.make[0].toUpperCase() + a.make.slice(1)}</p>
<p>Model: {a.model}</p>
</div>
)
})}
</div>
);
}
You are overwriting the items object, so any items not in a search will not show up even after deleting characters. This solution will dynamically filter the items, rather than removing them from the array.
Additionally, you should provide a default value to the term1 and term2 states. Without a default value, the inputs are switching from uncontrolled to controlled inputs, a practice that is discouraged in React.
See this Codesandbox.
import React, { useState, useEffect } from "react";
import "./style.css";
export default function App() {
const [items, setItems] = useState([]);
const [openFilterCt, setOpenFilterCt] = useState(false);
const [term1, setTerm1] = useState("");
const [term2, setTerm2] = useState("");
useEffect(() => {
fetch("https://private-anon-af560a53c6-carsapi1.apiary-mock.com/cars")
.then((res) => res.json())
.then((data) => {
setItems(data);
});
}, []);
function setManName(e) {
setTerm1(e.target.value);
}
function setModName(e) {
setTerm2(e.target.value);
}
function filterItems(item) {
if (term1 && !item.make.includes(term1)) return false;
if (term2 && !item.model.includes(term2)) return false;
return true;
}
function hi() {
setOpenFilterCt(!openFilterCt);
}
return (
<div>
<h1>React Search & Filter</h1>
<div>
<h3 onClick={hi}>Filter</h3>
<div className={openFilterCt ? "show" : "hide"}>
<label>
Name of manufacturer:{" "}
<input type="text" value={term1} onInput={setManName} />
</label>
<br />
<label>
Name of model:{" "}
<input type="text" value={term2} onInput={setModName} />
</label>
</div>
</div>
{items
.filter(filterItems)
.slice(0, 50)
.map((a, index) => {
return (
<div
key={index}
style={{
border: "1px solid black",
margin: "10px",
padding: "5px"
}}
>
<p>Manufacturer: {a.make[0].toUpperCase() + a.make.slice(1)}</p>
<p>Model: {a.model}</p>
</div>
);
})}
</div>
);
}
I have just fixed it for your model search, you can do the same for the manufacturer search. There may be more optimal ways, but this is something I worked it out.
What you need to do is preserve your original list. .filter() actually changes the original list, and when the response is blank, the original data is gone. So I just preserved the old data
const [orItem, setOrItems] = useState([]);
const prevList = orItem;
function setModName(e) {
setTerm2(e.target.value);
let u = prevList.filter((item) => {
return item.model.includes(e.target.value);
});
setItems(u);
}
You can see you code in action here for model search:
https://codesandbox.io/s/elegant-mcclintock-46ncr?file=/src/App.js
First, you should not modify the original array items. You need to create another one(another state variable) filteredItems so you can reset to the original state, also I believe there is another error here item.model.includes(e.target.value), it will always return false if the text is empty.
function setManName(e) {
setTerm1(e.target.value);
if(e.target.value){
let u = items.filter(item => {
return item.make.includes(e.target.value)
})
setFilteredItems(u)
}else{
setItems(items)
}
}
Also useEffect hook should be something like this:
useEffect(() => {
fetch("https://private-anon-af560a53c6-carsapi1.apiary-mock.com/cars")
.then(res => res.json())
.then(data => {
setItems(data)
setFilteredItems(data)
})
}, [])
And make sure to map over filteredItems.
The problem here is that you are resetting the value that you receive from the given URL. You should be maintaining a separate list for visibility you could go by approach 1. given below. Its the best I could do without modifying lots of your code however this is typically an over/mis use of states. Remember React is called react because of its amazing capability to react when the state changes.
The Approach2 realizes just that, you can be smart with the filter and alter it as you need. your search to behave.
// Approach 1
import React, { useState, useEffect } from "react";
// import "./style.css";
export default function App() {
const [items, setItems] = useState([]);
const [visibleItems, setVisibleItems] = useState([]);
const [openFilterCt, setOpenFilterCt] = useState(false)
const [term1, setTerm1] = useState()
const [term2, setTerm2] = useState()
useEffect(() => {
fetch("https://private-anon-af560a53c6-carsapi1.apiary-mock.com/cars")
.then(res => res.json())
.then(data => {
setItems(data);
setVisibleItems(data);
})
}, [])
function setManName(e) {
// setTerm1(e.target.value);
let u = items.filter(item => {
if(e.target.value){
return item.make.includes(e.target.value)
}
return true;
})
setVisibleItems(u)
}
function setModName(e) {
// setTerm2(e.target.value);
let u = items.filter(item => {
return item.model.includes(e.target.value)
})
setVisibleItems(u)
}
function hi() {
setOpenFilterCt(!openFilterCt)
}
return (
<div>
<h1>React Search & Filter</h1>
<div>
<h3 onClick={hi}>Filter</h3>
<div className={openFilterCt ? "show" : "hide"}>
<label>
Name of manufacturer: <input type="text" value={term1} onChange={setManName} />
</label>
<br />
<label>
Name of model: <input type="text" value={term2} onChange={setModName} />
</label>
</div>
</div>
{visibleItems.slice(0, 50).map((a, index) => {
return (
<div key={index} style={{border: "1px solid black", margin: "10px", padding: "5px"}}>
<p>Manufacturer: {a.make[0].toUpperCase() + a.make.slice(1)}</p>
<p>Model: {a.model}</p>
</div>
)
})}
</div>
);
}
// Approach2
import React, { useState, useEffect } from "react";
// import "./style.css";
export default function App() {
const [items, setItems] = useState([]);
const [openFilterCt, setOpenFilterCt] = useState(false);
const [term1, setTerm1] = useState("");
const [term2, setTerm2] = useState("");
useEffect(() => {
fetch("https://private-anon-af560a53c6-carsapi1.apiary-mock.com/cars")
.then((res) => res.json())
.then((data) => {
setItems(data);
});
}, []);
function setManName(e) {
setTerm1(e.target.value);
}
function setModName(e) {
setTerm2(e.target.value);
}
function hi() {
setOpenFilterCt(!openFilterCt);
}
return (
<div>
<h1>React Search & Filter</h1>
<div>
<h3 onClick={hi}>Filter</h3>
<div className={openFilterCt ? "show" : "hide"}>
<label>
Name of manufacturer:{" "}
<input type="text" value={term1} onChange={setManName} />
</label>
<br />
<label>
Name of model:{" "}
<input type="text" value={term2} onChange={setModName} />
</label>
</div>
</div>
{items
.filter((item) => {
return item.make.includes(term1) && item.model.includes(term2);
})
.slice(0, 50)
.map((a, index) => {
return (
<div
key={index}
style={{
border: "1px solid black",
margin: "10px",
padding: "5px"
}}
>
<p>Manufacturer: {a.make[0].toUpperCase() + a.make.slice(1)}</p>
<p>Model: {a.model}</p>
</div>
);
})}
</div>
);
}
I am looping over a list of students and displaying them on the page. Each student has the ability to add a tag to their container, but the problem I am having with my solution is that this is adding to every single students tags, instead of only the student I want it added to. How can i make this add tag function unique to each student?
import logo from "./logo.svg";
import React, { useState, useEffect } from "react";
import "./App.css";
function App() {
const [student, setStudent] = useState([]);
const [studentFilter, setFilter] = useState("");
const [tags, setTags] = useState([]);
useEffect(() => {
fetch(api)
.then((response) => response.json())
.then((result) => {
setStudent(result.students);
})
.catch((err) => {
console.error(err);
});
}, [setStudent]);
const addTag = (e) => {
if (e.key == "Enter") {
setTags([...tags, e.target.value]);
}
};
return (
<>
<input
id="name-input"
placeholder="Search by name"
type="text"
className="filter-students"
onChange={(e) => setFilter(e.target.value)}
/>
{student
.filter(
(name) =>
name.firstName.toLowerCase().includes(studentFilter) ||
name.lastName.toLowerCase().includes(studentFilter)
)
.map((students) => {
let total = 0;
for (let i = 0; i < students.grades.length; i++) {
total += parseInt(students.grades[i]);
}
const average = total / students.grades.length;
return (
<div className="student-container">
<img className="student-img" src={students.pic} />
<div className="student-column">
<p className="student-item">
{" "}
{students.firstName} {students.lastName}
</p>
<p className="student-item">Email: {students.email}</p>
<p className="student-item">Company: {students.company}</p>
<p className="student-item">Skill: {students.skill}</p>
<p className="student-item">Average: {average}%</p>
{tags.map((tag) => {
return <p className="student-tags">{tag}</p>;
})}
<input
onKeyDown={addTag}
className="student-tag"
type="text"
placeholder="Add a tag"
/>
</div>
</div>
<button onClick={displayScores} className="expand-btn">
+
</button>
</div>
);
})}
</>
);
}
Because you just store tags globally and display them all for every students.
Here is a way, not a solution, I didn't test anything. Just a proposition of how to do it. I just hope student have id.
Change the add tag to tell on which student it must be added. Tags are now object, with studentId and value.
const addTag = (studentId, e) => {
if (e.key == "Enter") {
setTags([...tags, {studentId, value:e.target.value}]);
}
};
Change the click handler
<input
onKeyDown={addTag.bind(this, student.id)}
className="student-tag"
type="text"
placeholder="Add a tag"
/>
Change the display method
{tags.filter(t => t.studentId === student.id).map((tag) => {
return <p className="student-tags">{tag.value}</p>;
})}
I'm making a simple React app that keeps track of expenses, income, and balances. I'm using a state hook called Balance that is the sum of all the input state. The issue is React throws a Balance.map is not a function. So, I'm unable to show what the total would be when the user enters all their input.
Here's the full code:
export default function App() {
const [Balance, setBalance] = useState([]);
const [Income, setIncome] = useState(0);
const [Expense, setExpense] = useState(0);
const [Input, setInput] = useState([]);
console.log(typeof Balance);
const handleChange = (e) => {
setInput(e.target.value);
};
const handleClick = (e) => {
e.preventDefault();
if (Input > 0) {
setIncome(Input);
} else {
setExpense(Input);
}
let val = parseInt(Input);
setBalance(val, ...Balance);
//console.log(...Balance);
setInput("");
};
return (
<div style={{ textAlign: "center", marginTop: "150px" }}>
<form>
<label htmlFor="input"> Please enter value: </label>
<input
name="input"
type="number"
value={Input}
onChange={handleChange}
/>
<button onClick={handleClick}> Submit</button>
</form>
<br />
<h3> My Balance:</h3>
<p>
{Balance.map((i) => {
return i;
})}
</p>
<h3> My Income: {Income}</h3>
<h3> My Expense: {Expense}</h3>
</div>
);
}
I know that the Balance state ,despite it being set as an array, is still an object. I'm assuming the error is coming from there? If so, should setBalance(...Input) work?
as users say in the comments, the problem is the way you update the value of Balance, Here I do some changes in this places:
let val = parseInt(Input, 10); // Here I add the base of parseInt
setBalance([...Balance, val]) Here I let the value as an array in the setBalance
The complete code is:
import React, {useState} from "react";
import "./styles.css";
export default function App() {
const [Balance, setBalance] = useState([]);
const [Income, setIncome] = useState(0);
const [Expense, setExpense] = useState(0);
const [Input, setInput] = useState([]);
console.log(typeof Balance);
const handleChange = (e) => {
setInput(e.target.value);
};
const handleClick = (e) => {
e.preventDefault();
if (Input > 0) {
setIncome(Input);
} else {
setExpense(Input);
}
let val = parseInt(Input, 10);
setBalance([...Balance, val]);
//console.log(...Balance);
setInput("");
};
return (
<div style={{ textAlign: "center", marginTop: "150px" }}>
<form>
<label htmlFor="input"> Please enter value: </label>
<input
name="input"
type="number"
value={Input}
onChange={handleChange}
/>
<button onClick={handleClick}> Submit</button>
</form>
<br />
<h3> My Balance:</h3>
<p>
{Balance.map((i) => {
return i;
})}
</p>
<h3> My Income: {Income}</h3>
<h3> My Expense: {Expense}</h3>
</div>
);
}