Fetching individual JSON data on reactJS - json

When I parse A JSON from my server to my react front end it works fine perfectly but when I add a parameter to display an individual item I get an error. How do I display individual JSON data on react JS. I get the JSON data from my rest server. My code looks like the following.
In order to get the JSON I use the following method.
state = {
isLoading: true,
groups: [],
};
async componentDidMount() {
const response = await fetch('/product/all/1');
const body = await response.json();
this.setState({ groups: body, isLoading: false });
}
This is how I call the array
{this.state.groups.map(group => <div className="col-sm-12 col-md-6 col-lg-4 p-b-50">
{/* Block2 */}
<div className="block2">
<div className="block2-img wrap-pic-w of-hidden pos-relative block2-labelnew">
<img src={group.thumbnail} />
<div className="block2-overlay trans-0-4">
<a href="#" className="block2-btn-addwishlist hov-pointer trans-0-4">
<i className="icon-wishlist icon_heart_alt" aria-hidden="true" />
<i className="icon-wishlist icon_heart dis-none" aria-hidden="true" />
</a>
<button key={group.id} onClick={() => this.add(group.productid, group.name)} className="flex-c-m size1 bg4 bo-rad-23 hov1 s-text1 trans-0-4">Add to Cart</button>
</div>
</div>
<div className="block2-txt p-t-20">
<a href={`/productdetails/` + group.productid}>{group.name}</a>
</div>
</div>
</div>)}
I get an error saying "TypeError: this.state.groups.map is not a function"
My Spring backend to call all items and individual items look as the following
#GetMapping("/product")
public List<Product> index(){
return productRepository.findAll( );
}
#GetMapping("/product/all/{id}")
public Product show(#PathVariable String id){
int productId = Integer.parseInt(id);
return productRepository.findOne(productId);
}
P.S both api's seem working fine when tested on postman and fetching ("api/products") too works fine

this.state.groups.map is not a function . This means React expects this.state.groups to be an array. If you are returning only one item then do it like it this:
this.setState({ groups: [body], isLoading: false });
this way, body will be the first and only item in an array.

Related

Not being able to test a HTML view including an async variable in Angular

I am writing a simple test for my game component. Just checking if all child components are getting loaded in right. They all seem to work except WordFormComponent. I am guessing this is because I only render it when a async variable has been set to True. This happens only when all variables have been set.
My game.component.html looks like this:
<div class="u-w-full lg:u-w-[70%] u-mx-auto">
<a routerLink="/gameList" class="lg:u-bg-white hover:u-bg-gray-100 u-text-[13px] u-font-medium u-py-1 u-px-3 u-border u-border-gray-300 u-rounded u-flex u-items-center" style="max-width: fit-content">
<mat-icon aria-label="Submit word" class="u-h-[50%] u-text-base">keyboard_backspace</mat-icon>
Games overview
</a>
<div class="lg:u-grid u-grid-cols-3 u-gap-y-[2rem]">
<div class="u-col-span-full u-p-6 u-w-full u-bg-white u-rounded u-mt-1 u-border u-border-gray-200">
<app-final-word-form (onGuessFinalWord)="submitFinalWord($event)"></app-final-word-form>
</div>
<div clas="u-col-span-1">
<app-dashboard (onNextRound)="nextRound($event)"></app-dashboard>
</div>
<div class="u-col-span-2 u-row-span-2 lg:u-ml-[2rem]">
<div *ngIf="dataLoaded | async; then thenBlock else elseBlock"></div>
<ng-template #thenBlock>
<!-- Does not show up in test -->
<app-word-form [game]="game" [word]="word" [gameWord]="gameWord" (onGuessWord)="submitWord($event)"></app-word-form>
</ng-template>
</div>
</div>
</div>
And my test looks like this:
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ GameComponent, FinalWordFormComponent, DashboardComponent, WordFormComponent ],
imports: [ ToastrModule.forRoot() ]
})
.compileComponents();
gameFixture = TestBed.createComponent(GameComponent);
gameComponent = gameFixture.componentInstance;
gameService = TestBed.inject(GameService);
spyOn(gameService, 'createGame').and.returnValue(of({ 'Game': game, 'Gameword': gameWord, 'Word': word, 'Finalword': finalWord }));
gameFixture.detectChanges();
});
fit('should display titles of all child components', waitForAsync(() => {
gameFixture.detectChanges();
expect(gameFixture.nativeElement.querySelector('a').textContent).toContain('Games overview'); // Works
expect(gameFixture.nativeElement.querySelector('p').textContent).toContain('How to win: guess the finalword correctly.'); // Works
expect(gameFixture.nativeElement.querySelector('#wordheader').textContent).toContain('Game word.'); // Failed: Cannot read properties of null (reading 'textContent')
}));
Whenever I log this.dataLoaded when running my test it does return true. So that should not be the problem. It seems like the view does not pick up on it. Anyone knows how to make this work?

Alpine JS fetch data - limit x-for iteration results and store data for later use

Alpine JS fetch data
How we should do to limit x-for iteration (like the json have 10 results but i want to show only five) and store data for later use with another script outside like a slider to add data ater each slide.
In short, retrieve the json response data to load the next slider image only when the slider arrow will be clicked or the slider will be swiped.
The data should be stored for use in javascript.
HTML:
<div class="main" x-data="init()">
<h4 class="font-xxlarge">Movie search in Alpine.js</h4>
<div class="searchArea">
<input
class="inputText"
type="text"
placeholder="Type to search a fact"
x-model="q"
#keyup.enter="search()"
/>
<button class="bg-default" #click="search()">Search</button>
<br><br>
</div>
<div>
<template x-for="result in results">
<div class="movieCard">
<div>
<img x-bind:src="result.Poster" />
</div>
<div>
<div class="movieDetailItem">
<span style="padding-right: 5px">Title:</span
><span><b x-text="result.Title">Man of Steel</b></span>
</div>
<div class="movieDetailItem">
<span style="padding-right: 5px">Year:</span
><span><b x-text="result.Year">2008</b></span>
</div>
</div>
</div>
</template>
JS:
function init() {
return {
results: [],
q: "",
search: function () {
fetch(
"https://www.omdbapi.com/?&apikey=e1a73560&s=" + this.q + "&type=movie"
)
.then((response) => response.json())
.then((response) => (this.results = response.Search))
.then(response => console.log(response))
.catch((err) => console.log(err));
// console.log(response);
},
};
}
Codepen example: https://codepen.io/onigetoc/pen/yLKXwQa
Alpine.js calls this feature getters, they return data based on other states. Let's say we have startIndex and endIndex variables, then we can do a simple filtering with filter() in the getter method, that returns the items between these two indexes.
<script src="https://unpkg.com/alpinejs#3.x.x/dist/cdn.min.js"></script>
<script>
function init() {
return {
results: ['#1', '#2', '#3', '#4', '#5'],
startIndex: 2,
endIndex: 4,
get filteredResults() {
return this.results.filter((val, index) => {
return index >= this.startIndex && index <= this.endIndex
})
}
}
}
</script>
<div class="main" x-data="init()">
Items:<br>
<template x-for="result in results">
<span x-text="`${result} `"></span>
</template>
<br><br>
Filtered items between index: <span x-text="`${startIndex} and ${endIndex}`"></span>:<br>
<template x-for="result in filteredResults">
<span x-text="`${result} `"></span>
</template>
</div>

React map is duplicating DOM Elements

I have the following issue:
I am building a web application, BackEnd in spring and FrontEnd in React.
Now I am adding a feature that shows how many products are in the cart while the client is clicking on "buy". The problem is that when I do a map to get the API in the dom tree, it seems like is duplicating the element.
Images:
Bug: "Carrinho ( )" is being duplicated
Note:
I am consuming two APIs
Code:
import React, { useEffect, useState } from 'react';
import {
Row,
CardBody,
Container,
} from 'reactstrap';
import api from '../../resources/api_produtos';
import apiCart from '../../resources/api_cart';
import axios from 'axios';
const Main = () =>{
const[product, setProduct] = useState([]);
const[cart, setCart] = useState([]);
const fetchData = () =>{
const productApi = api.get('');
const cartApi = apiCart.get('');
axios.all([productApi, cartApi]).then(
axios.spread((...allData) =>{
const allProductData = allData[0].data;
const allDataCart = allData[1].data;
setProduct(allProductData);
setCart(allDataCart);
console.log(allDataCart);
})
)
}
useEffect(() =>{
fetchData()
}, [])
return (
<div>
<div className="p-3 mb-2 bg-dark text-white d-flex justify-content-between">
<div>
<strong>Game Store</strong>
</div>
<div>
{cart.map(carrinho =>(
<div key={carrinho.id}>
<div>
Carrinho ( {carrinho.amount} ) HERE IS BEING DUPLICATED
</div>
</div>
))}
</div>
</div>
<Container>
<div className="jumbotron mt-3"><h1>Produtos</h1></div>
{product.map(produto => (
<div className="card mb-3">
<div key={produto.id}className="card-header d-flex justify-content-between">
<span>
Id: {produto.id}
</span>
<div>
<nav>
<form method="POST" action={"http://localhost:8080/comprar/" + produto.id}>
<input type="submit" className="btn btn-secondary" value="Comprar" ></input>
</form>
</nav>
</div>
</div>
<CardBody>
<Row>
<div className="col-12 col-sm-8 mb-3">
<div className="row">
<div key={produto.id}>
<div >
Nome: {produto.name}
</div>
<div >
Preço: R$ {produto.price}
</div>
</div>
</div>
</div>
<div className="col-12 col-md-4">
<figure key={produto.id}>
<img className="img-thumbnail"src={produto.image} />
</figure>
</div>
</Row>
</CardBody>
</div>
))}
</Container>
</div>
);
}
export default Main;
And this is how the API "Cart" looks like:
[{"amount":"7"},[{"id":12,"name":"Mortal Kombat XL","price":69.99,"score":150,"image":"https://images-americanas.b2w.io/produtos/01/00/offers/01/00/item/126077/6/126077695_1GG.png"},{"id":12,"name":"Mortal Kombat XL","price":69.99,"score":150,"image":"https://images-americanas.b2w.io/produtos/01/00/offers/01/00/item/126077/6/126077695_1GG.png"},{"id":12,"name":"Mortal Kombat XL","price":69.99,"score":150,"image":"https://images-americanas.b2w.io/produtos/01/00/offers/01/00/item/126077/6/126077695_1GG.png"},{"id":12,"name":"Mortal Kombat XL","price":69.99,"score":150,"image":"https://images-americanas.b2w.io/produtos/01/00/offers/01/00/item/126077/6/126077695_1GG.png"},{"id":12,"name":"Mortal Kombat XL","price":69.99,"score":150,"image":"https://images-americanas.b2w.io/produtos/01/00/offers/01/00/item/126077/6/126077695_1GG.png"},{"id":12,"name":"Mortal Kombat XL","price":69.99,"score":150,"image":"https://images-americanas.b2w.io/produtos/01/00/offers/01/00/item/126077/6/126077695_1GG.png"},{"id":12,"name":"Mortal Kombat XL","price":69.99,"score":150,"image":"https://images-americanas.b2w.io/produtos/01/00/offers/01/00/item/126077/6/126077695_1GG.png"}]]
How do I fix this duplication?
The problem is the response object from your cart API.
It's bad-formed, since your BE is returning an array which gives no sense since the response includes one single cart.
So, the map function iterate over an array, modifying its content by the callback, place them in a new array, and then returns it.
So essentially you are trying to modify the obj on the index 0, which is and obj with the "amount" field, and then, you are trying to map an array at index 1.
So you have to update your response from the BE, something like that:
{
"id": "cart_id",
"items": []
}
Which has more sense compared with yours. Note that since items is an array you don't need the "amount" field, you can access it with carrinho.items.length for instance. Then render it with
<div key={carrinho.id}>
<div>Carrinho ({carrinho.items.length})</div>
</div>

Order Json object

I'm creating a simple movie listing app with Angular 4. I'm making an HTTP GET request to fetch all the movies stored in a json file. They have some fields like "Id", "Title", "Genre", "Duration", etc. When i'm listing all the movies, how can i order them by ID descending, so that the last one appear first?
Here's the code that i am using to get this json data:
On my data service file:
getMovies(){
return this.http.get('assets/data/movies.json')
.map(res => res.json());
}
On my component.ts file:
export class MainComponent implements OnInit {
movies: Movies[];
username:string;
userimg:string;
constructor(private userService:UserService, private dataService:DataService) { }
ngOnInit() {
this.dataService.getMovies().subscribe((movies) =>{
this.movies = movies;
});
}
}
interface Movies {
id:number,
title:string,
year:number,
rating:number,
cover:string,
genre:string,
duration:string,
description:string,
favourite:number
}
On my component.html file:
<div *ngFor="let movie of movies" class="row row-movies">
<a [routerLink]="['/movies', {'id': movie.id}]">
<div class="col-md-9">
<h3> {{movie.title}}</h3>
<h4> {{movie.year}}</h4>
<h4> {{movie.rating}}</h4>
<p>{{movie.description}}</p>
<h5> {{movie.genre}}</h5>
<h5> {{movie.duration}}</h5>
</div>
<div class="col-md-3">
<img src="../assets/img/capas/movies/{{movie.capa}}" class="img-responsive capa-filme" width="350px" />
</div>
</a>
</div>
Can you help me please? I'm still very noob with Angular..
This question has nothing to do with angular. You need to do, after loading the movies, something like this:
this.movies.sort((a,b) => (b.id - a.id));
As a note: this works using any Array in vanilla javascript.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
If you don't need the sort to change, you can do it as you retrieve the data.
this.dataService.getMovies().subscribe((movies) =>{
movies.sort((a, b) => {
return a.id < b.id ? 1 : -1;
});
this.movies = movies;
});
NOTE: I did not syntax check this.

Passing Object in ng-repeat to another page to display its contents

I am working on a project using MySQL, Angular, Express, and Node. I have a list of objects in a ng-repeat and when I click a specific item I would like to pass the clicked object to another page and show the object's properties through angular.
Here is the code:
HTML:
<div class = "panel panel-info" ng-repeat="job in job">
<div class = "panel-heading clickable">
<h1 class = "panel-title">{{job.title}}</h1>
<span class = "pull-right"><i class = "glyphicon glyphicon-minus"></i></span>
</div>
<div class = "panel-body">
<!--This will soon be the place where the Students information is placed by NodeJS-->
<!--<p style = "text-decoration: underline"> Job Title <p>-->
<p> {{job.description}} </p>
<p> {{job.first_name}} {{job.last_name}}</p>
<p> {{job.location}} </p>
<br>
<div class="form-group">
<div class=" col-sm-15">
<button onclick="location.href='jobPage.html';" type="submit" class="btn btn-default btn-block">Apply</button>
</div>
</div>
</div>
</div>
Controller:
soopControllers.controller("landingController",
function ($scope, $http){
$scope.formData = {};
$http.get('/api/jobLanding')
.success(function(data){
$scope.job = data;
console.log(data);
})
.error(function(data){
console.log('Error: ' + data);
});
//I want this function to get the job and send it to another page
$scope.getJob = function(){
$http.post('/api/job', $scope.formData)
.success(function(data){
$scope.formData = {};
$scope.users = data;
//$location.redirect();
console.log(data);
})
.error(function(data){
console.log('Error: ' + data);
});
};
});
AngularJS applications work the same way as regular web sites when it comes to navigation. The difference is that instead of sending a request to the server to go to the new URL, the router intercepts the location change, and goes to a route. The controller (or the resolve function) of that route then gets what it needs to display.
So, what you need in your ng-repeat instead of your button is
<a ng-href="#/job/{{ job.id }}">Apply</a>
And you then need a route mapped to the path /job/:jobId.
In the controller of this route, you'll then do something like
$http.get('/api/job/' + $routeParams.jobId).then(function(response) {
$scope.job = response.data;
});
How about using ng-click on the repeated element and extract that element in your display/routed page.
<div ng-controller="plandingController"
class = "panel panel-info"
ng-repeat="job in job"
ng-click="value.val=job">
....
</div>
In jobPage.html
<div ng-controller="plandingController" ng-repeat="pickedjob in value.val">