React: How to update a property of an object? - javascript

I want to increase the likes of a single blog by 1 in the incLikes function and put the updated blog back in the blogs state
const App = () => {
const [ blogs, setBlogs ] = useState(null)
useEffect(() => {
blogsService.getAll().then(initialBlogs => {
setBlogs(initialBlogs)
})
}, [])
const incLikes = blog => {
...
}
...
My mongo database contains the following blogs:
[
{
"title": "The lost planet in the milky way",
"author": "Ford Beeblebrox",
"url": "www.goldenplanet.milky.way",
"likes": 102,
"id": "600aabcbf4492017c4068727"
},
{
"title": "How the Vogols destroyed the Earth",
"author": "Michael Faraday",
"url": "www.far-far-aday.com",
"likes": 45,
"id": "600ab1575883720a04743319"
}
]

To give you a fuller context, here is how you would do it:
const [blogs, setBlogs] = React.useState(all_blogs);
const incLikes = (blog) => {
setBlogs(
blogs.map((b) => {
if (b.id === blog.id) b.likes++;
return b;
})
);
};
return (
<div className="App">
<h1>Blogs:</h1>
{blogs.map((item, i) => (
<div key={i}>
{item.title} (Likes: {item.likes}) --{" "}
<button onClick={() => incLikes(item)}> Like</button>
</div>
))}
</div>
);
So the idea is to pass a blog object into your function, then map through all the blog objects stored in the state and increment the matching one. Here is a sandbox for you.

Related

How to make filter with Radio Input

i'm actually learning Javascript and React but i have a problem.
I'm trying to build a Pokedex but i have a problem.
i put some Radio Input to make a filter of generation (pokemon) but i don't know how to link my inputs with the apiGeneration number on the API...
i also want to make a search bar by name, i'm able to see the searchBar.value on a console.log(), but i can't put an addeventlistener on the searchBar element...
Thanks a lot for your help. ^^
API link : https://pokebuildapi.fr/api/v1/pokemon
import axios from "axios";
import React, { useEffect, useState } from "react";
import Card from "./Card";
const Pokedex = () => {
const [data, setData] = useState([]);
const radios = ["1G", "2G", "3G", "4G", "5G", "6G", "7G", "8G"];
const [selectedRadio, setSelectedRadio] = useState("");
useEffect(() => {
axios
.get("https://pokebuildapi.fr/api/v1/pokemon")
.then((res) => setData(res.data));
}, []);
return (
<div className="pokemons">
<h2 id="search-title">Pokedex</h2>
<p className="search-text">Recherhe par nom</p>
<div>
<input type="search" id="search-bar"></input>
</div>
<div className="gen-search">
{radios.map((generation) => (
<ul className="radio-container">
<li>
<input
type="radio"
id={generation}
name="generationRadio"
checked={generation === selectedRadio}
onChange={(e) => setSelectedRadio(e.target.id)}
/>
<label htmlFor={generation}>{generation}</label>
</li>
</ul>
))}
</div>
{selectedRadio && (
<button onClick={() => setSelectedRadio("")}>
Annuler la recherche
</button>
)}
<div className="pokemon-container">
<ul>
{data
.filter((pokemon) => {
/* ... */
})
.map((pokemon, index) => (
<Card key={index} pokemon={pokemon} />
))}
</ul>
</div>
</div>
);
};
export default Pokedex;
API
// 20230130034321
// https://pokebuildapi.fr/api/v1/pokemon
[
{
"id": 1,
"pokedexId": 1,
"name": "Bulbizarre",
"image": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/1.png",
"sprite": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/1.png",
"slug": "Bulbizarre",
"stats": {
"HP": 45,
"attack": 49,
"defense": 49,
"special_attack": 65,
"special_defense": 65,
"speed": 45
},
"apiTypes": [
{
"name": "Poison",
"image": "https://static.wikia.nocookie.net/pokemongo/images/0/05/Poison.png"
},
{
"name": "Plante",
"image": "https://static.wikia.nocookie.net/pokemongo/images/c/c5/Grass.png"
}
],
"apiGeneration": 1,
Here is an example based off your snippet.
Changes:
Calculate list of generations from the loaded pokemon data.
Store selected generation as numeric value provided by API.
Added useEffect to ensure that selected generation is always valid.
Added filter() to limit display to selected generation.
Updated key to use pokemon.id, this is a better value as it will require react to do less work if you reorder or change the list at any point in time.
import axios from "axios";
import React, { useEffect, useState, useMemo } from "react";
import Card from "./Card";
const Pokedex = () => {
const [data, setData] = useState([]);
// Get a list of generations from the data retrieved from the all pokemon api.
const generations = useMemo(() => {
// if data not yet loaded, return empty array (no generations).
if (!data) {
return [];
}
// get a list of all unique generations:
// - use list.map() to get the apiGeneration property of each pokemon.
// - use new Set(list) to restrict the list to only unique values.
// - use [...list] to change the Set back into an array.
const allGenerations = [...new Set(data.map(x => x.apiGeneration))];
// sort the list
allGenerations.sort();
// return the list of all generations.
return allGenerations;
}, [data])
// state value to store the currently selected generation.
const [selectedGeneration, setSelectedGeneration] = useState(undefined);
// When ever the list of generations changes (should only be on reload of
// pokemon data) ensure that the selected generation still exists, otherwise
// set it back to all generations (undefined).
useEffect(() => {
setSelectedGeneration(c => {
if (!!c && !generations.includes(c)) {
return undefined;
}
return c;
});
}, [generations, setSelectedGeneration])
// load pokemon data.
useEffect(() => {
axios
.get("https://pokebuildapi.fr/api/v1/pokemon")
.then((res) => setData(res.data));
}, []);
return (
<div className="pokemons">
<h2 id="search-title">Pokedex</h2>
<p className="search-text">Recherhe par nom</p>
<div>
<input type="search" id="search-bar"></input>
</div>
<div className="gen-search">
{
// loop through generations and create radio buttons
generations?.map((generation) => (
<ul className="radio-container">
<li>
<input
type="radio"
id={`generation-${generation}`}
name="generationRadio"
checked={generation === selectedGeneration}
onChange={(e) => setSelectedGeneration(generation)}
/>
<label htmlFor={`generation-${generation}`}>G{generation}</label>
</li>
</ul>
))
}
</div>
{
// create button to clear generation filter.
selectedGeneration && (
<button onClick={() => setSelectedGeneration(undefined)}>
Annuler la recherche
</button>
)
}
<div className="pokemon-container">
<ul>
{
// filter and map out pokemon.
data
.filter((pokemon) => {
// if no filter, return all pokemon.
if (!selectedGeneration) {
return true;
}
// ensure pokemon matches selected generation.
return pokemon.apiGeneration === selectedGeneration;
})
.map((pokemon, index) => (
<Card key={pokemon.id} pokemon={pokemon} />
))
}
</ul>
</div>
</div>
);
};
export default Pokedex;

Returning an object which is inside an array of another object

I am connecting to an api and returning some data on screen using the below:
return (
<>
{Object.values(items).map((item, index) => {
return <pre>{JSON.stringify(item, null, 2)}</pre>
</>
})}
This returns an object to the front end that looks like this.
[
{
"type": "player",
"id": "account.ac12c743e8044d42a6eafeffa2c3a8cf",
"attributes": {
"name": "JohnnyUtah",
"stats": null,
"titleId": "pubg",
"shardId": "stadia",
"patchVersion": ""
},
"relationships": {
"assets": {
"data": []
},
"matches": {
"data": [
{
"type": "match",
"id": "473019a4-fe3b-420a-b00e-b99ff2cd8c73"
I would like to as an example get just the id as shown below:
"id": "account.ac12c743e8044d42a6eafeffa2c3a8cf"
However I have an object with an array of objects inside of it and I don't know how to access this.
I am familiar with the array map function which I believe I need to use, but I don't know how to get inside that array in the object being returned.
I'm therefore struggling to see how I get at this value.
Adding full code as this may help clarify things:
import React, { useEffect, useState } from 'react'
function Player() {
const [player,setPlayer] = useState('JohnnyUtah')
const [items,setItems] = useState([])
useEffect(() => {
const apiKey = "key";
const options = {
"headers": {
"Accept": "application/vnd.api+json",
"Authorization": `Bearer ${apiKey}`
}
}
fetch(`https://api.pubg.com/shards/stadia/players?filter[playerNames]=${player}`, options)
.then(response => response.json())
.then(json => setItems(json))
}, [player])
return (
<>
<div>
<button onClick={() => setplayer('JohnnyUtah')}>JohnnyUtah</button>
<button onClick={() => setplayer('Binder')}>Binder</button>
<button onClick={() => setplayer('MartinSheehanUK')}>MartinSheehanUK</button>
</div>
<h1>{player}</h1>
<div>
<h2>This shows all</h2>
{data.map((item) => (
<li key={item.id}>{item.id}</li>
))}
</div>
<div>
<h2>This shows only one</h2>
{data[0].id}
</div>
</>
)
};
export default Player
Just showing the absolute latest version of my code which is still not returning any results for me:
import React, { useEffect, useState } from 'react'
function Player() {
const [player,setPlayer] = useState('JohnnyUtah')
const [items,setItems] = useState([])
useEffect(() => {
const apiKey = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI5MTI0M2U5MC1iMjk5LTAxM2EtN2Q5ZC0xNzRhYzM1YTM1ZGYiLCJpc3MiOiJnYW1lbG9ja2VyIiwiaWF0IjoxNjUyMTkyMTI4LCJwdWIiOiJibHVlaG9sZSIsInRpdGxlIjoicHViZyIsImFwcCI6InB1YnJnLWxlYWRlcmJvIn0.veCjNiRtCzchj2Gli-aZt_0YQjtMvey3io-UUDa0zpQ";
const options = {
"headers": {
"Accept": "application/vnd.api+json",
"Authorization": `Bearer ${apiKey}`
}
}
fetch(`https://api.pubg.com/shards/stadia/players?filter[playerNames]=${player}`, options)
.then(response => response.json())
.then(json => setItems(json))
}, [player])
return (
<>
<div>
<button onClick={() => setPlayer('JohnnyUtah')}>JohnnyUtah</button>
<button onClick={() => setPlayer('Binder')}>Binder</button>
<button onClick={() => setPlayer('MartinSheehanUK')}>MartinSheehanUK</button>
</div>
<h1>{player}</h1>
{items !==undefined && items.length>0 ? items.map((item) => (
<li key={item.id}>{item.id}</li>
)): <div>No result found</div>}
</>
)
};
You can get all ids from array of objects like
const data = [
{
type: "player",
id: "account.ac12c743e8044d42a6eafeffa2c3a8cf",
attributes: {
name: "Steve1989",
stats: null,
titleId: "pubg",
shardId: "stadia",
patchVersion: ""
},
relationships: {
assets: {
data: []
}
}
//some other values
},
{
type: "player",
id: "account.bc24c743e8044d42a6eafeffa2c3a8cf",
attributes: {
name: "John133",
stats: null,
titleId: "pubg",
shardId: "stadia",
patchVersion: ""
},
relationships: {
assets: {
data: []
}
}
//some other values
}
];
const App = props => (
<div className="App">
<div>
<h2>This shows all</h2>
{data.map((item) => (
<li key={item.id}>{item.id}</li>
))}
</div>
<div>
<h2>This shows only one</h2>
{data[0].id}
</div>
</div>
);
// Render it
ReactDOM.render(
<App />,
document.getElementById("root")
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>
I have also attached the solution to get only specific index.
Update:
The issue is happening with your array where you have to set setItems(json.data). You also need to check if your array is defined.
Full code:
function Player() {
const [player,setPlayer] = useState('JohnnyUtah')
const [items,setItems] = useState([])
useEffect(() => {
const apiKey = "your_key";
const options = {
"headers": {
"Accept": "application/vnd.api+json",
"Authorization": `Bearer ${apiKey}`
}
}
fetch(`https://api.pubg.com/shards/stadia/players?filter[playerNames]=${player}`, options)
.then(response => response.json())
.then(json => setItems(json.data))
}, [player])
return (
<>
<div>
<button onClick={() => setplayer('JohnnyUtah')}>JohnnyUtah</button>
<button onClick={() => setplayer('Binder')}>Binder</button>
<button onClick={() => setplayer('MartinSheehanUK')}>MartinSheehanUK</button>
</div>
<div>
{items !=undefined && items.length>0 ? items.map((item) => (
<li key={item.id}>{item.id}</li>
)): <div>No result found</div>}
</div>
</>
);
}
Assign stringify data to the variable and then you can convert JSON stringify data into parse data using this: JSON.parse then fetch the data:
Below is the example:
const obj = {name: "John", age: "30", city: "New York"};
const myJSON = JSON.stringify(obj);
var c = JSON.parse(myJSON);
console.log(c.name);
Please try it and let me know if you find any issue

TypeError: food.map is not a function in Reactjs

I am new to Reactjs. I am using React hooks and axios to fetch an api of a single post data from its id. I called the api and passed the data into child components using props through spread operator. The server runs but on the page it renders the above error. I have used the map function correctly, but it says map is not a function. I checked the issues online, where the users having similar problem says the data may not be an array. It should be an array but different data types like id,name,title,price etc in my case.
My FoodPage.js
function Food() {
const [food, setFood] = useState([])
const [id,setId] = useState(1)
useEffect(() => {
axios.get(`https://exampleurl.herokuapp.com/api/menus/${id}`)
.then(abc=>{
console.log(abc.data)
setFood(abc.data)
})
.catch(err =>{
console.log(err)
})
}, [])
return (
<div>
<div className="food-page">
{food.map((foodItem) => {
return <PageHeader {...foodItem} key={foodItem.id} />;
})}
<Customize />
{food.map((foodItem) => {
return <FoodDescription {...foodItem} key={foodItem.id} />;
})}
</div>
</div>
);
}
export default Food;
My FoodDescription:
function FoodDescription(props) {
console.log(props);
const {food_name,long_title,subtitle,description,price,id} = props;
return (
<div className="food-description">
<div className="container">
<div className="title">
<div className="main-title">{food_name}</div>
</div>
<div className="description">
{/* {description.map((des: string, index: number) => { */}
{description.map((des, index) => {
return <p key={index}>{des}</p>;
})}
</div>
<div className="order">
<div className="quantity">
<div className="negative" onClick={() => this.handleDecrement()}>
-
</div>
{this.state.quantity}
<div className="positive" onClick={() => this.handleIncrement()}>
+
</div>
</div>
<ExploreButton active="link-active">
Add to Order -{price}
</ExploreButton>
</div>
</div>
</div>
)
}
export default FoodDescription;
Update
The abc.data in console gives the following:
According to your API result, when you call
https://texas-crm1.herokuapp.com/api/menus/1
it return an object, not an array.
{
"id": 1,
"category": "main courses",
"image": "imgUrl",
"image_url": "imgUrl",
"food_name": "butter chicken",
"subtitle": null,
"long_title": null,
"description": "<p>this is a test data from backend for butter chicken in main courses.</p>",
"price": 49.0,
"rating": 3.0
}
So you don't need to map over food try this :
function Food() {
const [food, setFood] = useState([])
const [id,setId] = useState(1)
useEffect(() => {
axios.get(`https://texas-crm1.herokuapp.com/api/menus/${id}`)
.then(abc=>{
console.log(abc.data)
setFood(abc.data)
})
.catch(err =>{
console.log(err)
})
}, [])
return (
<div>
<div className="food-page">
<PageHeader {...food} key={food.id} />;
<Customize />
<FoodDescription {...food} key={food.id} />;
</div>
</div>
);
}
export default Food;
if you don't have any error from the catch, it means you're making a good request to the server, but you need to display loader in for the time out
if (!food?.length) return <div>Loading </div>
return (
<div>
<div className="food-page">
{food?.map((foodItem) => {
return <PageHeader {...foodItem} key={foodItem.id} />;
})}
<Customize />
{food?.map((foodItem) => {
return <FoodDescription {...foodItem} key={foodItem.id} />;
})}
</div>
</div>
);
}
You are calling an endpoint that return one item not a list of foods , I guess it is like "GetById" ,
this is an object (one food):
{
"id": 1,
"category": "main courses",
"image": "imgUrl",
"image_url": "imgUrl",
"food_name": "butter chicken",
"subtitle": null,
"long_title": null,
"description": "<p>this is a test data from backend for butter chicken in main courses.</p>",
"price": 49.0,
"rating": 3.0
}
This code will work but you should keep your code and change the endpoint to (if exist ) "https://texas-crm1.herokuapp.com/api/menus/
useEffect(() => {
axios.get(`https://texas-crm1.herokuapp.com/api/menus/${id}`)
.then(abc=>{
console.log(abc.data)
let arr=[abc.data]
setFood(prev=>arr)
})
.catch(err =>{
console.log(err)
})
}, [])
"

Search an array of odject

Imagen if I have an Array of objects - id and value. Right now the array is empty Array []. But is being fielded anytime the user type and add a name. If a user types Anna, Pedro, and Joana the array is updated to
Array [
Object {
"id": "0.265247357395923",
"value": "Anna",
},
Object {
"id": "0.07416445026556007",
"value": "Pedro",
},
Object {
"id": "0.9097178194832282",
"value": "Joana",
},
]
My task is to add a Search bar where I can search the object value inside the array. Here's my code so far.
My useState
const [folder, emptyFolder] = useState([]);
const [searchTerm, setSearchTerm] = useState("");
My name
const addFolder = (folderName) => {
emptyFolder((currentFolder) => [
...currentFolder,
{ id: Math.random().toString(), value: folderName },
]);
};
My Search. Here is where I want to implement my code so far this is what I am able to do:
const filteredData = folder.filter((item) => {
const textData = searchTerm.toUpperCase();
const nameFolder = item.value.toUpperCase();
return nameFolder.includes(textData);
});
const _searchFilterFunction = (value) => {
//???
};
Here is my Return where I have TextInput as my search bar and {filteredData.map((item, index) => { return {item.value}; })} displaying my name list every time the user creates.
return (
<View style={styles.HomeContainer}>
<TextInput
underlineColorAndroid="transparent"
autoCapitalize="none"
placeholderTextColor="#9a73ef"
style={styles.search}
placeholder="Search"
onChangeText={(value) => {
_searchFilterFunction(value, folder);
}}
/>
<FolderInput myFolder={addFolder} />
{filteredData.map((item, index) => {
return <Text key={item.id}>{item.value}</Text>;
})}
</View>
);
};
PLEASE HELP THANK YOU VERY MUCH.
You can just filter the array of objects(folders) like this:
const filteredFolders = () => {
return state.folders.filter(({ name }) =>
name.toLowerCase().includes(state.search.toLowerCase())
);
}
Where state.search comes from an input. I created a sandbox with working demo
What you are trying is, correct.
const filteredData = folder.filter((item) => {
const textData = searchTerm.toUpperCase();
const nameFolder = item.value.toUpperCase();
return (nameFolder.indexOf(textData) >= 0)
});
includes is used for the array with string, but here actually you are comparing strings right? so you can go with indexOf it will return true if your search string is a substring of the object item.

Filter and search by key and both lowercase or uppercase values

// state
const [searchTerm, setSearchTerm] = useState('');
const [allAccounts, setAllAccounts] = useState([])
const [searchedAccounts, setSearchedAccounts] = useState([])
// query
const GET_ACCOUNTS = gql`
query accounts {
accounts {
id
name
status
companyName
}
}
`;
// get query
const { loading, error } = useQuery(GET_ACCOUNTS, {
fetchPolicy: "no-cache",
skip: userType !== 'OS_ADMIN',
onCompleted: (data) => {
setAllAccounts(data.accounts || [])
setSearchedAccounts(data.accounts || [])
}
});
// useEffect
useEffect(() => {
if (searchTerm) {
const results = allAccounts.filter((c => c.name.toLowerCase().includes(searchTerm)))
setSearchedAccounts(results)
}
}, [searchTerm, searchedAccounts])
// example query result (more than 1)
{
"accounts": [
{
"id": "5deed7df947204960f286010",
"name": "Acme Test Account",
"status": "active",
"company": {
"id": "5de84532ce5373afe23a05c8",
"name": "Acme Inc.",
"__typename": "Company"
},
"__typename": "Account"
},
]
}
// render
{searchedAccounts.map(c => (
<>
<ListItem
dense
button
className={classnames({ [classes.selectedAccountContext]: c.id === accountContextId })}
key={c.id}
onClick={() => accountClicked(c.id)}
>
<ListItemText
primary={c.name}
secondary={
<>
<span>{c.companyName}</span>
<span className="d-flex align-items-center top-margin-tiny">
<Badge
color={c.status === 'active' ? "success" : "danger"}
style={{ marginBottom: 0 }}
>
{c.status.replace(/^\w/, c => c.toUpperCase())}
</Badge>
<span className='ml-auto'>
<SvgIcon><path d={mdiMapMarkerRadius} /></SvgIcon>
<SMARTCompanyIcon />
</span>
</span>
</>
}
/>
</ListItem>
</>
))}
I have a component which initially fetches a query and shows the results. There is an input search box above the list. When you start typing it re-renders the list filtering if the list results are found. Currently I have it working so that when I search, it searches by c.name which is name of the account.
I have been trying to add additional code to this but have had no luck. I want to be able to search by not only c.name but also c.companyName and make sure that it is case-insensitive. Currently if the value has an uppercase it does not find it. Hope that makes sense.

Categories

Resources