How to implement Finnhub.io stock API into a React App - javascript

I am new to web development and I am having trouble in a project I am creating. The end goal of this app is to make a stock reference app where the user can look up information about different stocks. It would end up being similar to the "Stocks" app on iOS devices. However, at this point I am still struggling to make this app work because I can't seem to figure out the API call. I managed to get the data that I wanted to get, however it doesn't work exactly how I wanted to.
The first issue with my code is that it takes two clicks of the submit button to actually get the data and then set the state to match the data. The second issue is that the once the data is received and is displayed by the StockItem component, the stockPrice state will go back to zero after a couple of seconds.
Here is my App.js
import './App.css';
import StockItem from "./StockItem"
class App extends React.Component {
constructor (props) {
super(props)
this.state = {
symbol: "",
stockPrice: 0,
value: "",
stockName: "",
}
this.handleChange = this.handleChange.bind(this)
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange (event) {
this.setState({
value: event.target.value
})
console.log(this.state.value)
}
handleSubmit(event) {
this.setState({
symbol: this.state.value
})
const finnhub = require('finnhub');
const api_key = finnhub.ApiClient.instance.authentications['api_key'];
api_key.apiKey = "API Key"
const finnhubClient = new finnhub.DefaultApi()
finnhubClient.quote(this.state.symbol, (error, data, response) => {
this.setState({stockPrice: (data.c)})
});
finnhubClient.companyProfile2({'symbol': this.state.symbol}, (error, data, response) => {
this.setState({stockName: (data.name)})
console.log(data.name)
});
console.log("update was called")
console.log(this.state.stockPrice)
event.preventDefault();
}
render () {
return (
<div>
<form onSubmit={this.handleSubmit}>
<label>
Stock Symbol:
<input type = "text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
<div>
<StockItem symbol = {this.state.symbol} price = {this.state.stockPrice} name = {this.state.stockName}/>
</div>
</div>
)
}
}
export default App;
Here is my StockItem.js component
function StockItem (props) {
return (
<div className = "stockItem">
<h2>Stock Symbol: {props.symbol} Stock Name: {props.name}</h2>
<h3>Stock Price: {props.price}</h3>
</div>
)
}
export default StockItem
Here is my Index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
reportWebVitals();
Also I am not sure if it is ok to share my API key publicly so I purposely left it out. If I can safely share it and it would help with solving the problem then please let me know. Again, I am new to this and there are a lot of things in this that I just don't know.

The problem here is the use of the state inside the handleSubmit function. this.setState will save the state at some point in the future. Change your handleChange and handleSubmit like the following (You'll also want to force the stock symbol to uppercase.):
handleChange(event) {
const value = `${event.target.value}`.toUpperCase();
this.setState({ value });
console.log(value);
}
handleSubmit(event) {
const symbol = this.state.value; // Use this as your symbol
this.setState({ symbol });
console.log(symbol);
const finnhub = require('finnhub');
const api_key = finnhub.ApiClient.instance.authentications['api_key'];
api_key.apiKey = "<API Key>";
const finnhubClient = new finnhub.DefaultApi()
finnhubClient.quote(symbol, (error, data, response) => {
console.log(data);
this.setState({stockPrice: (data.c)})
});
finnhubClient.companyProfile2({'symbol': symbol}, (error, data, response) => {
this.setState({stockName: (data.name)})
console.log(data.name)
});
console.log("update was called")
console.log(this.state.stockPrice)
event.preventDefault();
}

Related

How to receive data through props React and send it back?

I'm getting a placeholder value through props in my input component and I need to send the input value back to the main class. I'm using React but I'm not getting it. Follow my code.... The value I need to send is the value of 'usuario'
import React, { useState } from 'react';
import { EntradaDados } from './styled';
const PesqDados = ({placeholder, usuario}) => {
const [usuario, SetUsuario] = useState('')
const setValor =(e)=>{
SetUsuario(e.target.value);
}
console.log(usuario);
return(
<EntradaDados
onChange={setValor}
placeholder={placeholder}
>
</EntradaDados>
);
}
export default PesqDados;
You need to add a callback prop (onUsuarioChange) to your PesqDados component and call it with the new usuario. You have two options:
Call it from a useEffect with usuario as dependency (assuming usuario could get updated from somewhere other than setValor.
Call it from setValor, assuming that's the only place where usuario is going to get updated from.
This is how this should look:
import React, { useState } from 'react';
import { EntradaDados } from './styled';
const PesqDados = ({
placeholder,
usuario,
onUsuarioChange
}) => {
const [usuario, setUsuario] = useState('');
// Option 1:
useEffect(() => {
onUsuarioChange(usuario);
}, [usuario]);
const setValor = (e) => {
const nextUsuario = e.target.value;
setUsuario(nextUsuario);
// Option 2:
onUsuarioChange(nextUsuario);
};
return (
<EntradaDados
onChange={ setValor }
placeholder={ placeholder } />
);
}
export default PesqDados;
After studying properly, I found that I don't need to implement the function in the component page. I just needed to create a hook that calls the component's OnChange property on the component's page and then create a function just in the place where the component is installed. In this case, App.js.
Page Component....
const PesqDados = ({placeholder, Dados}) => {
return(
<EntradaDados
onChange={Dados}
placeholder={placeholder}
>
</EntradaDados>
);
}
export default PesqDados;
Page App.js
function App() {
const [usuario, SetUsuario] = useState('Aguardando Dados...')
const setValor =(e)=>{
SetUsuario(e.target.value);
}
const teste = ()=>{
alert("O usuário digitado foi : "+usuario)
};
return (
<>
<div className='divRepos'>
<div className='bloco'>
<div className='pesquisar'>
<p>{usuario}</p>
<PesqDados
placeholder={"Digite um username válido"}
Dados={setValor}
/>
<Button nomeBotao={"Pesquisar Perfil"}
onClick={teste}/>
</div>
...

Pass data between two independent components in ReactJS

I'm building a web-application with ReactJS and that needs me to implement a Search. So far, the search has been implemented (I'm using Fuse.js library for this) using a form with an input element. The form is implemented in the NavBar and after the user types a search-query, he is redirected to 'localhost:3000/search' URL where he can see the results corresponding to his query.
Here is the code I'm using for the form in the SearchBar.
import React, { useState } from 'react';
import { Form, FormControl } from 'react-bootstrap';
import { ReactComponent as SearchLogo } from '../../lib/search-logo.svg';
const SearchBar = () => {
const [searchQuery, setSearchQuery] = useState({ query: '' });
const searchQueryHandler = (event) => {
event.preventDefault();
setSearchQuery({ query: event.target.value });
};
const onFormSubmit = (event) => {
event.preventDefault();
window.location.href = "/search";
}
return (
<Form inline className="nav-search-form" onSubmit={onFormSubmit}>
<SearchLogo className="search-logo" />
<FormControl
type="text"
placeholder="Search spaces of interest"
className="nav-search"
value={searchQuery.query}
onChange={searchQueryHandler} />
</Form>
);
}
export default SearchBar;
I need to display the corresponding results in another SearchPage which will take the query from this component after submission and then display the results. Here is the code I have written for it.
import React, { useState, useRef } from 'react';
import { Col, Container, Row } from 'react-bootstrap';
import SpaceCardGrid from '../space-card-grid/space-card-grid';
import useSpaces from '../../utils/firebase/hooks/useSpaces';
import moment, { Moment } from 'moment';
import { roundTime } from '../../utils/date';
import Fuse from 'fuse.js';
const SearchPage = (queries) => {
const [date, setDate] = useState<[Moment, Moment]>([moment(new Date()), moment(new Date())]);
const [time, setTime] = useState([roundTime(), roundTime(30)]);
const [dateRangeType, setDateRangeType] = useState<'week' | 'day' | 'now'>('day');
const spaceCardGridRef = useRef(null);
const spaces = useSpaces(dateRangeType, date, time, 0);
const options = {
shouldSort: true,
keys: ['title', 'description'],
};
const fuse = new Fuse(spaces, options);
let filteredspaces = spaces;
if (queries.query !== '') {
const result = fuse.search(queries.query);
console.log(result);
filteredspaces = [];
result.forEach((space) => {
filteredspaces.push(space.item);
});
}
return (
<div>
<Container fluid className="bottom-container">
<Row style={{ justifyContent: 'center', alignItems: 'flex-start' }}>
<Col>
<div className="grid-root">
<SpaceCardGrid spaces={filteredspaces} spaceCardGridRef={spaceCardGridRef} />
</div>
</Col>
</Row>
</Container>
</div>
);
};
export default SearchPage;
Just for additional information useSpaces() is a function that gives me all the data (and it does so correctly), and filteredspaces is the final results array that I wish to display on the screen. All these things are perfectly working.
I'm stuck on how to pass the query between the two components though. The queries I have used in SearchPage(queries) is a dummy variable. I'm new to React, and I have learned about Redux, but it seems a lot of work (I might be wrong) for simply passing a value between 2 components. As you can clearly observe, the components aren't related but are independent. Is there a simple way to do this? Any help will be greatly appreciated!
you could use useContenxt along with useReducer hooks for a simpler state structure. I created a small example here. you can find more reference at docs
basically at root from your appplication you would start by creating a context and pass dispatch and query as values to your Provider:
export const QueryDispatch = React.createContext("");
const initialState = { query: "" };
export default function App() {
const [{ query }, dispatch] = useReducer(queryReducer, initialState);
return (
<QueryDispatch.Provider value={{ dispatch, query }}>
<SearchBar />
<SearchPage />
</QueryDispatch.Provider>
);
}
where queryReducer could be like:
export default function (state, action) {
switch (action.type) {
case 'update':
return {query: action.query};
default:
return state;
}
}
and at any component you could consume from your provider:
at your searchBar
import React, { useContext } from "react";
import { QueryDispatch } from "./App";
const SearchBar = () => {
const const { dispatch, query } = useContext(QueryDispatch);
const searchQueryHandler = (event) => {
dispatch({ type: "update", query: e.target.value })
};
..code
<FormControl
type="text"
placeholder="Search spaces of interest"
className="nav-search"
value={query}
onChange={searchQueryHandler} />
and at your SearchPage
import React, { useContext } from "react";
import { QueryDispatch } from "./App";
const SearchPage = () => {
const { query } = useContext(QueryDispatch);

Binding API Data from React Parent Component to Child Components

I'm new to React and am tripping over this issue.
Have read couple of tutorials and questions here to find out about how Parent & Child Components should communicate. However, I am unable to get the data to populate the fields + make it editable at the same time. I'll try explain further in code below:
Parent Component:
...imports...
export default class Parent extends Component {
constructor(props) {
this.state = {
data: null
};
}
componentDidMount() {
API.getData()
.then((response) => {
this.setState({ data: response });
// returns an object: { name: 'Name goes here' }
})
}
render() {
return (
<Fragment>
<ChildComponentA data={this.state.data} />
<ChildComponentB data={this.state.data} />
</Fragment>
);
}
}
Input Hook: (source: https://rangle.io/blog/simplifying-controlled-inputs-with-hooks/)
import { useState } from "react";
export const useInput = initialValue => {
const [value, setValue] = useState(initialValue);
return {
value,
setValue,
reset: () => setValue(""),
bind: {
value,
onChange: event => {
setValue(event.target.value);
}
}
};
};
ChildComponent:* (This works to allow me to type input)
import { Input } from 'reactstrap';
import { useInput } from './input-hook';
export default function(props) {
const { value, setValue, bind, reset } = useInput('');
return (
<Fragment>
<Input type="input" name="name" {...bind} />
</Fragment>
);
}
ChildComponent Component:
(Trying to bind API data - Input still editable but data is still not populated even though it is correctly received.. The API data takes awhile to be received, so the initial value is undefined)
import { Input } from 'reactstrap';
import { useInput } from './input-hook';
export default function(props) {
const { value, setValue, bind, reset } = useInput(props.data && props.data.name || '');
return (
<Fragment>
<Input type="input" name="name" {...bind} />
</Fragment>
);
}
ChildComponent Component:
(Trying to use useEffect to bind the data works but input field cannot be typed..)
I believe this is because useEffect() is trigged every time we type.. and props.data.name is rebinding its original value
import { Input } from 'reactstrap';
import { useInput } from './input-hook';
export default function(props) {
const { value, setValue, bind, reset } = useInput(props.data && props.data.name || '');
useEffect(() => {
if(props.data) {
setValue(props.data.name);
}
});
return (
<Fragment>
<Input type="input" name="name" {...bind} />
</Fragment>
);
}
I can think of a few tricks like making sure it binds only once etc.. But I'm not sure if it is the correct approach. Could someone share some insights of what I could be doing wrong? And what should be the correct practice to do this.
To iterate, I'm trying to bind API data (which takes awhile to load) in parent, and passing them down as props to its children. These children have forms and I would like to populate them with these API data when it becomes available and yet remain editable after.
Thanks!
Basic way to create your Parent/Child Component structure is below, I believe. You don't need a class-based component for what you are trying to achieve. Just add an empty array as a second argument to your useEffect hook and it will work as a componentDidMount life-cycle method.
Parent component:
import React, {useState, useEffect} from 'react';
export default const Parent = () => {
const [data, setData] = useState({});
const [input, setInput] = useState({});
const inputHandler = input => setInput(input);
useEffect(() => {
axios.get('url')
.then(response => setData(response))
.catch(error => console.log(error));
}, []);
return <ChildComponent data={data} input={input} inputHandler={inputHandler} />;
};
Child Component:
import React from 'react';
export default const ChildComponent = props => {
return (
<div>
<h1>{props.data.name}</h1>
<input onChange={(e) => props.inputHandler(e.target.value)} value={props.input} />
</div>
);
};

AWS Appsync graphqlMutation helper not updating query

I'm following this tutorial: https://egghead.io/lessons/react-execute-mutations-to-an-aws-appsync-graphql-api-from-a-react-application
I have a simple todo react app hooked up to AppSync via amplify. The queries and mutations were autogenerated by Amplify.
Using the graphqlMutation helper, my query is supposed to be automatically updated after running my mutations, but it's not working. Upon refresh I do see the mutations are updating the AppSync backend, but I also expect it to update immediately with an optimistic response.
Here is the code:
import React, { Component } from "react";
import gql from "graphql-tag";
import { compose, graphql } from "react-apollo";
import { graphqlMutation } from "aws-appsync-react";
import { listTodos } from "./graphql/queries";
import { createTodo, deleteTodo } from "./graphql/mutations";
class App extends Component {
state = { todo: "" };
addTodo = async () => {
if (this.state.todo === "") {
return;
}
const response = await this.props.createTodo({
input: {
name: this.state.todo,
completed: false
}
});
this.setState({ todo: "" });
console.log("response", response);
};
deleteTodo = async id => {
const response = await this.props.deleteTodo({ input: { id } });
console.log("response", response);
};
render() {
return (
<div>
<div>
<input
onChange={e => this.setState({ todo: e.target.value })}
value={this.state.todo}
placeholder="Enter a name..."
/>
<button onClick={this.addTodo}>Add</button>
</div>
{this.props.todos.map(item => (
<div key={item.id}>
{item.name}{" "}
<button onClick={this.deleteTodo.bind(this, item.id)}>
remove
</button>
</div>
))}
</div>
);
}
}
export default compose(
graphqlMutation(gql(createTodo), gql(listTodos), "Todo"),
graphqlMutation(gql(deleteTodo), gql(listTodos), "Todo"),
graphql(gql(listTodos), {
options: {
fetchPolicy: "cache-and-network"
},
props: props => ({
todos: props.data.listTodos ? props.data.listTodos.items : []
})
})
)(App);
A repo containing the codebase is here: https://github.com/jbrown/appsync-todo
What am I doing wrong here that my query isn't updated?
Your input contains only properties name and completed. Tool graphqlMutation will add id automatically.
Code doesn't contains list query, I can guess than query requested for more data than name, completed and id.
So item will not be added to list because of missing required informations.
Solution is add all listed properties to createTodo.

initialValues works the first time, but after page refresh breaks the page, REACT/REDUX

I can click on edit comment and it will bring me to the comment edit page with the value of that comment. The problem is if I hit refresh it breaks the page and I get error comments undefined. Now I know this is because the state has been wiped, but is there a way around this that I can wait till the state loads in or such?
UPDATE: I have fixed the page from breaking on refresh but I cannot get the initialValues to get the comment value. I am not sure if there is a way to set the state value later on or not. I am getting things like author undefined and _id undefined and I know it is because I am setting the commentValue to {}. My question is there a way that when the state does get correctly updated to get those values?
Here is code for the check:
function mapStateToProps({ posts, auth, user }, ownProps) {
let commentValue = {};
const post = posts[ownProps.match.params.id];
if(!post){
return commentValue = {};
}
if(!posts.comments[ownProps.match.params.comment_id]) {
return commentValue = {};
}
if(posts.comments[ownProps.match.params.comment_id]){
return commentValue = posts.comments[ownProps.match.params.comment_id];
}
return {
initialValues: commentValue,
post: posts[ownProps.match.params.id],
auth: auth.authenticated,
user: user
};
}
Here is the code:
import React , { Component } from 'react';
import * as actions from '../../actions/comments_actions';
import { bindActionCreators } from 'redux';
import * as actionsPosts from '../../actions/posts_actions';
import * as actionsIndex from '../../actions/index';
import { reduxForm, Field } from 'redux-form';
import {connect} from 'react-redux';
import {Link} from 'react-router-dom';
class EditComment extends Component {
componentDidMount() {
const {id, comment_id} = this.props.match.params;
this.props.getOnePost(id);
if(this.props.user._id !== this.props.post.comments[comment_id].author.id) {
this.props.history.replace(`/posts/${id}`);
}
if(this.props.auth) {
this.props.getUser();
}
}
componentWillReceiveProps ({ user: nextUser, history, match, post, auth}) {
const {user: currentUser} = this.props;
const { id } = match.params
if (!currentUser || nextUser !== currentUser) {
if (nextUser && (nextUser._id !== post.author.id)) {
history.replace(`/posts/${id}`);
}
}
}
renderField(field) {
const { meta: {touched, error} } = field;
const className = `form-group ${touched && error ? 'has-danger' : ''}`;
return (
<div className={className}>
<label><strong>{field.label}:</strong></label>
<input
className="form-control"
type={field.type}
{...field.input}
/>
<div className="text-help">
{ touched ? error : ''}
</div>
</div>
)
}
onSubmit(values) {
const {comment_id} = this.props.match.params;
this.props.editComment(values, comment_id, () => {
this.props.history.push(`/posts/${id}`);
});
}
render() {
const {handleSubmit} = this.props;
const {id} = this.props.match.params;
return (
<form onSubmit={handleSubmit(this.onSubmit.bind(this))}>
<Field
label="Comment"
name="text"
type="text"
component={this.renderField}
/>
<button type="submit" className="btn btn-success">Comment</button>
<Link to={`/posts/${id}`} className="btn btn-danger">Cancel</Link>
</form>
);
}
}
function validate(values) {
const errors = {};
if(!values.comment) {
errors.comment = "Enter a comment!";
}
return errors;
}
function mapStateToProps({ posts, auth, user }, ownProps) {
console.log(posts[ownProps.match.params.id]);
const commentValue = posts[ownProps.match.params.id].comments[ownProps.match.params.comment_id];
console.log(commentValue);
return {
initialValues: commentValue,
post: posts[ownProps.match.params.id],
auth: auth.authenticated,
user: user
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({...actions, ...actionsIndex, ...actionsPosts}, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(reduxForm({
validate,
form: 'editform'
})(EditComment));
Sounds like what's happening is that your routing logic is making sure that this component is displayed when you refresh the page at that URL, but your app doesn't have the data loaded that this component expects.
This is basically the same kind of problem that Dave Ceddia describes in his post Watch Out for Undefined State. If there's a chance that one of your components will be rendered before the data it needs is available, you should write code to handle that case. There's several ways to do that, and Dave shows examples in his post.
In your particular case, your mapState function is trying to dereference some very nested state:
const commentValue = posts[ownProps.match.params.id].comments[ownProps.match.params.comment_id];
You should break that up into a couple steps, and do defensive checks to see if a post with that ID exists first, and if it does, if a comment with that ID exists. If they don't, you would probably want to return null or undefined for that prop from mapState.
With the help of markerikson I was able to come up with a solution. In the mapStateToProps I did some checks and set some initial values in the case that the state would return undefined. Here is the working code:
function mapStateToProps({ posts, auth, user }, ownProps) {
let commentValue = {};
const {id, comment_id} = ownProps.match.params;
if(!posts.comments){
commentValue = null;
}
if(posts[id]){
if(posts[id] && posts[id].comments[comment_id]){
commentValue = posts[id].comments[comment_id];
}
}
return {
initialValues: commentValue,
post: posts[ownProps.match.params.id],
auth: auth.authenticated,
user: user
};
}

Categories

Resources