So i'm using an API to get an array of Objects,
each object has a lot of data, i want to filter this data to just grab the data i need so i can display it on a React-Table.
export default class Table extends React.Component {
constructor(props){
super(props);
this.state = {
}
}
fetchData() {
const string = 'http://localhost:8000/issues/assigned/mike';
fetch(string)
.then(function(response) {
return response.json();
})
.then((myJson) => this.setState(myJson));
console.log(this.state)
}
componentDidMount(){
this.fetchData();
}
componentDidUpdate(prevProps) {
if (prevProps.value !== this.props.value) {
this.fetchData()
}
}
render() {
return this.state.issues? (
<div>
<ResponseTable data={this.state.issues} />
</div>
) : (
<div>
Loading ...
</div>
);
}
}
The JSON file i'm receiving from the API:
JSON DATA NEST
For the example there is only one object, i'm receiving 50 object with the exact same nesting, i'm looking foward to extract a few properties ( for example, data.issues[0].fields.timespent ) soo i can pass this data into my react-table and create a row for each "issue"
setState function does not immediately update a component but you can use a callback function setState(updater[, callback]) to get your state right after it was updated.
Regarding data filtering, you can use .map() or .filter() function to transform or filter your collection after a response was converted to JSON.
You can use .filter() method to filter data that you need and .map() method to map this data to appropriate model.
Related
I'm trying to filter the data directly under the return statement. I am getting this error "Objects are not valid as a React child. If you meant to render a collection of children, use an array instead". Map function works just fine. Map and Filter both return array
Here's my code
export class TestPage extends Component {
constructor(){
super();
this.state = {
proPlayerData: []
}
}
componentDidMount(){
this.fetchData();
this.filterData();
}
filterData = () => {
}
fetchData = async() => {
const playerData = await fetch("https://api.opendota.com/api/playersByRank");
const player_data = await playerData.json()
console.log("fetch",player_data);
await this.setState({proPlayerData: [...player_data]})
}
render() {
// let topTenIds = this.state.proPlayerData
// console.log(topTenIds)
return (
<div>
{this.state.proPlayerData.filter((data,index) => {
if(index <= 10){
return <div key={index}>data.accountId</div>
}
})}
</div>
)
}
}
export default TestPage
Why can't I use filter just like map?
Array.prototype.map transforms data from one format to another, its used in react a lot to transform your arrays of data into JSX
Array.prototype.filter will filter data in your data arrays, but not alter the format, therefore if you start with an array of objects, you will end with an array of objects of the same shape (or an empty array if none meet the condition in the callback)
You need a combination of both, first a filter to filter the data you want, then a map to transform your filtered data into JSX, but even still rather than a filter, which will iterate over each element, you only need the first 10, looking at your example, therefore you can use Array.prototype.slice -
this.state.proPlayerData
.slice(0, 10)
.map((data) => (<div key={index}>{data.accountId}</div>))
edit... looks like you maybe want to the first 11, therefore update the slice args to suit...
I need help using the axios dependancy on react.
I'm fetching data from this endpoint: https://api.covid19api.com/summary
I setup a config for my API (config.jsx)
import axios from 'axios';
export default axios.create({
baseURL: `https://api.covid19api.com/summary`,
responseType: "json"
});
then i call it in my App, (a class component) as API
import API from './config.jsx';
and execute it within my ComponentDidMount()
componentDidMount() {
API.get().then(res => {
const countries = JSON.stringify(res.data.Countries);
//console.log(countries);
this.setState({ covid: countries });
console.log(`Etat du state: ${this.state.covid}`);
})
}
I get the data, store it in my state named 'covid', but when it comes to map over the results i get an error "TypeError Cannot read property 'map' of null" I think i have to convert the data into an array but i don't know how to do this .
render() {
return (
<div className="App">
<header className="App-header">
<h1>{this.state.appliname}</h1>
{this.state.covid.map(item => (
<div>{item.Country}</div>
))}
</header>
</div>
);
}
Here's the full script on codesandbox: https://codesandbox.io/s/intelligent-faraday-ykewv?file=/src/App.js
Thanks
There are several things you need to consider:
Always handle errors in promises. Sometimes you may face API failure, so you should handle the API request gets failed that we should do. So simply add a catch handler to your promise chain.
You should always handle first data initiation. In the first render of your page, there is no this.state.covid so you can't pass it to your view and map through it, so if you do this it will throw an error. To make this work you should add conditional rendering to your element.
Define your first state initiation correctly. Since you expect your this.state.covid to be an array, so you should define it as an array in the first place (this.state = {covid: []}).
Avoid passing strings to Array#map. When you try to convert your incoming items from API to JSON with JSON.stringify(res.data.Countries) it will make your data as JSON. Since JSON comes with string type it won't fit array helpers like Array#map, when you got your data there is no need to make JSON of the. If in any case, your incoming data was JSON itself you should parse it with JSON.parse().
Working demo:
Set init state default array
this.state = { covid: [], appliname: "CovidFacts" };
Set countries
const countries = res.data.Countries;
Follow all :
import React from "react";
import "./App.css";
import API from "./config.jsx";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
covid: [],
appliname: "CovidFacts"
};
}
componentDidMount() {
API.get().then(res => {
const countries = res.data.Countries;
this.setState({ covid: countries });
console.log(`Etat du state: ${this.state.covid}`);
});
}
render() {
return (
<div className="App">
<header className="App-header">
<h1>{this.state.appliname}</h1>
{this.state.covid.map(item => (
<div>{item.Country}</div>
))}
</header>
</div>
);
}
}
export default App;
Im new in ReactJS...
I have a project with the following class components structure:
index.js
--app
--chat
--header
--left
--right
In the chat.js component, I make a google search with the api to retrieve images based on specific keyword... My intuitive solution was:
this.client.search("cars")
.then(images => {
for(let el of images) {
ReactDOM.render(<img src="{{el.url}}" syle="{{width: '100%'}}" />, document.querySelector('#gimages'));
}
});
It is correct? Or I may to use Components with stored states with flux (redux)?
Perhaps a simpler more conventional use of react would achieve what your require?
You could follow a pattern similar to that shown below to achieve what you require in a more "react-like" way:
class Chat extends React.Component {
constructor(props) {
super(props)
this.state = { images : [] } // Set the inital state and state
// model of YourComponent
}
componentDidMount() {
// Assume "client" has been setup already, in your component
this.client.search("cars")
.then(images => {
// When a search query returns images, store those in the
// YourComponent state. This will trigger react to re-render
// the component
this.setState({ images : images })
});
}
render() {
const { images } = this.state
// Render images out based on current state (ie either empty list,
// no images, or populated list to show images)
return (<div>
{
images.map(image => {
return <img src={image.url} style="width:100%" />
})
}
</div>)
}
}
Note that this is not a complete code sample, and will require you to "fill in the gaps" with what ever else you have in your current Chat component (ie setting up this.client)
This is not the way you should go, you don't need to use ReactDOM.render for each item. Actually, you don't need to use ReactDOM.render at all. In your component you can use a life-cycle method to fetch your data, then set it to your local state. After getting data you can pass this to an individual component or directly render in your render method.
class Chat extends React.Component {
state = {
images: [],
}
componentDidMount() {
this.client.search( "cars" )
.then( images => this.setState( { images } ) );
}
renderImages = () =>
this.state.images.map( image => <Image key={image.id} image={image} /> );
render() {
return (
<div>{this.renderImages()}</div>
);
}
}
const Image = props => (
<div>
<img src={props.image.url} syle="{{width: '100%'}}" />
</div>
);
At this point, you don't need Redux or anything else. But, if you need to open your state a lot of components, you can consider it. Also, get being accustomed to using methods like map, filter instead of for loops.
Im new to react. I am trying to pull data from an API, and then loop through it and display it.
Error : Cannot read property 'map' of undefined.
The API data is coming through, but it seems as if React is calling the looplistings before the data is stored into State.
constructor () {
super()
this.state = {
data:'',
}
}
componentWillMount(){
// Im using axios here to get the info, confirmed data coming in.
//Updating 'data' state to equal the response data from the api call.
}
loopListings = () => {
return this.state.data.hits.map((item, i) => {
return(<div className="item-container" key={i}>
<div className="item-image"></div>
<div className="item-details">tssss</div>
</div>)
})
}
loopListings = () => {
return this.state.data.hits.map((item, i) => {
return(
<div className="item-container" key={i}>
<div className="item-image"></div>
<div className="item-details">tssss</div>
</div>)
})
}
render () {
return (
<div>
{this.loopListings()}
</div>
)
}
The reason you are receiving this error is that your call to the API is happening asynchronously to the react lifecycle methods. By the time the API response returned and persisted into the state the render method has been called for the first time and failed due to the fact you were trying to access an attribute on a yet undefined object.
In order to solve this, you need to make sure that until the API response has been persisted into the state the render method will not try to access that part of the state in your render method or to make sure that if it does there is a valid default state in the constructor:
Solve this by changing your render to do something like this:
render () {
return (
<div>
{this.state.data &&
Array.isArray(this.state.data.hits)
&& this.loopListings()}
</div>
)
}
or initialize your constructor like so :
constructor () {
super()
this.state = {
data: {hits: []},
}
}
Remeber react is just javascript and its behavior is just the same.
You could check if desir data.hits exists inside state.
{this.state.data && Array.isArray(this.state.data.hits) ?
this.loopListings()
: null}
Also make sure that, after retrieving a data cal this.setState method like below.
this.setState({ data })
Is there a way to log 1 object or 2 or as much as i want to be logged in console?
Im using simple data from jsonplaceholder.typicode.com (quite usefull for learning purpose) which every of object has unique id.
For example:
I fetched data and rendered 200 posts on website, ok... but if i have a data which contains 100 logos or banners or layouts for website i want to render a specific logo or banner with unique id 30.
So, how can I render only 1st, 2nd, 3rd or x post/posts from 200?
This is what i have now:
App.js
import React, { Component } from 'react';
import './css/App.css';
import './css/bootstrap.css';
import $ from 'jquery';
import Todos from './Todos';
class App extends Component {
constructor(props) {
super(props);
this.state={
todos:[],
};
}
getTodos() {
$.ajax({
url: 'https://jsonplaceholder.typicode.com/todos',
dataType: 'json',
cache: false,
success: function (data) {
this.setState({todos: data}, function () {
console.log(this.state);
});
}.bind(this),
error: function (xhr, status, err) {
console.log(err);
}
})
}
componentWillMount(){
this.getTodos();
}
componentDidMount() {
this.getTodos();
}
render() {
return (
<div>
<Todos todos={this.state.todos}/>
</div>
);
}
}
export default App;
Todos.js
import React, {Component} from 'react';
import TodoItem from './TodoItem';
class Todos extends Component {
render() {
let todoItems;
if(this.props.todos) {
todoItems = this.props.todos.map(todo => {
return (
<TodoItem key={todo.title} todo = {todo} />
);
});
}
return (
<div>
<h2>Todo list from api</h2>
{todoItems}
</div>
);
}
}
Todos.propTypes = {
todos: React.PropTypes.array
};
export default Todos;
TodoItem.js
import React, {Component} from 'react';
class TodoItem extends Component {
render() {
return (
<li>
<strong>{this.props.todo.title}</strong>
</li>
);
}
}
TodoItem.propTypes = {
todo: React.PropTypes.object
};
export default TodoItem;
#edit
Is there possible to filter mapped json data and find object depending on id and then render this object?
Slicing multiple items
You can use another state property, like filterCount, which can be either set by you manually, or you can trigger setState events from buttons e.g.
constructor(props) {
super(props);
this.setFilter = this.setFilter.bind(this);
this.state={
todos: [],
filterCount: 20 // Default value
};
}
setFilter(count) {
this.setState({ filterCount: count });
}
render() {
const { todos, filterCount } = this.state;
return(
<div>
...
<button onClick={this.setFilter(10)} />
<button onClick={this.setFilter(20)} />
<button onClick={this.setFilter(50)} />
</div>
)
}
This will ensure that your component is rerendered each time when you change the count.
Now the second part, filtering the first x items.
The main way is to use Array.prototype.slice
Slice example:
render() {
const { todos, filterCount } = this.state;
const filteredToDos = todos.slice(0,filterCount);
return(
<div>
...
{ filteredToDos.map(todo => {
return (
<TodoItem key={todo.title} todo={todo} />
);
}) }
</div>
)
}
Make sure you don't accidentally use splice instead of slice, because splice doesn't do the copy to a new array, but modifies the original one.
This example can be easily modified to support paging aswell. You could add a currentPage parameter to state, and just modify the slice line to slice the array based on which page you are.
Also, think about hard whether you need to use jQuery in your app. If you are using it only for convenience, then you are making a big mistake, as it is a hefty library that increases your bundle size considerably. Try to learn doing things the react way :)
Your ajax request can be done by using fetch api.
return fetch("https://jsonplaceholder.typicode.com/todos")
.then(response => response.json())
.catch((error) => {
console.error(error);
});
If you don't need out of the box support for the older browsers, fetch api will be fine for last 2-3 major versions of modern browsers, including mobile ones. It can be polyfilled for older ones such as IE8 aswell.
Also, there are libraries like axios that are actually much smaller then jQuery if you need more options and support for all request types and headers.
Also, the fetch action itself, could be decoupled from the component to a separate actions.js file so it can be imported / used from multiple components in the future, or refactored more easily to support working with something like Redux if your app grows.
Getting a single item
Fetching single item from API directly - suggested
If we are talking about performance, then the best way is to get a single item directly from API. Considering that this is a REST based api then the way would be:
https://jsonplaceholder.typicode.com/todos/{id}
Rewriten for a fetch example with a template literal:
return fetch(`https://jsonplaceholder.typicode.com/todos/${itemId}`)
.then(response => response.json())
.catch((error) => {
console.error(error);
});
This should also return a single object, not an array, so you should be able to pass it as a prop and use it immediately. IMHO, this is the best solution to the problem.
Fetching all items from API, then filtering with filter
Second option is to get all layouts, then filter the result which you can see in the example of: #mayank-shukla
Bottleneck of this method is that Array.prototype.filter can return multiple results.
Fetching all items from API, then filtering with find
Third option is to get all layouts, then filter the result with Array.prototype.find
Example is the same as filter, just change the keyword to find.
Find will return the first result in case of multiple ones.
Fetching all items from API, then filtering by index
Fourth option is getting all layouts, then filtering by index, e.g.
todos[itemId-1]
This will also work, but, you need to be 100% certain that backend will order the results in the array by their id. Also, index starts from 0, and your ids from 1, so this can be a source of major confusion.
Instead of passing all the data to child component filter out the data on the basis of condition and pass that filtered data to child component.
Like this:
render() {
//filter data of these ids
let ids = [1,2,3,4];
//filtered data
let data = this.state.todos.filter(el => ids.indexOf(el.id) != -1);
return (
<div>
<Todos todos={data}/>
</div>
);
}
If you want to pass first x data, the use #array.slice and pass the result, like this:
render() {
//filtered data
let data = this.state.todos.slice(0, 20);
return (
<div>
<Todos todos={data}/>
</div>
);
}
Instead of putting the filter part inside render you can do that after getting the response.