Sending 'sent' data from a page to a component in Gatsby - reactjs

I have state data in my index.js page that is being sent to my details.js page via the Link component built into gatsby. From my details.js page, I am trying to send data to my component ChartData.js.
In details.js I can access my information by using {props.location.state.x}. Now I need the same data to be sent to my component and what I did was put Stock (Stock is the class name in ChartData.js) the comonent and set 'symbol' equal to the way I would reference data just like this: {<Stock symbol={props.location.state.symbol}/>}
Now under ChartData/.js when I try to reference symbol I get the error 'symbol is not defined'. Not sure if I am messing up the syntax of passing it or if you cannot do it this way.
index.js:
import React from "react"
import { Link } from "gatsby"
import axios from "axios"
import "../css/style.css"
import Layout from "../components/layout"
import { symbol } from "prop-types"
//import Stock from "../components/ChartData"
//import Characters from "../components/ChartData"
export default class index extends React.Component {
state = {
companyName: "",
previousClose: "",
marketCap: "",
change: "",
symbol: "",
topStocks: [],
Yearweekhigh: "",
Yearweeklow: "",
avgTotalVolume: "",
peRatio: "",
}
clickHandler = (event) => {
if (event.keyCode === 13) {
const query = event.target.value;
const API_KEY = '******************';
axios.get(`https://cloud.iexapis.com/stable/stock/${query}/quote?token=${API_KEY}`)
.then(res => {
const companyName = res.data['companyName'];
this.setState({ companyName })
const previousClose = res.data['previousClose'];
this.setState({ previousClose })
const marketCap = res.data['marketCap'];
this.setState({ marketCap })
const change = res.data['change'];
this.setState({ change })
const symbol = res.data['symbol'];
this.setState({ symbol })
const Yearweekhigh = res.data['week52High'];
this.setState({ Yearweekhigh })
const Yearweeklow = res.data['week52Low'];
this.setState({ Yearweeklow })
const avgTotalVolume = res.data['avgTotalVolume'];
this.setState({ avgTotalVolume })
const peRatio = res.data['peRatio'];
this.setState({ peRatio })
const open = res.data['open'];
this.setState({ open })
const high = res.data['high'];
this.setState({ high })
const low = res.data['low'];
this.setState({ low })
const volume = res.data['volume'];
this.setState({ volume })
})
}
}
render() {
return (
<Layout>
<div class = "main-div">
<input type="search" class="main-search" onKeyDown={event => this.clickHandler(event)}/>
<table>
<tr>
<th>Ticker-Symbol</th>
<th>Market Cap</th>
<th>Previous Close</th>
</tr>
<tr>
<td>
<Link to='/details/' state={{
setState: this.state.symbol,
companyName: this.state.companyName,
previousClose: this.state.previousClose,
marketCap: this.state.marketCap,
change: this.state.change,
Yearweekhigh: this.state.Yearweekhigh,
Yearweeklow: this.state.Yearweeklow,
avgTotalVolume: this.state.avgTotalVolume,
peRatio: this.state.peRatio,
open: this.state.open,
high: this.state.high,
low: this.state.low,
volume: this.state.volume,
symbol: this.state.symbol
}}>
{this.state.symbol}</Link>
</td>
<td>{this.state.marketCap}</td>
<td>{this.state.previousClose}</td>
</tr>
</table>
</div>
<div>
{
this.state.topStocks.length && this.state.topStocks.map(stock => (
<h1>{stock.symbol}</h1>
))
}
</div>
</Layout>
)
}
}
details.js
//import { Link } from "gatsby"
import axios from 'axios';
import React, { useEffect, useState } from 'react';
import Layout from '../components/layout';
import "../css/style.css"
import Stock from "../components/ChartData"
const Details = props => {
const [yourState, setYourState] = useState('');
useEffect(() => {
}, []);
return <Layout>
<div>
<h1 class="details-company-name">{props.location.state.companyName}</h1>
<div class = "details-div">
<div class="details-div-1">
<p>Open <h2>{props.location.state.open}</h2> </p>
<p>High <h2>{props.location.state.high}</h2> </p>
<p>Low <h2>{props.location.state.low}</h2> </p>
<p>52 WK HIGH <h2>{props.location.state.Yearweekhigh}</h2> </p>
<p>52 WK LOW <h2>{props.location.state.Yearweeklow}</h2> </p>
<p>{props.location.state.symbol}</p>
</div>
<div class="details-div-2">
<p>VOLUME <h2>{props.location.state.volume}</h2></p>
<p>AVG VOL <h2>{props.location.state.avgTotalVolume}</h2> </p>
<p>MKT CAP <h2>{props.location.state.marketCap}</h2></p>
<p>P/E RATIO <h2>{props.location.state.peRatio}</h2></p>
<p>DIV/YIELD</p>
</div>
</div>
</div>
<Stock symbol={props.location.state.symbol}/>
</Layout>;
};
export default Details;
ChartData.js
import React from 'react'
import Plot from 'react-plotly.js'
class Stock extends React.Component {
constructor(props) {
super(props);
this.state = {
stockChartXValues: [],
stockChartYValues: [],
};
}
componentDidMount() {
this.fetchStock();
}
fetchStock() {
const pointerToThis = this;
const API_KEY = '*****************';
let API_CALL = `https://cloud.iexapis.com/stable/${symbol}/aapl/chart/5y?token=${API_KEY}`;
let stockChartXValuesFunction = [];
let stockChartYValuesFunction = [];
fetch(API_CALL)
.then(function (response) {
return response.json();
})
.then(function (data) {
for (var x in data) {
stockChartXValuesFunction.push(x);
stockChartYValuesFunction.push(
data[x]['uOpen']
);
pointerToThis.setState({
stockChartXValues: stockChartXValuesFunction,
stockChartYValues: stockChartYValuesFunction,
});
}
})
}
render() {
return (
<div>
<Plot
data={[
{
x: this.state.stockChartXValues,
y: this.state.stockChartYValues,
type: "scatter",
mode: "lines+markers",
marker: {color: "red"}
},
]}
layout={{ width: 720, height: 440, title: "A Fancy Plot"}}
/>
</div>
)
}
}
export default Stock

You have a few issues there:
Everything you pass through props must be received by the child component accessing to those props. So instead of symbol, you must this.props.symbol. Since you are not destructuring your props:
let API_CALL =
`https://cloud.iexapis.com/stable/${this.props.symbol}/aapl/chart/5ytoken=${API_KEY}`;
To avoid wrong or empty calls, I would ensure that you have your props properly set and I would add a condition like:
componentDidMount() {
if(props.symbol) this.fetchStock();
}
You may need to add a constructor in your file to gather props:
constructor(props) {
super(props);
this.state = {}; // remove if not needed
}

Related

Axios post method in react isn't working: suggestions?

I'm trying to update a database with a user's text input, and it isn't working, even after trying a bunch of different approaches.
The text input is controlled by the following component:
import React from 'react'
class Dogue extends React.Component {
constructor(props){
super(props)
this.state = {
id: '',
nameInput:''
}
this.handleChange = this.handleChange.bind(this)
this.handleSubmit = this.handleSubmit.bind(this)
}
handleChange(e) {
this.setState({
id: Date.now(),
nameInput: e.target.value
})
}
handleSubmit(e){
e.preventDefault()
this.props.inputFunction(this.state.nameInput, this.state.id)
}
render(){
console.log(this.props.id)
return (
<div className = 'dogue-container'>
<img className = 'img' src = {this.props.dogList}/>
<br/>
<form onSubmit = {this.handleSubmit} className = 'form'>
<input
onChange ={this.handleChange}
className ='input'
type = 'text'
placeholder = 'Enter dog name'
/>
<br/>
<button className = 'button'>Submit</button>
</form>
<h2 className = 'text'>Name: {this.props.name} </h2>
</div>
)
}
}
export default Dogue
and the state update and post is controlled by the App component:
import React, { Component } from "react";
import './styles.css'
import DogList from "./DogList";
import axios from "axios";
class App extends React.Component {
constructor() {
super();
this.state = {
loading: false,
dog: [],
dogName: [],
newName:''
};
this.updateStateWithInput = this.updateStateWithInput.bind(this)
}
setData = async () => {
const x = await fetch("https://dog.ceo/api/breed/hound/images");
const y = await x.json();
const z = await y.message;
let newArr = [];
for (let i = 0; i < z.length; i++) {
if (i <= 9) {
newArr.push(z[i]);
}
}
return newArr;
};
async componentDidMount() {
this.setState({
loading: true
});
let dogPromise = await this.setData();
let dogNamePromise = await axios.get('http://localhost:3000/dogs');
this.setState({
loading: false,
dog: dogPromise,
dogName: dogNamePromise.data
});
}
//Here is the function to update state and make axios post
async updateStateWithInput (nameInput,id) {
let newDog={id:id, dogName:nameInput}
this.setState({
dogName: this.state.dogName.push(newDog)
})
await axios.post('http://localhost:3000/dogs', this.state.dogName)
.then(res => {
console.log(res)
})
}
render() {
return this.state.loading ? (
<h1 className = 'text'> Dogues Loading.....</h1>
) : (
<div>
<h1 className = 'text'>Rate My Dogue</h1>
<DogList
dogs={this.state.dog}
name={this.state.dogName}
inputFunction = {this.updateStateWithInput}
/>
</div>
);
}
}
export default App
Basically, all I'm trying to do is update an array of objects, with a new object - example as follows:
//existing array:
[
{
id: 1,
dogName: 'bruce',
},
{
id: 2,
dogName: 'borker',
},
{
id: 3,
dogName: 'henry',
},
];
//new object to be pushed into array:
{id: id of some sort, dogName: the text input from the user}
Either you use await or use then, cannot use both:
const res = await axios.post('http://localhost:3000/dogs', this.state.dogName);
console.log(res)

How to make an axios POST request in React?

So, some context: Users submit a dog name via a text input, and this is controlled by the 'Dogue.jsx' component:
import React from 'react';
class Dogue extends React.Component {
constructor(props) {
super(props);
this.state = {
id: props.id,
nameInput: '',
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(e) {
this.setState({
nameInput: e.target.value,
});
}
handleSubmit(e) {
e.preventDefault();
this.props.inputFunction(this.state.nameInput);
}
render() {
console.log(this.props.id);
return (
<div className="dogue-container">
<img className="img" src={this.props.dogList} />
<br />
<form onSubmit={this.handleSubmit} className="form">
<input
onChange={this.handleChange}
className="input"
type="text"
placeholder="Enter dog name"
/>
<br />
<button className="button">Submit</button>
</form>
<h2 className="text">Name: {this.props.name} </h2>
</div>
);
}
}
export default Dogue;
The submitted information is then passed to 'App.jsx', where it is used to update state:
import React, {Component} from 'react';
import './styles.css';
import DogList from './DogList';
import axios from 'axios';
class App extends React.Component {
constructor() {
super();
this.state = {
loading: false,
dog: [],
dogName: [],
};
this.updateStateWithInput = this.updateStateWithInput.bind(this);
}
setData = async () => {
const x = await fetch('https://dog.ceo/api/breed/hound/images');
const y = await x.json();
const z = await y.message;
let newArr = [];
for (let i = 0; i < z.length; i++) {
if (i <= 9) {
newArr.push(z[i]);
}
}
return newArr;
};
async componentDidMount() {
this.setState({
loading: true,
});
let dogPromise = await this.setData();
let dogNamePromise = await axios.get('http://localhost:3000/dogs');
this.setState({
loading: false,
dog: dogPromise,
dogName: dogNamePromise.data,
});
}
updateStateWithInput(nameInput) {
//Here is where state is updated.
//change state, then use axios.post to submit data
}
render() {
return this.state.loading ? (
<h1 className="text"> Dogues Loading.....</h1>
) : (
<div>
<h1 className="text">Rate My Dogue</h1>
<DogList
dogs={this.state.dog}
name={this.state.dogName}
inputFunction={this.updateStateWithInput}
/>
</div>
);
}
}
export default App;
The updated state, I imagine, will be used in the axios post request to submit data to the database. So, I've got input data being sent from Dogue to App, I'm just not sure what to do now? The information currently in state looks as follows:
[
{
id: 1,
dogName: 'bruce',
},
{
id: 2,
dogName: 'borker',
},
{
id: 3,
dogName: 'henry',
},
];
I should also show my map function, in DogList.jsx:
import React from 'react';
import Dogue from './Dogue';
const DogList = (props) => {
return (
<div className="img-container">
{props.dogs.map((doggie, index) => {
return (
<Dogue
id={props.name[index] && props.name[index].id}
key={index}
dogList={doggie}
name={props.name[index] && props.name[index].dogName}
inputFunction={props.inputFunction}
/>
);
})}
</div>
);
};
export default DogList;
You can send a POST request with axios by calling:
axios.post(url, data, options);
It’s similar to the way you called the get method to make a GET request.
I’m leaving this axios cheat sheet here since it’s really useful until you get the hang of it:
https://kapeli.com/cheat_sheets/Axios.docset/Contents/Resources/Documents/index

How to add page number to the URL

Could someone please tell me how can I add page number to my url. The component is as follows:
/** NPM Packages */
import React, { Component } from "react";
import { connect } from "react-redux";
import { Spinner, Pagination } from "react-bootstrap";
//import styles from "./App.module.css";
/** Custom Packages */
import List from "../List";
//import fetchCategories from "../../../actions/configuration/category/fetchCategories";
import deleteCategory from "../../../actions/configuration/category/deleteCategory";
import API from "../../../../app/pages/utils/api";
class Category extends Component {
constructor(props) {
super(props);
this.state = {
mesg: "",
mesgType: "",
isLoading: true,
total: null,
per_page: null,
current_page: 1,
pdata: []
};
this.fetchCategoriesAPI = this.fetchCategoriesAPI.bind(this);
}
fetchCategoriesAPI = async pno => {
await API.get("categories?offset=" + (pno.index+1))
.then(res => this.setState({ pdata: res.data }))
.then(() => this.props.passToRedux(this.state.pdata))
.catch(err => console.log(err));
};
componentDidMount = async () => {
const { state } = this.props.location;
if (state && state.mesg) {
this.setState({
mesg: this.props.location.state.mesg,
mesgType: this.props.location.state.mesgType
});
const stateCopy = { ...state };
delete stateCopy.mesg;
this.props.history.replace({ state: stateCopy });
}
this.closeMesg();
await this.fetchCategoriesAPI(1);
this.setState({ isLoading: false });
};
onDelete = async id => {
this.props.removeCategory(id);
await deleteCategory(id).then(data =>
this.setState({ mesg: data.msg, mesgType: "success" })
);
this.closeMesg();
};
closeMesg = () =>
setTimeout(
function() {
this.setState({ mesg: "", mesgType: "" });
}.bind(this),
10000
);
/** Rendering the Template */
render() {
let activePage = this.state.pdata.currPage;
let items = [];
let totalPages = Math.ceil(this.state.pdata.totalCount / 10);
for (let number = 1; number <= totalPages; number++) {
items.push(
<Pagination.Item key={number} active={number == activePage}>
{number}
</Pagination.Item>
);
}
const paginationBasic = (
<div>
<Pagination>
{items.map((item,index)=>{
return <p key={index} onClick={() => this.fetchCategoriesAPI({index})}>{item}</p>
})}
</Pagination>
<br />
</div>
);
const { mesg, mesgType, isLoading } = this.state;
return (
<>
{mesg ? (
<div
className={"alert alert-" + mesgType + " text-white mb-3"}
role="alert"
>
{mesg}
</div>
) : (
""
)}
{isLoading ? (
<div className="container-fluid">
<h4
className="panel-body"
style={{ "text-align": "center", margin: "auto" }}
>
Loading
<Spinner animation="border" role="status" />
</h4>
</div>
) : (
<div>
<List
listData={this.props.categories}
listName="category"
_handleDelete={this.onDelete.bind(this)}
/>
{paginationBasic}
</div>
)}
</>
);
}
}
const matchStatestoProps = state => {
return { categories: state.categories };
};
const dispatchStatestoProps = dispatch => {
return {
passToRedux: pload =>
dispatch({ type: "FETCH_CATEGORIES", payload: pload }),
removeCategory: id => dispatch({ type: "DELETE_CATEGORY", payload: id })
};
};
export default connect(matchStatestoProps, dispatchStatestoProps)(Category);
the route is as follows:
<Route exact path="/categories/:page?" component={Category} />
So basically I want the page number to be displayed in the URL. Also if I change the page number, the data should load the corresponding page. Please help me
Could someone please help me out?
In a class component:
Your router will pass match in as a prop. When your component mounts, get this.props.match.params.page and load the data accordingly:
class MyComponent extends React.Component {
componentDidMount () {
// get the 'page' param out of the router props.
// default to 0 if not specified.
const { page = 0 } = this.props.match.params;
// it comes in as a string, parse to int
const p = parseInt(page, 10);
// do whatever you need to do (load data, etc.)
}
}
In a function component:
In a function component, you can get the page param via react-router's useParams hook:
import { useParams } from 'react-router-dom';
function MyComponent () {
const { page } = useParams(); // get the 'page' router param
const p = parseInt(page, 10); // comes in as a string, convert to int
// do whatever you need to do with it
}
If you need prev/next navigation you can deduce those page numbers from the current page.
I made this quick example that demonstrates how to access and use the route's url parameters via react router's useParams hook and how to do it via the match prop with a class component.
You can get page number from props like this:
const matchStatestoProps = (state, ownProps) => {
return { id: ownProps.match.params.id; categories: state.categories };
};
In your routes:
<Route path="/page/:id" component={Page} />

Can't loop through array using map loop with react.js & axios

I am fetching data from an API, i get all the data in the console.log(this.state.reviews) but i can't access the data after the map loop is done.
The Brand data is accessible, the only problem is with the map loop (the reviews array)
I know it has something with async calls! i have found this Can't access values for Axios calls in map loop Does anyone know how to adapt it with the code below ;-)
class App extends React.Component {
constructor(props){
super(props);
this.state = {
reviews : []
};
}
componentDidMount() {
axios.get(`https://api.prime.com/businesses/reviews/${this.props.BrandId}`)
.then((res) => {
const brand = res.data;
this.setState({
// Reviews data
reviews : brand.reviews.items,
// Brand data
logo : brand.business.logo,
name : brand.business.name,
voters : brand.business.voters,
url : brand.business.url
});
})
}
render() {
console.log(this.state.reviews);
return (
<div className="widget-wrapper">
<OwlCarousel className="reviews-container"
loop
nav
margin={12}
dots={false}
items={5}
autoplay={true}
>
{this.state.reviews.map((review) => {
<div key={review.id} className="review-item">
<a href={this.state.url + '/' + review.slug} target="_blank" rel="nofollow">
<div className="review-heading">{review.subject}</div>
<div className="review-content">{review.message}</div>
</a>
</div>
})}
</OwlCarousel>
</div>
)
}
}
i figured out your problem you need to wait for the promise to resolve that why your state is not set for doing this u can use async await like
import React from "react";
import ReactDOM from "react-dom";
import axios from "axios";
import "./styles.css";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
posts: []
};
}
componentDidMount() {
this.loadData();
}
loadData = async () => {
const res = await axios.get("https://jsonplaceholder.typicode.com/posts");
if (res) {
const posts = res.data;
this.setState({
posts: [...posts]
});
}
};
renderPost = () => {
return this.state.posts
? this.state.posts.map(data => (
<div style={{ color: "black" }}>
<h5>{data.title}</h5>
<p>{data.body}</p>
</div>
))
: "loading...";
};
render() {
console.log(this.state.posts);
return <div> {this.renderPost()}</div>;
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
full app is here check it out : codesandbox

ReactJs update state from Select List

I have a react-select component with options from a axios GET, I want my Car component to display an image from a url stored in the component state when the option is selected.
I am using componentDidMount and componentDidUpdate, however, in componentDidUpdate, this.getImage(capID); keeps firing, how can I prevent this and evoke it once?
import React from "react";
import axios from "axios";
import { Panel } from "react-bootstrap";
export default class CarList extends React.Component {
constructor(props) {
super(props);
this.state = {
imageSrc: ""
};
this.getImage = this.getImage.bind(this);
}
getImage(id) {
axios
.get(`xxx${id}`)
.then(response => {
this.setState({
imageSrc: response.data.url
});
})
.catch(error => {
console.log(error);
});
}
componentDidMount() {
const {
agrNo,
balloon,
bpid,
capID,
dealer,
derivative,
id,
make,
model,
name
} = this.props.car;
this.getImage(capID);
}
componentDidUpdate() {
const {
agrNo,
balloon,
bpid,
capID,
dealer,
derivative,
id,
make,
model,
name
} = this.props.car;
this.getImage(capID);
}
render() {
let car = this.props.car;
const {
agrNo,
balloon,
bpid,
capID,
dealer,
derivative,
id,
make,
model,
name
} = this.props.car;
return (
<div className="car-details">
<Panel header={name}>
<div className="flex-container">
<div className="flex-item">
{this.state.imageSrc && (
<img
src={this.state.imageSrc}
alt={model}
className="car-details__image"
/>
)}
</div>
<div className="flex-item">
<p>{car.Plot}</p>
<div className="car-info">
<div>
<span>Genre:</span> {car.Genre}
</div>
</div>
</div>
</div>
</Panel>
</div>
);
}
}
App:
import React, { Component } from "react";
import logo from "./logo.svg";
import axios from "axios";
import { Alert } from "react-bootstrap";
import AsyncSelect from "react-select/lib/Async";
import CarList from "./CarList";
import "react-select/dist/react-select.css";
import "./App.css";
class App extends Component {
constructor(props) {
super(props);
this.state = {
car: {}
};
}
getCars(e) {
return axios
.get(`xxx${e}`)
.then(response => {
var arr = [];
if (response.data !== undefined) {
var searchResults = response.data.length;
for (var i = 0; i < searchResults; i++) {
arr.push({
label: `${response.data[i].name} - ${response.data[i].id}`,
value: response.data[i].id
});
}
}
return {
options: arr
};
})
.catch(error => {
console.log(error);
});
}
getCar(e) {
axios
.get(`xxx}`)
.then(response => {
this.setState({
car: response.data
});
})
.catch(error => {
console.log(error);
});
}
render() {
const {
car: { id }
} = this.state;
return (
<div className="container">
<AsyncSelect
name="carOwner"
value="ABC"
cacheOptions
defaultOptions
loadOptions={this.getCars}
onChange={this.getCar.bind(this)}
/>
{id ? (
<CarList car={this.state.car} />
) : (
<Alert bsStyle="info">
<p>Enter a surname above to begin...</p>
</Alert>
)}
</div>
);
}
}
export default App;
componentDidUpdate will fire whenever any prop or state for this component has changed (checkout the official docs for more info).
You're changing the state inside the getImage(id) function, and every time that happens, the componentDidUpdate function will fire in your case, which will call the getImage function again, which will then became an infinite loop.
You need to check if the capID prop has changed, in order to decide if you should make the call again or not:
componentDidUpdate(oldProps) {
const {
agrNo,
balloon,
bpid,
capID,
dealer,
derivative,
id,
make,
model,
name
} = this.props.car;
const oldCapID = oldProps.capID;
if (capID !== oldCapID) {
this.getImage(capID);
}
}

Resources