My Balance States Throws Balance.map is not a function error? - javascript

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>
);
}

Related

Incorrect validation when trying to send data from additional inputs

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

Combining 2 arrays into their own arrays React

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; }

Array items only appearing once when filtering in React

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>
);
}

React: Fetch Data onSubmit, not on onChange

I got this code working pretty much how I want it. However, it's fetching & display data after each keystroke. I only want it to fetch once, when I hit submit.
Also, if there's anything i'm doing that's not "best practice" please let me know so I don't make silly mistakes in the future.
import React, { useEffect, useState } from "react";
export default function App() {
const [data, setData] = useState(null);
const [query, setQuery] = useState("");
useEffect(() => {
if (!query) return;
async function fetchData() {
const response = await fetch(
`https://www.omdbapi.com/?apikey=2e8b5857&s=${query}`
);
const data = await response.json();
const results = data.Search;
setData(results);
}
fetchData();
}, [query]);
const handleSubmit = (e) => {
e.preventDefault();
setQuery(query);
};
return (
<div
style={{
margin: 20,
}}
>
<form onSubmit={handleSubmit}>
<br />
<label>
Input Movie:{" "}
<input
type="text"
placeholder="ex. Harry Potter"
value={query}
onChange={(e) => {
setQuery(e.target.value);
}}
/>
</label>
<input type="submit" value="Submit" onClick={() => setQuery} />
</form>
{data &&
data.map((movie) => (
<div key={movie.imdbID}>
<h1>{movie.Title}</h1>
<h4>
{movie.Year} | {movie.imdbID}
</h4>
<img alt={movie.imdbID} src={`${movie.Poster}`} />
</div>
))}
</div>
);
}
Since you only want it after submit, you can skip the useEffect with [query] and just copy the same logic inside your handleSubmit like so :-
import React, { useEffect, useState } from "react";
export default function App() {
const [data, setData] = useState(null);
const [query, setQuery] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
if (!query) return;
async function fetchData() {
const response = await fetch(
`https://www.omdbapi.com/?apikey=2e8b5857&s=${query}`
);
const data = await response.json();
const results = data.Search;
setData(results);
}
fetchData();
};
return (
<div
style={{
margin: 20,
}}
>
<form onSubmit={handleSubmit}>
<br />
<label>
Input Movie:{" "}
<input
type="text"
placeholder="ex. Harry Potter"
value={query}
onChange={(e) => {
setQuery(e.target.value);
}}
/>
</label>
<input type="submit" value="Submit"/>
</form>
{data &&
data.map((movie) => (
<div key={movie.imdbID}>
<h1>{movie.Title}</h1>
<h4>
{movie.Year} | {movie.imdbID}
</h4>
<img alt={movie.imdbID} src={`${movie.Poster}`} />
</div>
))}
</div>
);
}
Here's the codesandbox :-
Pass the code that is inside the useEffect, that is, the fetch function, inside the submit function. leaving useEffect unused

Managing the state and working with inputs

I'm making a recipe box App, and I decided to start by my handleTitle and handleInput methods. I was able to pull that out, but now I want to make an array of objects (the objects contain the title and the description) and then I would map through this array and display the data.
but my handleSubmit function is not working the way I wanted. I want the user to be able to write several titles and descriptions, and those will keep being added to the recipes array in the state. Take a look at the code:
import React, { useState } from "react";
import "./styles.css";
import { Form } from "./components/containers/Form";
export default function App() {
const [title, setTitle] = useState("");
const [description, setDescription] = useState("");
const [recipe, setRecipe] = useState([]);
const handleTitle = e => {
setTitle(e.target.value);
};
const handleDescription = e => {
setDescription(e.target.value);
};
const handleSubmit = e => {
e.preventDefault();
if (title !== "" && description !== "") {
setRecipe(prevState => {
const data = { title: title, description: description };
return {
...prevState,
recipe: prevState.recipe.concat(data)
};
});
}
};
return (
<div className="App">
<Form
title={title}
description={description}
handleTitle={handleTitle}
handleDescription={handleDescription}
handleSubmit={handleSubmit}
/>
</div>
);
}
import React from "react";
export const Form = ({
handleTitle,
handleDescription,
handleSubmit,
title,
description
}) => {
return (
<div>
<form onSubmit={handleSubmit}>
<input
type="text"
onChange={handleTitle}
placeholder="title"
value={title}
/>
<input
type="text"
onChange={handleDescription}
placeholder="description"
value={description}
/>
<button>Add</button>
</form>
</div>
);
};
When you set the recipes, you're changing the primitive type of recipes state to an object. Instead you should just return a new array with the previous recipes and the new recipe.
I've attached a runnable example below:
const Form = ({
handleTitle,
handleDescription,
handleSubmit,
title,
description
}) => {
return (
<div>
<form onSubmit={handleSubmit}>
<input
type="text"
onChange={handleTitle}
placeholder="title"
value={title}
/>
<input
type="text"
onChange={handleDescription}
placeholder="description"
value={description}
/>
<button>Add</button>
</form>
</div>
);
};
function App() {
const [title, setTitle] = React.useState("");
const [description, setDescription] = React.useState("");
const [recipes, setRecipes] = React.useState([]);
const handleTitle = e => {
setTitle(e.target.value);
};
const handleDescription = e => {
setDescription(e.target.value);
};
const handleSubmit = e => {
e.preventDefault();
if (title !== "" && description !== "") {
setRecipes(prevState => {
const data = { title: title, description: description };
return [...prevState, data];
});
setTitle("");
setDescription("");
}
};
return (
<div className="App">
<Form
title={title}
description={description}
handleTitle={handleTitle}
handleDescription={handleDescription}
handleSubmit={handleSubmit}
/>
<h5>Recipes</h5>
{recipes.length === 0
? (
<div>No recipes</div>
)
: (
<ul>
{recipes.map(({ title, description }) => (
<li>
{title} : {description}
</li>
))}
</ul>
)}
</div>
);
}
ReactDOM.render(<App/>, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Categories

Resources