I am trying to capitalize the product name after fetching if from a products list using a button.
Basically, a button fetches the data. Which gives us the product name as a heading with a CLEAR and CAPITALIZE button with each item.
Clear button removes the item from the list. Implemented.
Capitalize button Capitalizes the product name.
Not able to figure out how to implement the capitalize function.
CODE::
capHandler function implements the functionality.
import React, { useRef, useState } from 'react';
import axios from 'axios';
function App(){
const url = 'https://course-api.com/react-store-products';
const [products, setProducts] = useState([]);
const productNameRef = useRef();
const fetchData = async () => {
const { data } = await axios.get(url);
setProducts(data);
};
const clearProducts = () => {
setProducts([]);
};
const clearSingleProduct = (productID) => {
const filteredArray = products.filter((item) => item.id != productID);
setProducts(filteredArray);
};
function capitalizeFirstLetter(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
function capHandler() {
console.log('Single Word Capitalized');
}
return (
<>
<button onClick={fetchData}>Fetch Products</button>
<button onClick={clearProducts}>Clear Products</button>
<div>
{products.map((product) => {
return (
<div ref={productNameRef} id={product.id} key={product.id}>
<h3 className="productName">{product.name}</h3>
<button onClick={() => clearSingleProduct(product.id)}>
Clear
</button>
<button onClick={capHandler}>Capitalize</button>
</div>
);
})}
</div>
</>
);
};
export default App;
you make the capHandler accept product index id and update the state.
function capHandler(id) {
const newProducts = [...products]
newProducts[i].name = capitalizeFirstLetter(newProducts[i].name)
setProducts([...newProducts]);
}
Also need to pass the map index while mapping the products
<div>
{products.map((product, i) => {
return (
...
Lastly call the fucntion in the button
<button onClick={() => capHandler(i)}>Capitalize</button>
Update the capHandler() function with the following body:
function capHandler() {
setProducts(products.map(i => ({...i , name: capitalizeFirstLetter(i.name)}))
}
Related
I'm trying to use a search bar component in my React project to search/filter through an api list of movies by title. Right now my search term is console logging, but i'm trying to filter the movie list to only show the titles that match the term. I'm having issues with updating my movies state with the term and displaying the new array.
App
import SearchBar from "../Search/SearchBar"
export default function Movies() {
const [movies, setMovies] = useState([]);
async function getMovies() {
const movieData = await fetchMovies();
console.log(movieData);
setMovies(
movieData.data.data.sort((a, b) => a.title.localeCompare(b.title))
);
}
useEffect(() => {
getMovies();
}, []);
async function onSearchSubmit(term) {
console.log(term)
let fill = []
movies.filter((movie) => {
if(movie.title === term) {
fill.push(movie.title)
}
setMovies(fill)
})
}
return (
<>
<Nav
movies={movies}
setMovies={setMovies}/>
<SearchBar
onSubmit={onSearchSubmit}/>
{movies ? (
<div>
<div>
{movies.map((m, idx) => {
return <div key={idx}>{m.title}</div>;
})}{" "}
</div>
</div>
) : (
"loading..."
)}
</>
);
}
Search Bar component
import React,{useState} from 'react';
const SearchBar = ({onSubmit}) => {
const [term, setTerm] = useState("")
function onFormSubmit(event){
event.preventDefault()
onSubmit(term)
}
return (
<div className="ui segment">
<form onSubmit={onFormSubmit} className="ui form">
<div className="field">
<label>Movie Search</label>
<input
type="text"
value={term}
onChange={(e) => setTerm( e.target.value)}
/>
</div>
</form>
</div>
);
}
export default SearchBar;
First of all additional state is needed to record the loaded moves list:
const movies = useRef([]);
const [filteredMovies, setFilteredMovies] = useState([]);
It is better to declare handlers with useCallback and avoid the mixture of declarative and imperative styles. For example:
const onSearchSubmit = useCallback(async (term) => {
if (term) {
const _ = movies.current.filter(({ title }) => (title === term));
setFilteredMovies(_);
} else {
setFilteredMovies(movies.current);
}
}, [movies]);
https://jsfiddle.net/pq9xkewz/2/
I have made a basic application to practice React, but am confused as to why, when I try to delete a single component from an state array, all items after it get deleted too. Here is my basic code:
App.js:
import React from 'react'
import Parent from './Parent';
import './App.css';
function App() {
return (
<div className="App">
<Parent />
</div>
);
}
export default App;
Parent.js:
import React, { useState } from 'react';
import ListItem from './ListItem';
import './App.css';
function Parent() {
const [itemList, setItemList] = useState([])
const [numbers, setNumbers] = useState([])
const addItem = () => {
const id = Math.ceil(Math.random()*10000)
const newItem = <ListItem
id={id}
name={'Item-' + id}
deleteItem={deleteItem}
/>
const list = [...itemList, newItem]
setItemList(list)
};
const deleteItem = (id) => {
let newItemList = itemList;
newItemList = newItemList.filter(item => {
return item.id !== id
})
setItemList(newItemList);
}
const addNumber = () => {
const newNumbers = [...numbers, numbers.length + 1]
setNumbers(newNumbers)
}
const deleteNum = (e) => {
let newNumbers = numbers
newNumbers = newNumbers.filter(n => n !== +e.target.innerHTML)
setNumbers(newNumbers);
}
return (
<div className="Parent">
List of items:
<div>
{itemList}
</div>
<button onClick={addItem}>
Add item
</button>
<div>
List of numbers:
<div>
{numbers.map(num => (
<div onClick={deleteNum}>{num}</div>
))}
</div>
</div>
<button onClick={addNumber}>
Add number
</button>
</div>
);
};
export default Parent;
ListItem.js:
import React from 'react';
import './App.css';
function ListItem(props) {
const { id, name, deleteItem } = props;
const handleDeleteItem = () => {
deleteItem(id);
}
return (
<div className="ListItem" onClick={handleDeleteItem}>
<div>{name}</div>
</div>
);
};
export default ListItem;
When I add an item by clicking the button, the Parent state updates correctly.
When I click on the item (to delete it), it deletes itself but also every item in the array that appears after it <-- UNWANTED BEHAVOUR. I only want to delete the specific item.
I have tested it with numbers too (not creating a separate component). These delete correctly - only the individual number I click on is deleted.
As far as I can tell, the individual item components are saving a reference as to what the Parent state value was when they are created. This seems like very strange behaviour to me...
How do I delete only an individual item from the itemList state array when they are made up of separate components?
Thanks
EDIT: As per the instruction from Bergi, I fixed the issue by converting the 'itemList' state value to an array of objects to render (and rerender) when the list is changed instead:
const addItem = () => {
const id = Math.ceil(Math.random()*10000);
const newItem = {
id: id,
name: 'Item-' + id,
}
const newList = [...itemList, newItem]
setItemList(newList)
}
...
React.useEffect(() => {
}, [itemList]);
...
<div className="Parent">
List of items:
<div>
{itemList.map(item => {
return (<ListItem
id={item.id}
name={item.name}
deleteItem={deleteItem}
/>);
})}
...
The problem is that your deleteItem function is a closure over the old itemList, back from the moment in which the item was created. Two solutions:
use the callback form of setItemList
don't store react elements in that list, but just plain objects (which you can use as props) and pass the (most recent) deleteItem function only when rendering the ListItems
I have the following setup in react, now it complains that setClose is not a function inside the add to cart. I am stuck as to how I would trigger the setclose useState from inside the add to cart componenet, I guess I can't pass it as a prop down to the child. Not sure what to do at this point.
Thanks ahead of time
main component
const [close, setClose] = useState(true)
const toggleCart = () => {
setClose(!close)
}
return (
<AddToCart
cartAdd={setClose}
/>
{close ? <CartItems /> : null}
)
add to cart componenet
import React from "react"
import { useShoppingCart, formatCurrencyString } from "use-shopping-cart"
const AddToCart = ({ sku, setClose }) => {
const { addItem } = useShoppingCart()
const test = () => {
setClose(false)
}
return (
<div>
<button onClick={(() => addItem(sku), test())}>ADD TO CART</button>
</div>
)
}
export default AddToCart
const [close, setClose] = useState(true)
const toggleCart = () => {
setClose(!close)
}
return (
<AddToCart
cartAdd={toggleCart}
/>
{close ? <CartItems /> : null}
)
const AddToCart = ({ sku, cartAdd }) => {
const { addItem } = useShoppingCart()
const handleButtonClick = () => {
addItem(sku);
cartAdd();
}
return (
<div>
<button onClick={handleButtonClick}>ADD TO CART</button>
</div>
)
}
use like this
UPDATED
I'm using global giving API to make a charity finder app.
I have two dropdowns and a search button in the CharityFinderPage.js component. Now on clicking the search button, I want to fetch the charities using the themeId. The endpoint is https://api.globalgiving.org/api/public/projectservice/themes/{themeId}/projects
I know that on handleClick I should fetch the charities, but how do I get the value of themeId in the handleClick of CharityFinderPage.js component.
What I want is to show a new card component when the button clicks like showing a charity card with the fields populated on it from the data of the API, but first I need to be able to get the data from the API, then I can render a new component.
Here's the code:
CharityFinderPage.js
const CharityFinderPage = () => {
const handleClick = () => {
console.log("inside handleclick")
}
return (
<div style={containerStyle}>
<h1>Charity Finder ❤️</h1>
<h3>Search for charity</h3>
<h4>
Filter charities by personal search conditions. Use the dropdown below
to see charities matching your criteria.
</h4>
<Themes />
<Regions />
<button onClick={handleClick}>Search</button>
</div>
)
}
export default CharityFinderPage
Themes.js
import React, { useEffect, useState } from "react"
import axios from "axios"
const url = `https://api.globalgiving.org/api/public/projectservice/themes.json?api_key=${process.env.REACT_APP_api_key}`
const Themes = () => {
const [isLoading, setIsLoading] = useState(false)
const [selectValue, setSelectValue] = useState("")
const [themes, setThemes] = useState([])
useEffect(() => {
const fetchThemes = async () => {
try {
setIsLoading(true)
const result = await axios.get(url)
setThemes(result.data.themes.theme)
setIsLoading(false)
} catch (err) {
console.log(err)
}
}
fetchThemes()
}, [])
const handleChange = (event) => {
console.log("inside handleChange", event.target.value)
setSelectValue(event.target.value)
}
return (
<div>
{isLoading ? (
<h4>Loading......</h4>
) : (
<div>
<label>Select theme: </label>
<select onChange={handleChange} value={selectValue}>
{themes.map((theme, id) => {
return <option key={id}>{theme.name}</option> //{id} is the `themeId`
})}
</select>
</div>
)}
</div>
)
}
export default Themes
Regions component is exactly similar to Themes.
So the thing that you need to do here is called lifting the state up.
You need to move your states of theme component to CharityFinder component
I am lifting only selectedValue because that is all that you need
CharityFinderPage.js
const CharityFinderPage = () => {
const [selectValue, setSelectValue] = useState("")
const handleClick = () => {
console.log(`inside handleclick with ${selectValue}`)
}
return (
<div style={containerStyle}>
<h1>Charity Finder ❤️</h1>
<h3>Search for charity</h3>
<h4>
Filter charities by personal search conditions. Use the dropdown below
to see charities matching your criteria.
</h4>
// you can pass the setSelectValue as prop to Themes component
<Themes setSelectValue={setSelectValue} selectValue={selectValue} />
<Regions />
<button onClick={handleClick}>Search</button>
</div>
)
}
export default CharityFinderPage
Theme.js
import React, { useEffect, useState } from "react"
import axios from "axios"
const url = `https://api.globalgiving.org/api/public/projectservice/themes.json?api_key=${process.env.REACT_APP_api_key}`
const Themes = ({ selectValue, setSelectValue }) => {
const [isLoading, setIsLoading] = useState(false)
const [themes, setThemes] = useState([])
useEffect(() => {
const fetchThemes = async () => {
try {
setIsLoading(true)
const result = await axios.get(url)
setThemes(result.data.themes.theme)
setIsLoading(false)
} catch (err) {
console.log(err)
}
}
fetchThemes()
}, [])
const handleChange = (event) => {
console.log("inside handleChange", event.target.value)
setSelectValue(event.target.value)
}
return (
<div>
{isLoading ? (
<h4>Loading......</h4>
) : (
<div>
<label>Select theme: </label>
<select onChange={handleChange} value={selectValue}>
{themes.map((theme, id) => {
return <option key={id}>{theme.name}</option> //{id} is the `themeId`
})}
</select>
</div>
)}
</div>
)
}
export default Themes
You can do this.
const CharityFinderPage = () => {
const [themeId, setThemeId] = useState();
const handleClick = () => {
console.log("inside handleclick")
// make call to endpoint with themeId
}
return (
<div style={containerStyle}>
<h1>Charity Finder ❤️</h1>
<h3>Search for charity</h3>
<h4>
Filter charities by personal search conditions. Use the dropdown below
to see charities matching your criteria.
</h4>
<Themes setThemeId={setThemeId} />
<Regions />
<button onClick={handleClick}>Search</button>
</div>
)
}
export default CharityFinderPage
Then in Themes.js:
...
const handleChange = (event) => {
console.log("inside handleChange", event.target.value)
props.setThemeId(event.target.value);
setSelectValue(event.target.value)
}
...
I am totally confused about this scenario , I am having a state variable called listItems setting the value for listItems using the api call inside useEffect now in the handleChange I am changing the particular object value inside the listItems but I didn't change the actual listItems value but if i console the listItems it's showing as updated value even without setList how come it happens?
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import OrderSummary from './orderSummary'
export default function displayItems() {
const [listItems, setList] = useState([]);
const [order, setorder] = useState([]);
var newarr = [];
useEffect(() => {
axios.post('http://localhost:3006/listItem', {
})
.then(function (resp) {
let res = resp.data.sendList.response;
let newRes = res.map((item) => {
return item;
})
setList(newRes);
})
.catch(function (error) {
console.log(error);
});
}, [])
function handleChange(type,item) {
var arrList=item;
var newAr=[];
if (type === 1) {
arrList.quantity=arrList.quantity+1;
}
else if (type === 0 && item.quantity > 1) {
arrList.quantity=arrList.quantity-1;
}
newAr.push(arrList);
console.log("test",listItems) // value changes here dont know how
// setList(listItems);
}
function placeOrder(item) {
newarr.push(...order, item);
setorder(newarr)
}
return (
<div className="col">
<div className="row">
<div classname="col-md-6">
<p><b>Available Items</b> </p>
{listItems && listItems.map((item) => {
return (
<div key={item._id}>
<p>Name:{item.name}</p>
<p>Description:{item.description}</p>
<p>Cost:{'₹'}{' '}{item.cost}</p>
<p>Quantity:{' '}
<i onClick={() => handleChange(1,item)} className="fa fa-plus-circle" />
<span className="text-center"><b>{item.quantity}</b></span><i onClick={() => handleChange(0,item)} className="fa fa-minus-circle" /></p>
<div>
<button onClick={() => placeOrder(item)}>Add to order</button>
</div>
</div>)
})}
</div>
{order && <OrderSummary orderItems={order} />}
</div>
</div>
)
}
sandox
The following code var arrList=item; is an assignment by reference, it means that arrList and item are both references to the same object which explains the modification of the second when modifying the first, if you want to clone an object you can use Object.assign() or the Spread operator or another solution:
var arrList = Object.assign({}, item);
// Or
var arrList = {...item};
Working demo: