Displaying Data from One Component in Another - reactjs

I'm learning react at the moment and I'm trying to have two components interact with each other. The hierarchy is as follows:
App
--SearchForm
--Results
There's a data object that will be filtered through a string I enter in the SearchForm component. The filtered result should be displayed in the Results component.
My logic was to have all the functions needed in the App component, and pass the data to the individual components.
I want to be able to display the filtered data in the results component.
Can anyone help me with this please?
Please find the App.js file's code below, as well as a sample of the object I'm using.
App.js
import React, { Component } from "react";
import styled from "styled-components";
import Header from "./Header";
import SearchForm from "./SearchForm";
import Results from "./Results";
import Map from "./Map";
const Outer = styled.div`
text-align:center;
`;
class App extends Component {
constructor(props) {
super(props);
this.state = {
query: "",
data: [],
refinedData: [],
};
// this.handleSearchChange = this.handleSearchChange.bind(this);
}
handleSearchChange = (event) => {
this.setState({
query: event.target.value,
});
}
getData = async () => {
const response = await fetch("http://localhost:4200/bookings");
const json = await response.json();
this.setState({
data: json,
})
console.log(this.state.data);
}
filterData = () => {
const filtered = this.state.data.filter(element => {
return element.toLowerCase().includes(this.state.query.toLowerCase());
});
this.setState({
refinedData: filtered,
});
console.log(this.state.refinedData);
}
componentDidMount() {
this.getData();
}
render() {
return (
<Outer>
<Header/>
<SearchForm triggeredUpdate={this.handleSearchChange}/>
<Results searchQuery={this.state.filterData}/>
<Map/>
</Outer>
);
}
}
export default App;
Object
[
{
"id": 50000,
"car": {
"id": 1000,
"licence_plate": "SKK5050Q"
},
"book_start": 1543271643,
"book_end": 1543340723,
"pickup": {
"id": 87,
"code": "WDL",
"lat": 1.434,
"lng": 103.78
},
"dropoff": {
"id": 85,
"code": "TPY",
"lat": 1.33,
"lng": 103.851
},
"user": {
"id": 51498,
"name": "Count Dooku"
}
}
]

This is a simple logic actually in React. You want to show filtered results in your Results component, then you pass the filtered state to it. You can trigger the search with a button, then maybe the suitable place for this can be Search component. For this, you will pass your filterData method to it as a prop as you think.
I said a few times "it is an array not object" in my comments since the last data you show in your question says Object as bold but it is an array :) So, I got confused but you are doing it right.
You should filter your data with a prop in your object. As you think again, like user.name, car.license_late etc. You need a target here.
Here is a simple working example:
class App extends React.Component {
state = {
query: "",
data: [
{
"id": 50000,
"car": {
"id": 1000,
"licence_plate": "SKK5050Q"
},
"book_start": 1543271643,
"book_end": 1543340723,
"pickup": {
"id": 87,
"code": "WDL",
"lat": 1.434,
"lng": 103.78
},
"dropoff": {
"id": 85,
"code": "TPY",
"lat": 1.33,
"lng": 103.851
},
"user": {
"id": 51498,
"name": "Count Dooku"
}
}
],
refinedData: [],
};
handleSearchChange = event => this.setState({
query: event.target.value,
});
filterData = () => {
const { data, query } = this.state;
const filtered = !query ? [] : data.filter(element =>
element.car.licence_plate.toLowerCase().includes(this.state.query.toLowerCase())
);
this.setState({
refinedData: filtered,
});
}
render() {
return (
<div>
<SearchForm filterData={this.filterData} triggeredUpdate={this.handleSearchChange} />
<Results refinedData={this.state.refinedData} />
</div>
);
}
}
const Results = props => (
<div>
{
props.refinedData.map( el =>
<div key={el.id}>
<p>ID: {el.id}</p>
<p>User name: {el.user.name}</p>
</div>
)
}
</div>
)
const SearchForm = props => (
<div>
<input onChange={props.triggeredUpdate} />
<br />
<button onClick={props.filterData}>Search</button>
</div>
)
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
Update after discussion on chat
You can do the search without a button while you typing. We don't have filterData method anymore since we moved the filter logic into handleSearchChange method. Also, we don't need any query state right now.
filterData array created with a ternary operator. If there is no search value we are returning an empty array since we don't want to list all of our data if there is not any search. By the way, I've updated my previous solution according to that, too. It was returning all the data if we hit the Search button with an empty input.
class App extends React.Component {
state = {
data: [
{
"id": 50000,
"car": {
"id": 1000,
"licence_plate": "SKK5050Q"
},
"book_start": 1543271643,
"book_end": 1543340723,
"pickup": {
"id": 87,
"code": "WDL",
"lat": 1.434,
"lng": 103.78
},
"dropoff": {
"id": 85,
"code": "TPY",
"lat": 1.33,
"lng": 103.851
},
"user": {
"id": 51498,
"name": "Count Dooku"
}
}
],
refinedData: [],
};
handleSearchChange = event => {
const { value: query } = event.target;
this.setState(prevState => {
const filteredData = !query ? [] : prevState.data.filter(element =>
element.car.licence_plate.toLowerCase().includes(query.toLowerCase())
);
return {
refinedData: filteredData
};
});
}
render() {
return (
<div>
<SearchForm triggeredUpdate={this.handleSearchChange} />
<Results refinedData={this.state.refinedData} />
</div>
);
}
}
const Results = props => (
<div>
{
props.refinedData.map(el =>
<div key={el.id}>
<p>ID: {el.id}</p>
<p>User name: {el.user.name}</p>
</div>
)
}
</div>
)
const SearchForm = props => (
<div>
<input onChange={props.triggeredUpdate} />
</div>
)
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

Related

How to access the data passed through "this.props.history.push(...)"

Constructor code which contains the data
constructor(props){
super(props);
this.state = {
Data: [
{
"name": 'red'
},
{
"name": 'green'
},
{
"name": 'red'
},
{
"name": 'brown'
},
{
"name": 'yellow'
},
{
"name": 'brown'
}
]
}
}
The code for my button where I map the data
{this.state.Data.map((color) => (
<>
<div className="ViewDetailsBtn"><Button onClick={this.clickMe.bind(this, color.name)} className="ViewDetailsBtnLink">View Details</Button></div></>
))}
onClick function Code
clickMe = (details) => {
console.log(details);
this.props.history.push({
pathname: "/ViewDetails",
state: {detail: details}
});
}
Here it displays the color name on my console properly and it redirects me to ViewDetails but how do I display the color name on the ViewDetails page?
ViewDetails page code
import React from 'react'
const App = (props) => {
console.log(props);
//const data = props.location.state.detail;
return (
<div>
<h1>Details:-</h1>
{/* <h1>{data}</h1> */}
</div>
)
}
export default App
your ViewDetails component should accept a parameter:
const ViewDetails = (props) => {}
then you should be able to access that data through that parameter

how to fetch api data in ReactJS?

i was trying to fetch api data in reactjs but data are not loading perfectly as expected, instead of i am getting an empty page as result. here below is my source code;
i am probably new to reactjs, it would be great if anybody could help me where i am doing thing wrong. thank you so much in advance.
endpoint_url : http://localhost:8000/api/blog_list
api-data:
[
{
"id": 1,
"url": "http://localhost:8000/api/blog_detail/brown",
"title": "brown",
"slug": "brown",
"image": "http://localhost:8000/media/blog/image_2.jpg",
"description": "",
"created_on": "2020-05-08T15:20:53Z",
"status": true,
"category": [
1
]
},
{
"id": 2,
"url": "http://localhost:8000/api/blog_detail/black",
"title": "black",
"slug": "black",
"image": "http://localhost:8000/media/blog/loc.png",
"description": "",
"created_on": "2020-05-08T17:14:31Z",
"status": true,
"category": [
2
]
}
]
./src/Base.js
export default class App extends Component{
state = {
bloglist:[]
};
componentDidMount(){
this.fetchData()
}
fetchData = async () => {
try{
const response = await fetch("http://localhost:8000/api/blog_list");
const jsonResponse = await response.json()
this.setState({bloglist:jsonResponse})
}
catch(error){
console.log(error)
}
}
render(){
const {bloglist} = this.state
if(!bloglist){
return (
<div>
<h1>loading...</h1>
</div>
)
}
return (
{
bloglist.map(bloglist => (
<h3 class="mb-2">
{bloglist.title}
</h3>
<p class="mb-4">{bloglist.description}</p>
))
}
)
}
}
Wrap it in a div. React expects a single element.
<h3 class="mb-2">
{bloglist.title}
</h3>
<p class="mb-4">{bloglist.description}</p>
Check this code.
export default class App extends Component {
state = {
bloglist: [],
};
componentDidMount() {
this.fetchData();
}
fetchData = async () => {
try {
const response = await fetch('http://localhost:8000/api/blog_list');
const jsonResponse = await response.json();
this.setState({ bloglist: jsonResponse });
} catch (error) {
console.log(error);
}
};
render() {
const { bloglist } = this.state;
var page = (
<div>
<h1>loading...</h1>
</div>
);
if (bloglist.length > 0)
page = bloglist.map((bloglistEntry) => {
return (
<React.Fragment key={bloglistEntry.id}>
<h3 className='mb-2'>
<a href='single.html'>{bloglistEntry.title}</a>
</h3>
<p className='mb-4'>{bloglistEntry.description}</p>
</React.Fragment>
);
});
return page;
}
}

How to pass argument to function in reactjs?

How can I send sport_id form getSport to getEvents to show each sports events?
Can I put getSport function to other component, call and use it in this component?
events json:
[
{
"id": "912653",
"time": "1536471082",
"time_status": "1",
"league": {
"id": "900",
"name": "Hong Kong 2nd Division",
"cc": "hk"
},
"home": {
"id": "13767",
"name": "Yau Tsim Mong",
"image_id": "193606",
"cc": "hk"
},
"away": {
"id": "63770",
"name": "Tuen Mun SA",
"image_id": "56045",
"cc": "hk"
},
"timer": {
"tm": 74,
"ts": 25,
"tt": "1",
"ta": 0
},
"scores": {}
}
]
sports json:
[
{
"id": 8,
"name": "Rugby Union",
"is_active": null,
"slug": "rugby-union"
}
]
Here is my code:
import React, { Component } from "react";
import axios from "axios";
import moment from "moment";
export default class Feutred extends Component {
state = {
sports: [],
events: [],
isLoading: true,
errors: null
};
getSports() {
axios
.get("/api/v1/sports.json")
.then(response =>
response.data.map(sport => ({
id: sport.id,
name: sport.name,
slug: sport.slug
}))
)
.then(sports => {
this.setState({
sports,
isLoading: false
});
})
.catch(error => this.setState({ error, isLoading: false }));
}
getEvents() {
axios
.get("/api/v1/events?sport_id=${sport_id}")
.then(response =>
response.data.map(event => ({
id: event.id,
time: event.time,
league: event.league,
time_status: event.time_status,
homeTeam: event.home,
awayTeam: event.away
}))
)
.then(events => {
this.setState({
events,
isLoading: false
});
})
.catch(error => this.setState({ error, isLoading: false }));
}
componentDidMount() {
this.getSports();
(this.interval = setInterval(
() => this.getEvents({ time: Date.now() }),
12000
));
}
componentWillUnmount() {
clearInterval(this.interval);
}
render() {
const { sports, isLoading } = this.state;
return (
<React.Fragment>
{!isLoading ? (
sports.map(sport => {
const { id, name } = sport;
return (
<div key={sport.id}>
<div className="text">
<p className="meta">
<span className="matchinfo">
<span className="block">time</span>
<span className="block">timestatus</span>
</span>
</p>
<h3>
home-team vs aya tream
</h3>
<p className="league">
<a className="watchlive" href="">
<span className="icon" />
<span>Watch live</span>
</a>
<span>{sport.name} - league cc - league name</span>
</p>
</div>
</div>
);
})
) : (
<p>Loading...</p>
)}
</React.Fragment>
);
}
}
Just destructure it - load sports in one component then render some <EventsLoadingComponent /> passing sport id as prop ...
HINT: Use if(isLoading) return <p>Loading...</p> in render before 'main return' - no need to use ternary operator in return JSX.
UPDATE:
render() {
const { sports, isLoading } = this.state;
if(isLoading) return <p>Loading...</p>
return (
<React.Fragment>
{sports.map(sport => <EventsLoadingComponent sport={sport}/>}
</React.Fragment>
);
}
Move getEvents into <EventsLoadingComponent/> - you'll be fething for events related to this.props.sport.id and render them. This way each of them can be separately updated.
Remember to use key in the topmost html element.
UPDATE #2:
can you please give your code comparison with my code ?
Your code - linear, procedural, 'flat template-driven', forcing async to be sync, all-in-one-component ... while html is a (flatten view of) tree structure.
React thinking (generally, not my code only) - more OO, building tree of objects closer related to data and view structure, giving them own responsibility (data handling, view). Easier to read, expand (destructure further details to components - even one-liners), suitable to decorating, easy to manage ... and reuse.
Often object in structure renders only passed children (or nothing) only providing functionality. Available level of complexity is greater, communication within this structure is far easier (and less dependent) than (it could be done) in html.
Something like this:
getEvents({ id }) {
axios
.get(`/api/v1/events?sport_id=${id}`)
...
}
componentDidMount() {
this.getSports()
.then(() => {
return Promise
.all(this.state.sports.map(this.getEvents))
});
...
}
Note:
You need to refine the way you save the data because you need to know which events are for which sport.

ReactJS: Fetching data from API

I'm having difficulties fetching data from an API in a React app with a simple standard template for fetching API data. The returned result from the console logs are blank arrays.
import React, {Component} from 'react';
import './App.css';
import Chart from './components/chart'
const API_URL = "http://ergast.com/api/f1/2016/1/results.json";
class App extends Component {
constructor(props) {
super(props)
this.state = {
results: [],
};
}
componentDidMount() {
fetch(API_URL)
.then(response => {
if (response.ok) {
return response.json()
}
else {
throw new Error ('something went wrong')
}
})
.then(response => this.setState({
results: response.MRData
})
)}
render() {
const {results} = this.state;
return (
<div className="App">
<Chart data={results}/>
</div>
);
}
}
export default App;
chart.js
import React from 'react';
import {XYPlot, XAxis, YAxis, VerticalGridLines, HorizontalGridLines, LineSeries} from 'react-vis';
const Chart = (props) => {
console.log(props.data);
const dataArr = props.data.map((d)=> {
return {x: d.RaceTable.Results.Driver.driverId, y: d.RaceTable.Results.position}
});
console.log(dataArr);
return (
<XYPlot
xType="ordinal"
width={1000}
height={500}>
<VerticalGridLines />
<HorizontalGridLines />
<XAxis title="Driver" />
<YAxis title="Race Finish Position" />
<LineSeries
data={dataArr}
style={{stroke: 'violet', strokeWidth: 3}}/>
</XYPlot>
);
}
export default Chart;
Really cannot figure out where I have gone wrong. I have set the state correctly with results: response.MRData isn't it? (MRData is the key of the JSON.) This is the structure of the json. API Link: http://ergast.com/api/f1/2016/1/results.json
JSON Example Response
{
"MRData": {
"xmlns": "http://ergast.com/mrd/1.0",
"RaceTable": {
"Races": [
{
"season": "2008",
"round": "1",
}
},
"Results": [
{
"position": "1",
"Driver": {
"driverId": "hamilton",
"permanentNumber": "44",
"code": "HAM",
"url": "http://en.wikipedia.org/wiki/Lewis_Hamilton",
"givenName": "Lewis",
"familyName": "Hamilton",
"dateOfBirth": "1985-01-07",
"nationality": "British"
},
}
},
.
.
.
]
}
]
}
}
}

Uncaught TypeError - issue mapping over json object in React

I have a react component which is pulling in user data, and should be able to display one of the values from the JSON object. However, I'm getting the following Uncaught TypeError: this.state.reasons.map is not a function error. I believe its because it is expecting an array vs an object, but not quite sure how to check or convert as needed. It should be able to map over the object and render the value of subscription.current_period_end
This is the JSON object:
{
"displayName": "username",
"email": "user#email.com",
"entitled": true,
"id": "23456789",
"subscription": {
"canceled_at": 1508519952,
"current_period_end": 1524765490,
"is_premium": true,
"is_renewing": false,
"plan_type": "Annual",
"saved": true,
"status": "Active"
},
"country": "us",
"language": "en"
}
React Component
class CancelConfirm extends React.Component {
constructor(props) {
super(props)
this.state = {
reasons: []
}
this.processData = this.processData.bind(this)
}
componentDidMount() {
this.fetchContent(this.processData)
}
fetchContent(cb) {
superagent
.get('/api/user')
.then(cb)
}
processData(data) {
this.setState({
reasons: data.body
})
}
render(props) {
const content = this.props.config.contentStrings
const reason = this.state.reasons.map((reason, i) => {
return (
<p key={i}>{reason.subscription.current_period_end}</p>
)
})
console.log(reason)
return (
<div className = 'confirm' >
<p className = 'confirm-subpara' >{reason}</p>
</div>
)
}
}
export default CancelConfirm
Your this.state.reasons is response object from JSON, object doesn't have map function it's only for iterator values such as new Array() if you are trying to show reason you would simply do
Currently I don't know why you are trying to iterate over object when it has no array array data, maybe I got your question wrong so please specify why you even did map instead of simple object reference.
var API_RESPONSE = {
"body": {
"displayName": "username",
"email": "user#email.com",
"entitled": true,
"id": "23456789",
"subscription": {
"canceled_at": 1508519952,
"current_period_end": 1524765490,
"is_premium": true,
"is_renewing": false,
"plan_type": "Annual",
"saved": true,
"status": "Active"
},
"country": "us",
"language": "en"
}
}
class CancelConfirm extends React.Component {
constructor(props) {
super(props)
this.state = {
reasons: false
}
this.processData = this.processData.bind(this)
}
componentDidMount() {
this.fetchContent(this.processData)
}
fetchContent(cb) {
setTimeout(() => {
cb(API_RESPONSE)
}, 2000)
}
processData(data) {
this.setState({
reasons: data.body
})
}
render(props) {
if (!this.state.reasons) return (<i>Loading ....</i>)
const reason = <p>{this.state.reasons.subscription.current_period_end}</p>
return (
<div className = 'confirm' >
<p className = 'confirm-subpara' >{reason}</p>
</div>
)
}
}
ReactDOM.render(<CancelConfirm />, document.getElementById('react'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="react"></div>
You can't map() over an Object. As such it would be better to do something like this:
render(props) {
const content = this.props.config.contentStrings
const { reasons } = this.state;
const keys = Object.keys(reasons);
return (
keys.map((k, i) => {
return <p key={i}>{reasons[k].subscription.current_period_end}</p>
})
)
}

Resources