How to use reduce() function in Vue 3 Composition API - javascript

I am building Pokemon filtered search app using Vue 3 with Composition API, based on the following tutorial: https://www.youtube.com/watch?v=QJhqr7jqxVo. The Home view in the app uses a fetch method to fetch Pokemon from the PokemonAPI:
fetch('https://pokeapi.co/api/v2/pokemon?offset=0')
.then((res)=> res.json())
.then((data)=> {
console.log(data)
state.pokemons = data.results;
state.urlIdLookup = data.results.reduce((acc, cur, idx)=>
acc = { ...acc, [cur.name]:idx+1 }
,{})
})
Each Pokemon in the JSON response includes an index number. The second promise uses a reduce method to handle urlIdLookup. urlIdLookup is then passed into the router-link path used to redirect to Pokemon about/details page:
<div class="ml-4 text-2x text-blue-400"
v-for="(pokemon, idx) in filteredPokemon" :key="idx">
<router-link :to="`/about/${urlIdLookup[pokemon.name]}`">
{{ pokemon.name }}
</router-link>
</div>
The tutorial, however, does not explain why it is necessary to create a new accumulator object ("acc") inside the reduce method, and then deconstruct the accumulator ("...acc"). Could someone perhaps explain why it's necessary to create that object, then deconstruct it? Also, is there perhaps a better method for retrieving the id and passing it into the router link?
Here is the full component:
<template>
<div class="w-full flex justify-center">
<input type="text" placeholder="Enter Pokemon here"
class="mt-10 p-2 border-blue-500 border-2" v-model="text"/>
</div>
<div class="mt-10 p-4 flex flex-wrap justify-center">
<div class="ml-4 text-2x text-blue-400"
v-for="(pokemon, idx) in filteredPokemon" :key="idx">
<router-link :to="`/about/${urlIdLookup[pokemon.name]}`">
{{ pokemon.name }}
</router-link>
</div>
</div>
</template>
<script>
import { reactive, toRefs, computed } from 'vue';
export default {
name: 'Home',
setup() {
const state = reactive({
pokemons: [],
urlIdLookup:{},
text: "",
filteredPokemon: computed(()=> updatePokemon())
})
const updatePokemon = () => {
if(!state.text) {
return []
}
return state.pokemons.filter((pokemon) =>
pokemon.name.includes(state.text)
)
}
fetch('https://pokeapi.co/api/v2/pokemon?offset=0')
.then((res)=> res.json())
.then((data)=> {
console.log(data)
state.pokemons = data.results;
state.urlIdLookup = data.results.reduce((acc, cur, idx)=>
acc = { ...acc, [cur.name]:idx+1 }
,{})
})
return { ...toRefs(state), updatePokemon }
}
}
</script>

Related

Why I can't get categories?

Does anyone know why I can't filter out the categories?
Error: Objects are not valid as a React child (found: object with keys {_key, _ref, _type}). If you meant to render a collection of children, use an array instead.
I also tried with {categories.title}
when implementing
{categories && categories.map((index, category) => (
<p key={index}>{category}</p>
))}
it just shows 0 in all cards.
import React from 'react'
import Link from 'next/link'
import groq from 'groq'
import sanityClient from '../client'
import imageUrlBuilder from '#sanity/image-url';
function urlFor (source) {
return imageUrlBuilder(sanityClient).image(source)
}
const stories = ({posts}) => {
return (
<div className='bg-gray-100'>
<div className='md:grid md:grid-cols-3 px-4 py-4'>
{posts.length > 0 && posts.map(
({ _id, title = '', slug = '', description, mainImage, categories }) =>
slug && (
<div key={_id} className='py-2 md:px-2'>
<Link href="/post/[slug]" as={`/post/${slug.current}`}>
<div className='border rounded-md p-4 bg-white cursor-pointer'>
<img className='w-full' src={urlFor(mainImage).url()}
width='500'
height='500'
alt="Mainn Image"/>
<h2 className='text-2xl py-6'>{title}</h2>
<p>{categories}</p>
<p className='opacity-60'>{description}</p>
</div>
</Link>
</div>
)
)}
</div>
</div>
)
}
export async function getStaticProps() {
const posts = await sanityClient.fetch(groq`
*[_type == "post" && publishedAt < now()] | order(publishedAt desc)
`)
return {
props: {
posts
}
}
}
export default stories
The main problem is in your code is related to built-in map method of Array constructor. map 's callback function as a first parameter gets an array value and as a second an index of that array item. So you mixed up the order of callback function parameters.
Also you used a category value as a children for JSX element (in your case it's <p>), whose type is probably an object.
For example, if your category looks like this: {id: 0, title: 'Books'}, then you can use as a child or an innerHTML/innerText of <p> like this:
<p key={category.id}>{category.title}</p>,
but not like:
<p key={index}>{category}</p>

problem in looping and passing data in vue component

guys its my first time to ask a question here in stackoverflow and i really needs an answer
i have a project which i get data from external api from pinia (similar to VueX) then i pass them into a page then i loop through the data and purse them into a component card to be a dynamic component which renders what ever the data i get
i am having a problem in passing the data into the dynamic component.
i fetched the data successflly in pinia , store it into the state in the store . but cant make it into a variable to loop through them
first iam using typescript
for shop interface ShopData.ts
export default interface ShopData {
id: string
name: string
logoPath: string
address: string
}
for types.ts
export type Shop = ShopData
that is my ShopQueries.ts
import { acceptHMRUpdate, defineStore } from 'pinia'
import type { Shop } from '~/types'
import { getShops } from '~/api/ShopsQueries'
export const useShopQueriesStore = defineStore('ShopQueries', {
state: () => ({
shops: [] as Shop[],
}),
actions: {
async getShops(num: number) {
const response = await getShops(num)
this.shops = response.data
return this.shops
},
},
})
if (import.meta.hot)
import.meta.hot.accept(acceptHMRUpdate(useShopQueriesStore, import.meta.hot))
the page file index.vue
<script setup lang="ts">
import { useShopQueriesStore } from '~/stores/ShopQueries'
import type { Shop } from '~/types'
const shopStore = useShopQueriesStore()
const shops = ref<Shop[] | null>()
onMounted(async() => {
shops.value = await shopStore.getShops(6)
})
</script>
<template>
<div class="row">
<div class="col-md-6 col-xxl-4 mt-3 my-3">
<ShopCard
v-for="shop in shopStore.$state.shops"
:key="shop.id"
:address="shop.address"
:name="shop.name"
:image="shop.logoPath"
/>
</div>
</div>
</template>
Which i also want to make it a card and wraps down and i cant :(
that is the card component ShopCard.vue
<script setup lang="ts">
import type { PropType } from '#vue/runtime-core'
import type { Shop } from '~/types'
const props = defineProps({
shop: null as null | PropType<Shop>,
})
console.log(props)
onMounted(() => {
})
const { shop } = toRefs(props)
</script>
<template>
<div class="card">
<div class="card-body d-flex flex-center flex-column pt-12 p-9">
<div class="symbol symbol-65px symbol-circle mb-5">
<img src="{{shop.image}}" alt="image">
</div>
<a class="fs-4 text-gray-800 text-hover-primary fw-bolder mb-0" href="">{{ shop.name }}</a>
</div>
<div class="fw-bold text-gray-400 mb-6">
{{ shop.address }}
</div>
</div>
</template>
i know its hard .. but i really needs some help please !
the whole task depends on it
waiting for help ...

Prop is an empty object in React child

I'm trying to add a search bar to a parent component.
All the logic is working fine in the console. With every character that is typed in the search field I get fewer results.
I try to pass it to a child component to render the card(s) result, but I get a blank card: I can not see data passed.
Parent Component <AllAssets>
class AllAssets extends Component {
state = {
cards: [],
searchField: '',
}
async componentDidMount() {
const { data } = await cardService.getAllCards();
if (data.length > 0) this.setState({ cards: data });
}
addToFavorites = (cardId, userId) => {
saveToFavorites(cardId, userId)
toast.error("The asset was added to your favorites.")
}
render() {
const { cards, searchField } = this.state;
const user = getCurrentUser();
const filteredAssets = cards.filter(card => (
card.assetName.toLowerCase().includes(searchField.toLowerCase())));
console.log(filteredAssets);
return (
<div className="container">
<SearchBox placeholder={"Enter asset name..."}
handleChange={(e) => this.setState({ searchField: e.target.value })}
/>
<PageHeader>Assets available for rent</PageHeader>
<div className="row">
<div className="col-12 mt-4">
{cards.length > 0 && <p>you can also add specific assets to your favorites and get back to them later...</p>}
</div>
</div>
<div className="row">
{!!filteredAssets.length ? filteredAssets.map(filteredAsset => <SearchResult addToFavorites={this.addToFavorites} filteredAsset={filteredAsset} user={user} key={filteredAsset._id} />) :
cards.map(card => <CardPublic addToFavorites={this.addToFavorites} card={card} user={user} key={card._id} />)
}
</div>
</div >
);
}
}
export default AllAssets;
Child Component <SearchResult>
const SearchResult = (addToFavorites, filteredAsset, card, user) => {
return (
<div className="col-lg-4 mb-3 d-flex align-items-stretch">
<div className="card ">
<img
className="card-img-top "
src={filteredAsset.assetImage}
width=""
alt={filteredAsset.assetName}
/>
<div className="card-body d-flex flex-column">
<h5 className="card-title">{filteredAsset.assetName}</h5>
<p className="card-text">{filteredAsset.assetDescription}</p>
<p className="card-text border-top pt-2">
<b>Tel: </b>
{filteredAsset.assetPhone}
<br />
<b>Address: </b>
{filteredAsset.assetAddress}
</p>
<p>
<i className="far fa-heart text-danger me-2"></i>
<Link to="#" className="text-danger" onClick={() => addToFavorites(card._id, user._id)}>Add to favorites</Link>
</p>
</div>
</div>
</div>
);
}
export default SearchResult;
When I console.log(filteredAsset) in <SearchResult> I get an empty object. What am I doing wrong?
This line is incorrect:
const SearchResult = (addToFavorites, filteredAsset, card, user) => {
You are passing in positional arguments, not named props. Do this instead:
const SearchResult = ({addToFavorites, filteredAsset, card, user}) => {
In your original code, React attaches all of your props as fields on the first argument. So they would be accessible in the child, but not in the way you're trying to access them. Try logging out the values of each of the arguments in the child, if you're curious to see what happens.
The corrected version passes in a single object with field names that match the names of your props. It's shorthand that's equivalent to:
const SearchResult = (
{
addToFavorites: addToFavorites,
filteredAsset: filteredAsset,
card: card,
user: user,
}
) => {

How to render Array stored in a Object in React?

I am trying to develop a discussion forum website using React, Node and MongoDB.In post object, there is nested author object and tags array.
Here is sample image of a post object:
here is the component which I am trying to render:
import React, { Component } from "react";
import http from "../services/httpService";
import { postEndPoint, repliesEndPoint } from "../config.json";
class PostPage extends Component {
state = {
post: [],
replies: [],
};
async componentDidMount() {
const id = this.props.match.params.id;
const { data: post } = await http.get(postEndPoint + "/" + id);
const { data: replies } = await http.get(repliesEndPoint + "/" + id);
console.log(post.tags, typeof post.tags);
this.setState({ post: post, replies: replies });
}
render() {
const { post, replies } = this.state;
return (
<React.Fragment>
<div className="container col-lg-8 shadow-lg p-3 mt-5 bg-body rounded">
<h2>{post.title}</h2>
<p className="mt-4" style={{ color: "#505050" }}>
{post.description}
</p>
<div className="mt-1">
Related Topics:
{post.tags.map((tag) => (
<span className="badge badge-secondary m-1 p-2">
{(tag).name}
</span>
))}
<h6 className="mt-2">
{post.upvotes.length} Likes {post.views} Views
</h6>
<div class="d-flex w-100 justify-content-between">
<small class="mb-1">Posted by {post.author['name']}</small>
</div>
</div>
</div>
</React.Fragment>
);
}
}
export default PostPage;
This throws the following : TypeError: post.tags is undefined. a Similar error is throws while accessing post.upvotes and post.author
Since you do your http request in 'componentDidMount' a render occured at least once before. So react tried to read post.something and it was still undefined.
And even if you do it before an http request is asynchronous so be careful
You need to check that post.something is defined before you use.
Also your initialisation if confusing you initialize post as an array but you are trying to do post.title.
If post is really an array then post.map() won't crash on an empty array.
If it's an object check that is it defined correctly.
Try this as initial state
state = {
post: {
description:"",
title:"",
tags: [],
author:[] ,
upvotes:[] ,
views : 0
},
}
initial state for post is {}
state = {
post: { tags: [] },
replies: [],
};
You can have a simple if condition added. So it will only loop through that if it is present. Check this.
import React, { Component } from "react";
import http from "../services/httpService";
import { postEndPoint, repliesEndPoint } from "../config.json";
class PostPage extends Component {
state = {
post: [],
replies: [],
};
async componentDidMount() {
const id = this.props.match.params.id;
const { data: post } = await http.get(postEndPoint + "/" + id);
const { data: replies } = await http.get(repliesEndPoint + "/" + id);
console.log(post.tags, typeof post.tags);
this.setState({ post: post, replies: replies });
}
render() {
const { post, replies } = this.state;
return (
<React.Fragment>
<div className="container col-lg-8 shadow-lg p-3 mt-5 bg-body rounded">
<h2>{post.title}</h2>
<p className="mt-4" style={{ color: "#505050" }}>
{post.description}
</p>
<div className="mt-1">
Related Topics:
{post.tags && post.tags.map((tag) => ( // <--- map will only execute when it finds tags.
<span className="badge badge-secondary m-1 p-2">
{(tag).name}
</span>
))}
<h6 className="mt-2">
{(post.upvotes && post.upvotes.length) || 0} Likes {post.views} Views // <---- These default values too will handle the case where the data isnt ready yet
</h6>
<div class="d-flex w-100 justify-content-between">
<small class="mb-1">Posted by {post.author['name']}</small>
</div>
</div>
</div>
</React.Fragment>
);
}
}
export default PostPage;

Axios Multiple requests to return different object values

I am building a Film grid that return their Id, thumbnail, title, episode number and released date.
How can I display the Object Values on each specific view of species.names?
getSpecies() it returns object value but how can i passed them in render method?
DetailFilms.js Component:
import React, { Component } from 'react';
import axios from 'axios';
import { Link } from 'react-router-dom';
class DetailFilms extends Component {
state = {
film: null,
specie: null,
}
getFilms() {
let id = this.props.match.params.films_id;
return axios.get('https://swapi.co/api/films/' + id)
.then(res => {
this.setState({
film: res.data
})
// console.log(res)
})
}
getSpecies() {
let id = this.props.match.params.films_id;
return axios.get('https://swapi.co/api/species/' + id)
.then((res) => {
const species = res.data;
const keys = Object.keys(species)
console.log(species)
this.setState({
species: res.data.results,
keys: keys
})
})
}
componentDidMount() {
this.getFilms();
this.getSpecies();
}
render() {
const film = this.state.film ? (
<div className="jumbotron">
<h2 className="display-4">{this.state.film.title}</h2>
<p className="lead">{this.state.film.director}</p>
<hr className="my-4" />
<p>{this.state.film.producer}</p>
<p>Episode NÂș: {this.state.film.episode_id}</p>
<p className="font-italic">{this.state.film.opening_crawl}</p>
<p>Species: {this.state.film.species.name}</p>
<Link className="btn btn-primary btn-lg" to="/" role="button">Character</Link>
</div>
) : (
<div className="spinner-border" role="status">
<span className="sr-only">Loading...</span>
</div>
)
return (
<div className="d-flex justify-content-center align-items-center ">
{film}
</div>
)
}
}
export default DetailFilms;
CodeSandbox Demo & Api Documentation Swapi
The props you are passing in with the route, it is called films_id not species_id.
So in your getSpecies method, this is how you should get the id of the current movie:
let id = this.props.match.params.films_id;
Then if you see the species api: https://swapi.co/api/species/
if you query it through the id, you will get the specie corresponding to that id, which is not the species of that movie.
In order to get all the species of that movie, you need to do an HTTP get request to the root https://swapi.co/api/species/ and then check all the results which have as film the one for which you are seeing the details. These will be all the species of that movie.

Categories

Resources