I am working on a recipe app. I'm using Yummly API I am getting a response however I am confused how to render the data I get back from the API because the response is a Object with an array of recipes. When I try to render the array I get this error:
Uncaught (in promise) Error: Objects are not valid as a React child (found: object with keys {imageUrlsBySize, sourceDisplayName, ingredients, id, smallImageUrls, recipeName, totalTimeInSeconds, attributes, flavors, rating}). If you meant to render a collection of children, use an array instead.
Link to an image of what the API response looks like:
Object from API
"Matches" is the part I want to render in my component
Action.js
import Axios from 'axios';
import {LOOK_UP_RECIPE`enter code here`} from './types';
const API_ID = '########';
const API_KEY = '######';
const ROOT_LOOK_UP_URL = `http://api.yummly.com/v1/api/recipes?
_app_id=${API_ID}&_app_key=${API_KEY}`
export function lookuprecipesYummly(ingredients) {
const yummlyurl =`${ROOT_LOOK_UP_URL}&q=${ingredients}`;
const request = Axios.get(yummlyurl);
return {
type: LOOK_UP_RECIPE,
payload: request
};
}
Reducer.js
import { LOOK_UP_RECIPE } from '../actions/types'
export default function(state = [], action) {
console.log(action)
switch (action.type){
case LOOK_UP_RECIPE:
return [ action.payload.data, ...state ];
default:
return state;
}
}
Component:
import _ from "lodash";
import React, {Component} from 'react';
import { connect } from 'react-redux';
class RecipeList extends Component {
renderRecipe(recipeData) {
return (
<tr key={0}>
<td key={1}>{recipeData.matches}</td>
</tr>
)
}
render() {
return(
<table>
<thead>
<tr key={1}>
<th>Recipe</th>
</tr>
</thead>
<tbody>
{this.props.recipes.map(this.renderRecipe)}
</tbody>
</table>
)
}
}
function mapStateToProps({recipes}) {
return {
recipes
}
};
export default connect(mapStateToProps)(RecipeList);
You need to use the data for each recipe inside some JSX. Here is an example of how to populate a table row:
import _ from "lodash";
import React, {Component} from 'react';
import { connect } from 'react-redux';
class RecipeList extends Component {
renderRecipe(recipe) {
return (
<tr key={recipe.id}>
<td>{recipe.recipeName}</td>
<td>{recipe.rating}</td>
<td>{recipe.attributes.course[0]}</td>
<td>{recipe.ingredients.join(', ')}</td>
</tr>
)
}
render() {
return(
<table>
<thead>
<tr>
<th>Recipe</th>
<th>Rating</th>
<th>Course</th>
<th>Ingredients</th>
</tr>
</thead>
<tbody>
{this.props.recipes.matches.map(this.renderRecipe)}
</tbody>
</table>
)
}
}
function mapStateToProps({recipes}) {
return { recipes }
};
export default connect(mapStateToProps)(RecipeList);
Related
Hello I am trying to create a navbar and have succeeded in labeling elements from an ArrayList such as [{a:['a','b','c','d'},{b:['hello','world']}] to a navbar.the only issue is the links are not collapsing inside the tag. I am using.jsx
the input given to this component is navbar=[{geolocations['country','region','area','city','neighborhood']}];
import React,{Component} from "react";
import 'bootstrap/dist/css/bootstrap.min.css';
import { MDBDropdown, MDBDropdownMenu, MDBDropdownToggle, MDBDropdownItem } from 'mdb-react-ui-kit';
const stopClickPropagation = event => event.stopPropagation();
class NavbarItem extends Component{
constructor(props) {
super(props);
// Don't call this.setState({) here!
this.state={navbarContent:""}
this.createCategory= this.createCategory.bind(this);
}
createCategory(newObject){
let currentDropDown="";
let category=Object.keys(newObject);
let subCategories=Object.values(newObject);
//for the navbar's button's name
currentDropDown+= "<MDBDropdown> <MDBDropdownToggle>"+category[0]+"</MDBDropdownToggle><MDBDropdownMenu>";
//for the list of menu items.
subCategories.map(e=>{
e.map(i=>{
currentDropDown+= "<MDBDropdownItem href=http://localhost:3000/"+i+"'>"+i+"</MDBDropdownItem>";
})
});
currentDropDown+= " </MDBDropdownMenu></MDBDropdown>";
return currentDropDown;
}
async componentDidMount(){
let currentDropDown="";
let navbarList=Object.values(this.props.navbar);
navbarList.map(e=>
{currentDropDown+=this.createCategory(e);}
)
await this.setState({navbarContent:currentDropDown});
}
render(){
return(
<div dangerouslySetInnerHTML={{ __html: this.state.navbarContent }}>
</div>
);
}
}
export default NavbarItem;
I have a factory smart contract that deploys another smart contract.
In the factory contract I have this function
address[] public migrations;
...
function getMigrations() public view returns (address[] memory) {
return migrations;
}
after the migration is pushed in this array, I want to display a list of addresses on the first page but I really can't.
This is what I've tried:
import React from "react";
import web3 from "./web3";
import factory from "./factory";
import AccordionExampleStyled from "./components/Acordeon";
import { Card, Button } from "semantic-ui-react";
import 'semantic-ui-css/semantic.min.css'
class App extends React.Component {
static async getInitialProps() {
const migration = await factory.methods.getMigrations().call();
return { migration };
};
renderCampaigns() {
const items = this.props.migration.map((address) => {
return {
header: address,
description: (
<Button>See migration</Button>
),
fluid: true,
};
});
return <Card.Group items={items} />;
}
render() {
return (
<div>
<center>
<h2>MigrationHelper by DRIVENecosystem</h2>
<p>
<p><AccordionExampleStyled /></p>
<p>List of all migrations </p>
<p></p>
{this.renderCampaigns()}
</p>
</center>
</div>
);
}
};
export default App;
I got this error: Cannot read properties of undefined (reading 'map')
P.S. I am a newbie in React.js and web3
Edit: I also tried that option, but nothing
import React from "react";
import web3 from "./web3";
import factory from "./factory";
import AccordionExampleStyled from "./components/Acordeon";
import 'semantic-ui-css/semantic.min.css'
class App extends React.Component {
static async getInitialProps() {
const migrations = await factory.methods.getMigrations().call();
return { migrations };
}
renderMigrations(){
const items = this.props.migrations.map((migrations) => {return{migrations}});
return <li>{items}</li>;
}
render() {
return (
<div>
<center>
<h2>MigrationHelper by DRIVENecosystem</h2>
<p>
<p><AccordionExampleStyled /></p>
<p>List of all migrations </p>
<p>{this.props.renderMigrations}</p>
</p>
</center>
</div>
);
}
};
export default App;
The problem
migrations is not present on the first render. so mapping through it, cause the error.
The Solution
Just need to check the migrations data has arrived and has content before mapping it:
class App extends React.Component {
static async getInitialProps() {
const migrations = await factory.methods.getMigrations().call();
return { migrations };
}
const renderMigrations = () => {
return this.props.migrations.map((migration) => {
return(
<div>
<li>{migration.name}</li>)
<p>{migration.detail}</li>)
</div>
)
})
}
render() {
const items = this.props.migration;
return (
<div>
<center>
<h2>MigrationHelper by DRIVENecosystem</h2>
<p>
<p><AccordionExampleStyled /></p>
<p>List of all migrations </p>
<p>
{
items?.length > 0 && renderMigrations
}
</p>
</p>
</center>
</div>
);
}
};
export default App;
In the renderMigrations method, I used e sample to show the migration detail with li and p elements, since the migrations array does not exist in your question, I just take an example. You should change it with your requirement in the application.
I'm struggling with the Angular mat-table library. I got an app with a Node.js backend and an Angular frontend. The node app provides data from a MySQL database in JSON.
Now I want to display this data in a mat-table. I have logged the data in the console, which allows me to see that the data is actually retrieved but just not displayed.
However, the HTML table is empty:
This is my Angular component:
component.html
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8 demo-table">
<ng-container matColumnDef="id">
<th mat-header-cell *matHeaderCellDef>ID</th>
<td mat-cell *matCellDef="let element">{{element.id}}</td>
</ng-container>
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef>Name</th>
<td mat-cell *matCellDef="let element">{{element.name}}</td>
</ng-container>
<ng-container matColumnDef="pop">
<th mat-header-cell *matHeaderCellDef>Population</th>
<td mat-cell *matCellDef="let element">{{element.population}}</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr
mat-row
(click)="clickedRows.add(row)"
[class.demo-row-is-clicked]="clickedRows.has(row)"
*matRowDef="let row; columns: displayedColumns;"
></tr>
</table>
component.ts
import {Component, OnInit,ViewChild} from '#angular/core';
import { Player } from '../player';
import { PlayerService } from '../player.service';
import { MatTableDataSource } from '#angular/material/table';
import {MatPaginator} from '#angular/material/paginator';
import {MatSort, SortDirection} from '#angular/material/sort';
/**
* #title Binding event handlers and properties to the table rows.
*/
#Component({
selector: 'app-players',
styleUrls: ['players.component.css'],
templateUrl: 'players.component.html',
})
export class PlayersComponent implements OnInit {
displayedColumns: string[] = ['id', 'name', 'pop'];
dataSource = new MatTableDataSource<Player>();
#ViewChild(MatPaginator, { static: true }) paginator!: MatPaginator;
#ViewChild(MatSort, { static: true }) sort!: MatSort;
constructor(private playerService:PlayerService) { }
ngOnInit(): void {
this.getPlayers();
}
getPlayers() {
this.playerService.getPlayers().subscribe(players => {
console.log(players);
this.dataSource.data = players;
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
});
}
clickedRows = new Set<Player>();
}
player.service.ts
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { Observable, of } from 'rxjs';
import { Player } from './player';
#Injectable({
providedIn: 'root'
})
export class PlayerService {
constructor(private http: HttpClient) { }
rootURL = '/api';
getPlayers(): Observable<Player[]> {
return this.http.get<Player[]>(this.rootURL+ '/players');
}
}
Any ideas on this?
EDIT:
Could it have something to do with how the array comes back from the API? In Node.js it is retrieved with sequelize and maybe it is the response?
// Get all Players
exports.findAll = (req, res) => {
Player.findAll().then((players) => {
// Send all players as response
res.status(200).json({
status: true,
data: players,
});
});
};
Issue
The data returned based on the screenshot and Node.js API is not Player array, but it is an object with status and data properties.
{
"status": 200,
"data": [...]
}
This line expected that HTTP GET to receive Player array which is conflict with your data.
this.http.get<Player[]>(this.rootURL+ '/players');
Hence, it returns Observable with an empty array and your <mat-table> will not show the data.
Solution
Transform the data to Player array with map rxjs operator.
import { map } from 'rxjs';
getPlayers(): Observable<Player[]> {
return this.http
.get(this.rootURL+ '/players')
.pipe(map((response: any) => response.data as Player[]));
}
Sample Solution on StackBlitz
Create a instance of MatTableDataSource and assign to data Source.
Try this option
this.dataSource = new MatTableDataSource(players);
this.dataSource.sort = this.sort;
Ref: Using HTTP GET request to fetch data in an Angular Material Table
The docs state that bulkActions don't get selected records of a List component, just the selected ids, but i need to check a specific field in each selected record from a button's click handler of the bulkActionsButtons.
Any ideas of how this could be achieved?
I used the aside prop that is passed to the List component. Your ref based solution did not work for me.
https://marmelab.com/react-admin/List.html#aside-component
Check the above link.
The component passed as the aside prop to List component receives selectedIds and the data as part of the props.
Just to expand on #Sudharsan-Ravikumar's answer, the ref solution didn't work in my situation either (react-admin 3.14.1, using classes instead of functions+hooks mostly). I used aside like this...
import React, {Fragment} from 'react';
import {List, Datagrid, TextField, useListContext} from 'react-admin';
import Button from '#material-ui/core/Button';
import AccountTreeIcon from '#material-ui/icons/AccountTree'
import dataProvider from './dataProvider';
export class MyList extends React.Component {
list = null
handleStartMyTask = async () => {
if (!this.list || !this.list.ids || this.list.ids.length===0) return;
// console.log(`LIST DATA:`, this.list.data)
try {
const result = await dataProvider.doMyRemoteTask(this.list.data)
console.log(result)
}
catch(err) {
console.log()
}
}
/**
* This was the only way i could figure out how to get the list details.
* That is, the props you get access to when you do useListContext().
* Refs didn't work.
*/
Aside = () => {
this.list = useListContext()
// We don't actually want an aside component for the list.
return null;
}
render = () => {
return <Fragment>
/* A little card outside the list with some useful buttons */
<div class="card">
<Button
variant="outlined" color="primary" size="medium"
onClick={this.handleStartMyTask}
>
<AccountTreeIcon/> Start my task now!
</Button>
</div>
<List {...this.props} aside={<this.Aside/>} >
<Datagrid>
<TitleField source="title" />
</Datagrid>
</List>
</Fragment>
}
}
Probably absolute heresy to hooks dorks but life is short!
Ok, this is what i did and it works. A combination of a render prop and a ref.
Please if anyone have a better idea, please let me now.
import React, {Component} from 'react';
import {
List,
Datagrid,
TextField,
Button
} from 'react-admin';
class MyBulkButtons extends Component {
handleClick = () => {
const {getSelectedRecords} = this.props;
const records = getSelectedRecords();
const selectedRecords = records.filter(i => i.title === 'Extra action!');
this.processExtraActions(selectedRecords);
};
processExtraActions(selectedRecords) {
//...
}
render() {
return (
<Button onClick={this.handleClick} label={"Check for extra actions"}/>
);
}
}
export class MyList extends Component {
constructor(props) {
super(props);
this.myDataGrid = React.createRef();
}
getSelectedRecords() {
const gridProps = this.myDataGrid.current.props;
return gridProps.selectedIds.map(id => gridProps.data[id]);
}
render() {
return (
<List {...this.props}
bulkActionButtons={<MyBulkButtons getSelectedRecords={this.getSelectedRecords.bind(this)}/>}>
<Datagrid ref={this.myDataGrid}>
<TextField source="id"/>
<TextField source="title"/>
</Datagrid>
</List>
);
}
}
I'm trying to do a crud with angular and node.
My rest API is completed (made with node and mysql);
Trying to display my JSON data at the HTML template, but I'm not being successful...
Thank you if you could help me :)
My service:
import { Injectable } from '#angular/core';
import { Http, Response, Headers, RequestOptions } from '#angular/http';
import { map } from "rxjs/operators";
import { Produto} from './produto';
import { catchError } from 'rxjs/operators';
import { Observable, of } from 'rxjs';
#Injectable()
export class ProductService {
constructor(private _http : Http){ }
getProdutos(): Observable<Produto[]>{
return this._http
.get("http://localhost:8080/produtos").pipe(
map(res => res.json()));
}
}
Html:
<tr>
<th>Product</th>
<th>Price</th>
<th>Description</th>
<th>Category</th>
<th>Actions</th>
</tr>
<!-- Use *ngFor directive to loop throught our list of products. -->
<tr *ngFor="let produto of produtos">
<td>{{produto.nome}}</td>
<td>{{produto.descricao}}</td>
<td>{{produto.valor}}</td>
<td>{{produto.src}}</td>
<td>
Component
import { Component, OnInit, Input, Output, EventEmitter } from
'#angular/core';
import { ProductService } from '../product.service';
import { Observable } from 'rxjs';
import { Produto } from '../produto';
#Component({
selector: 'app-read-products',
templateUrl: './read-products.component.html',
styleUrls: ['./read-products.component.css'],
providers: [ProductService]
})
export class ReadProductsComponent implements OnInit {
produtos: Produto[];
constructor(private productService: ProductService){}
ngOnInit(){
this.productService.getProdutos()
.subscribe(produtos =>
this.produtos=produtos['records']
);
}
}
Class:
export class Produto {
constructor(
public produto_id: number,
public nome: string,
public descricao: string,
public valor: number,
public src: string
){}
}
My json response (when goes to the link):
[{"produto_id":10,"nome":"caderno","descricao":"maycon","valor":23.2212,"src":"aasssaa"}]
Have more classes in my project, if someone thinks that the problem is in another, just tell...
OBS: CREATED WITH ANGULAR CLI AND FOLLOWING THIS TUTORIAL
If you get un-named array from backend then you should assign result directly to productos :
this.productService.getProdutos()
.subscribe(produtos =>
this.produtos=produtos
);
If you get your array data here in productos then your HTML ngFor loop will work correctly :
<tr *ngFor="let produto of produtos">
<td>{{produto.nome}}</td>
<td>{{produto.descricao}}</td>
<td>{{produto.valor}}</td>
<td>{{produto.src}}</td>
<td>
</tr>
You should return the changed response inside map function. Like this
return this._http
.get("http://localhost:8080/produtos").pipe(
map(res =>
return res.json()));
}
Do a console.log to check whether you are receiving the data and identify the structure of the data. For the json response you have mentioned , in your component just assign it directly to the array
this.produtos=produtos;
Hope this helps.