I'm having issues with setting this.setState from within my API call. If I console.log the stocks array inside the axios call the data is available in the array. It is not available outside if it.
Is the problem because this.setState is merging objects? I'm having a hard time conceptualizing what is happening here. How do I fix this problem so I can pass the contents to props?
import React, { Component } from 'react';
import axios from 'axios';
import SearchBar from './components/search_bar';
import StockList from './components/StockList';
import './App.css';
class App extends Component {
constructor() {
super();
this.state = {
stocks: [],
term: null,
value: ''
};
this.handleClick = this.handleClick.bind(this);
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.setState({
value: e.target.value
});
}
handleClick(e) {
if(e) e.preventDefault();
this.setState({
value: '',
term: this.state.value
});
let term = this.state.value;
const key = 'F41ON15LGCFM4PR7';
const url = `https://www.alphavantage.co/query?function=BATCH_STOCK_QUOTES&symbols=${term}&apikey=${key}`;
axios.get(axios.get(url)
.then(res => {
let stocks = Array.from(res.data['Stock Quotes']).map((stock) => [{symbol: stock['1. symbol'], price: stock['2. price'], volume: stock['3. volume'], timestamp: stock['4. timestamp']}]);
this.setState((state, props) => {
return [...this.state.stocks]
})
})
.catch(error => console.log(error))
)
}
render () {
let stocks = this.state.stocks;
const value = this.state.value;
return (
<div className="App">
<h1>Stock Search</h1>
<SearchBar value={ value }
onChange={ this.handleChange }
onClick={ this.handleClick }/>
<StockList stockItems={ stocks }/>
</div>
);
}
}
export default App;
Your setState there is the issue, it's messing up the structure of your state.
this.setState((state, props) => {
return [...this.state.stocks]
});
Should be either:
this.setState({
// set stocks to that array you parsed from the axios response
stocks
});
or
this.setState((state, props) => {
return {
...state,
// set stocks to that array you parsed from the axios response
stocks
};
});
I suggest that because you're accessing the stocks via this.state.stocks in your render
Related
I have no idea How to store the react js state into localstorage.
import React, { Component } from 'react'
import './App.css';
import { auth,createUserProfileDocument } from './firebase/firebase.utils'
import { TodoForm } from './components/TodoForm/TodoForm.component'
import {TodoList} from './components/TodoList/TodoList.component'
import {Footer} from './components/footer/footer.component'
import Header from '../src/components/header/header.component'
import {Redirect} from 'react-router-dom'
import {connect} from 'react-redux'
import {setCurrentUser} from './redux/user/user.actions'
export class App extends Component {
constructor(props) {
super(props)
this.input=React.createRef()
this.state = {
todos:[
{id:0, content:'Welcome Sir!',isCompleted:null},
]
}
}
todoDelete = (id) =>{
const todos = this.state.todos.filter(todo => {
return todo.id !== id
})
this.setState({
todos
})
}
toDoComplete = (id,isCompleted) =>{
console.log(isCompleted)
var todos = [...this.state.todos];
var index = todos.findIndex(obj => obj.id === id);
todos[index].isCompleted = !isCompleted;
this.setState({todos});
console.log(isCompleted)
}
addTODO = (todo) =>{
todo.id = Math.random()
todo.isCompleted = true
let todos = [...this.state.todos, todo]
this.setState({
todos
})
}
unsubscribeFromAuth = null;
componentDidMount() {
const { setCurrentUser } = this.props;
this.unsubscribeFromAuth = auth.onAuthStateChanged(async userAuth => {
if (userAuth) {
const userRef = await createUserProfileDocument(userAuth);
userRef.onSnapshot(snapShot => {
setCurrentUser({
id: snapShot.id,
...snapShot.data()
});
});
}
setCurrentUser(userAuth);
});
}
componentWillUnmount() {
this.unsubscribeFromAuth();
}
render() {
return (
<div className='App'>
<Header />
<TodoForm addTODO={this.addTODO} />
<TodoList
todos={this.state.todos}
todoDelete={ this.todoDelete}
toDoComplete={ this.toDoComplete}
/>
<Footer/>
</div>
)
}
}
const mapStateToProps = ({ user }) => ({
currentUser: user.currentUser
});
const mapDispatchToProps = dispatch => ({
setCurrentUser: user => dispatch(setCurrentUser(user))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);
in my input Form
import './TodoForm.style.css'
export class TodoForm extends Component {
constructor(props) {
super(props)
this.state = {
content : ''
}
}
handleChange = (e) =>{
this.setState({
content: e.target.value
})
}
handleSubmit =(e) =>{
e.preventDefault();
this.props.addTODO(this.state);
this.setState({
content: ''
})
}
render() {
return (
<div className='inputTask'>
<form onSubmit={ this.handleSubmit}>
<input
className="textBox"
type='text'
onChange={ this.handleChange}
value={this.state.content}
placeholder='what you want to do ...'
/>
</form>
</div>
)
}
}
export default TodoForm
I have no idea How to store the react js state into localstorage.
i searched on internet but unable to find the exact solution all the codes that i think is necessary post.
You can use reactLocalStorage to save any data in local storage
import {reactLocalStorage} from 'reactjs-localstorage';
reactLocalStorage.set('var', true);
reactLocalStorage.get('var', true);
reactLocalStorage.setObject('var', {'test': 'test'});
reactLocalStorage.getObject('var');
reactLocalStorage.remove('var');
reactLocalStorage.clear();
Read out the localStorage item in the componentDidMount callback. Simply read the item you want to get, check if it exists and parse it to a usable object, array or datatype that need. Then set the state with the results gotten from the storage.
And to store it, simply handle it in an event handler or helper method to update both the state and the localStorage item.
class ExampleComponent extends Component {
constructor() {
super();
this.state = {
something: {
foo: 'bar'
}
}
}
componentDidMount() {
const storedState = localStorage.getItem('state');
if (storedState !== null) {
const parsedState = JSON.parse(storedState);
this.setState({ something: parsedState });
}
}
clickHandler = (event) => {
const value = event.target.value;
const stringifiedValue = JSON.stringify(value);
localStorage.setItem('state', stringifiedValue);
this.setState({ something: value });
}
render() {
return (
<button onClick={clickHandler} value={this.state.something}>Click me</button>
);
}
}
Set data in localStorage
key-value pair :
localStorage.setItem('key_name',"value");
object
localStorage.setItem('key_name', JSON.stringify(object));
Remove data from localStorage
localStorage.removeItem('key_name');
Get data from localStorage
let data = localStorage.getItem('key_name');
object :
let data = JSON.parse(localStorage.getItem('key_name'));
clear localStorage (delete all data)
localStorage.clear();
I'm trying to initiate an API request upon paste of a URL into an input field and then show the result on the page.
According to documentation and this link on SOF, setState is the way to initiate re-render, I know and it seems I did it the right way myself, but something is off, I get the url state only when I do onChange again, React doesn't seem to show me my pasted data anywhere in any of the available lifecycle events.
Using create-react-app:
import React from "react";
import ReactDOM from "react-dom";
const UserInput = props => {
return (
<div>
<label>Enter URL:</label>
<input onChange={props.handleChange} type="text" value={props.value} />
</div>
);
};
class Fetch extends React.Component {
constructor() {
super();
this.state = {
url: null,
userData: null,
fetching: false,
error: null
};
}
componentDidUpdate() {
this.fetchData();
}
fetchData() {
fetch(this.state.url)
.then(result => result.json())
.then(json => this.setState({ userData: json }))
.error(error => console.log(error));
}
render() {
return this.props.render();
}
}
const UserProfile = ({ name, gender }) => {
return (
<div>
Hey {name}, you are {gender}!
</div>
);
};
class App extends React.Component {
constructor() {
super();
this.state = {
url: null
};
}
handleChange(e) {
this.setState({
url: e.target.value
});
}
render() {
return (
<div>
<UserInput
value={this.state.url}
handleChange={this.handleChange.bind(this)}
/>
<Fetch url={this.state.url} render={data => <UserProfile />} />
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
If you paste any URL in the field, you won't have it in state, so when fetchData is triggered its
this.state.url
is actually still null.
Thanks
Your Fetch component and App component are using two separate copies of the url state which causes the issue, you have to use the url you pass as prop to the Fetch component instead.
class Fetch extends React.Component {
constructor(props) {
super(props);
this.state = {
// url: null, remove this
userData: null,
fetching: false,
error: null
};
}
componentDidUpdate() {
this.fetchData();
}
fetchData() {
fetch(this.props.url) // update here
.then(result => result.json())
.then(json => this.setState({ userData: json }))
.error(error => console.log(error));
}
render() {
return this.props.render(userData); // the render prop is a function in your case that expects data
}
}
update the below line too so that the UserProfile gets the data that has been obtained from API. I am not sure about the keys
<Fetch url={this.state.url} render={data => <UserProfile name={data.name} gender={data.gender}/>} />
In the async function below, I call stationData just to confirm that I'm passing an array of objects into bartData (which is just an empty array). Attached is a response of the array of Objects that I am receiving. However, when trying to use this.state.bartData (to confirm that it does have the array of objects), my return function is returning bartData as undefined. Any ideas?
import React from 'react';
const bartKey = process.env.REACT_API_BART_API_KEY;
class StationBaseRoutes extends React.Component{
constructor(props){
super(props);
this.state = {
isLoading: true,
station: [],
stationAbbv: 'ALL',
destination: '',
bartData: []
}
}
componentDidMount(){
this.getAllStationRoutes();
}
async getAllStationRoutes(){
try{
setInterval(async () => {
const response = await fetch(`http://api.bart.gov/api/etd.aspx?cmd=etd&orig=${this.state.stationAbbv}&key=${bartKey}&json=y`);
const jsonResponse = await response.json();
const apiData = jsonResponse.root;
const stationData = apiData.station;
console.log(stationData);
this.setState(({
isLoading: false,
bartData: stationData
}), () => {
console.log(`Callback: ${this.state.bartData}`)
})
}, 20000)
} catch(error){
console.log(error);
}
}
getRoutes = () => {
console.log(`bartData: ${this.bartData}`)
}
render(){
const {station, destination} = this.state;
return(
<div>
<h2>Calling get routes: {this.getRoutes()}</h2>
<h2>Origin: {station}</h2>
<h3>Destination: {destination}</h3>
</div>
)
}
}
export default StationBaseRoutes;
Responses: https://imgur.com/gallery/Luk9MCX
There's a couple of bugs here.
First of all, getRoutes() is using this.bartData instead of this.state.bartData
Secondly, all your objects in console.log are being converted to strings. You can change it to
console.log('bartData:', this.state.bartData);
to be able to see the actual data.
I was unable to get the Bart API to work in a codesandbox, so I had to mock the API... however, the data is still structured the same.
On that note, the API is working as expected, you just need to map over the objects in the this.state.bartData array and deconstruct the properties you want to show.
Working example: https://codesandbox.io/s/031pn7w680
import map from "lodash/map";
import React, { Component, Fragment } from "react";
import { fakeAPI } from "../../api/fakeAPI";
class StationBaseRoutes extends Component {
constructor(props) {
super(props);
this.state = {
isLoading: true,
station: [],
stationAbbv: "ALL",
destination: "",
bartData: []
};
this.getAllStationRoutes = this.getAllStationRoutes.bind(this);
}
componentDidMount() {
this.getAllStationRoutes();
}
async getAllStationRoutes() {
try {
const res = await fakeAPI.get();
const apiData = res.data.root;
const stationData = apiData.station;
this.setState({
isLoading: false,
bartData: stationData
});
} catch (error) {
console.log(error);
}
}
render() {
const { bartData, isLoading } = this.state;
return (
<div className="app-container">
{isLoading ? (
<p className="t-a-c">Loading...</p>
) : (
<Fragment>
<h1 className="t-a-c">Bart Stations</h1>
{map(bartData, ({ name, etd }) => (
<div className="jumbotron station" key={name}>
<h1>Origin: {name}</h1>
{map(etd, ({ destination }) => (
<li key={destination}>Destination: {destination}</li>
))}
</div>
))}
<pre className="preview">
<code>{JSON.stringify(bartData, null, 4)}</code>
</pre>
</Fragment>
)}
</div>
);
}
}
export default StationBaseRoutes;
I need to send an array from React.js frontend to the Django backend. I can easily send all variables, except just an array csvData.
I see that console.log("csvData", csvData) returns the following data:
NUM,COL1,COL2
1,300,500
2,566,VL
This is the code of React component called BatchFlights:
import React, { Component, Fragment } from 'react';
import TopControls from "./layout/batch/TopControls"
import MainContent from "./layout/batch/MainContent"
import BottomControls from "./layout/batch/BottomControls"
import styles from "./layout/styles/styles";
import { withStyles } from "#material-ui/core/styles";
class BatchFlights extends Component {
constructor(props) {
super(props);
this.state = {
csvData: [],
temperature: 20,
visibility: 5999.66,
windIntensity: 8.0,
prediction: 0,
labelWidth: 0
};
this.handleChange = this.handleChange.bind(this);
};
componentDidMount() {
this.fetchData();
};
updateDelay(prediction) {
this.setState(prevState => ({
prediction: prediction
}));
};
setCsvData = csvData => {
this.setState({
csvData
}, () => {
console.log("csvData",this.state.csvData)
});
}
fetchData = () => {
const url = "http://localhost:8000/predict?"+
'&temperature='+this.state.temperature+
'&visibility='+this.state.visibility+
'&windIntensity='+this.state.windIntensity+
'&csvData='+this.state.csvData;
fetch(url, {
method: "GET",
dataType: "JSON",
headers: {
"Content-Type": "application/json; charset=utf-8",
}
})
.then((resp) => {
return resp.json()
})
.then((data) => {
this.updateDelay(data.prediction)
})
.catch((error) => {
console.log(error, "catch the hoop")
})
};
handleChange = (name, event) => {
this.setState({
[name]: event.target.value
}, () => {
console.log("csvData", csvData)
});
};
handleReset = () => {
this.setState({
prediction: 0
});
};
render() {
return (
<Fragment>
<TopControls state={this.state} styles={this.props.classes} handleChange={this.handleChange} />
<MainContent state={this.state} styles={this.props.classes} setCsvData={this.setCsvData} />
<BottomControls state={this.state} styles={this.props.classes} fetchData={this.fetchData} handleReset={this.handleReset}/>
</Fragment>
);
}
}
const StyledBatchFlights = withStyles(styles)(BatchFlights);
export default StyledBatchFlights;
MainContent
import React, { Component, Fragment } from 'react';
import CssBaseline from '#material-ui/core/CssBaseline';
import CSVDataTable from './CSVDataTable';
class MainContent extends Component {
render() {
return (
<Fragment>
<CssBaseline />
<main className={this.props.styles.mainPart}>
<CSVDataTable setCsvData={this.props.setCsvData} />
</main>
</Fragment>
);
}
}
export default MainContent;
CSVDataTable
import React, { Component } from 'react';
import { CsvToHtmlTable } from 'react-csv-to-table';
import ReactFileReader from 'react-file-reader';
import Button from '#material-ui/core/Button';
const sampleData = `
NUM,COL1,COL2
1,300,500
2,566,VL
`;
class CSVDataTable extends Component {
state={
csvData: sampleData
};
handleFiles = files => {
var reader = new FileReader();
reader.onload = (e) => {
// Use reader.result
this.setState({
csvData: reader.result
})
this.props.setCsvData(reader.result)
}
reader.readAsText(files[0]);
}
render() {
return <div>
<ReactFileReader
multipleFiles={false}
fileTypes={[".csv"]}
handleFiles={this.handleFiles}>
<Button
variant="contained"
color="primary"
>
Load data
</Button>
</ReactFileReader>
<CsvToHtmlTable
data={this.state.csvData || sampleData}
csvDelimiter=","
tableClassName="table table-striped table-hover"
/>
</div>
}
}
export default CSVDataTable;
But when csvData arrives to Django backend, it's empty:
csvData = request.GET.get('csvData')
print("CSV DATA",csvData) # IT IS EMPTY!!!
I believe the problem is you're trying to coerce the array csvData into a string and attach the stringified array as a parameter to your get request fetchData
'&csvData='+this.state.csvData;
If the value of csvData is made up of arrays within an array, when you coerce the array into a string through you get:
'' + [[], [], []] // "[object Object]"
If you want to keep your current get function you should instead do
'&csvData='+JSON.stringify(this.state.csvData);
but it is highly recommended that you change the get request a post or put request if you are dealing with highly nested JSON objects.
I'm not sure what I'm doing wrong, but I have an input field for entering a search term and trying to filter results based on the search term. The problem is that the first value being passed is an empty string and input is offset by 1 item for each keypress after that. For example, if I type 'sea', it would update the search term to be ' se'. Then, when I try to delete the value, it is offset the other direction, so deleting ' se' ends with 's', which can't be deleted.
(Here's a link to the app in progress: https://vibrant-yonath-715bf2.netlify.com/allpokemon. The full search functionality isn't working quite yet. I'm pretty new at this.)
import React, { Component } from 'react';
import Pokemon from './Pokemon';
class PokemonList extends Component {
constructor(props) {
super(props);
this.state = {
pokemonList: [],
searchTerm: '',
fetched: false,
loading: false
};
this.updateResults = this.updateResults.bind(this);
}
componentWillMount() {
this.setState({
loading: true
});
fetch('https://pokeapi.co/api/v2/pokemon?limit=151')
.then(res => res.json())
.then(response => {
this.setState({
pokemonList: response.results,
loading: true,
fetched: true
});
});
}
handleSearchTermChange = (
event: SyntheticKeyboardEvent & { target: HTMLInputElement }
) => {
this.setState({ searchTerm: event.target.value });
this.updateResults();
};
updateResults() {
const filteredList = this.state.pokemonList.filter(
pokemon =>
pokemon.name.toUpperCase().indexOf(this.state.searchTerm.toUpperCase()) >= 0
);
console.log(this.state.searchTerm);
this.setState({
pokemonList: filteredList
});
}
render() {
const { fetched, loading, pokemonList } = this.state;
let content;
if (fetched) {
content = (
<div className="flex-grid">
{pokemonList.map((pokemon, index) => (
<Pokemon key={pokemon.name} id={index + 1} pokemon={pokemon} />
))}
</div>
);
} else if (loading && !fetched) {
content = <p> Loading ...</p>;
} else {
content = <div />;
}
return (
<div>
<input
onChange={this.handleSearchTermChange}
value={this.state.searchTerm}
type="text"
placeholder="Search"
/>
{content}
</div>
);
}
}
export default PokemonList;
setState is asynchronous, so your this.state.searchTerm is not updated when you call updateResults. You could e.g. filter the array in render instead.
Example
class App extends Component {
state = {
pokemonList: [
{ name: "pikachu" },
{ name: "bulbasaur" },
{ name: "squirtle" }
],
searchTerm: ""
};
changeSearchTerm = event => {
this.setState({ searchTerm: event.target.value });
};
render() {
const { pokemonList, searchTerm } = this.state;
const filteredList = pokemonList.filter(pokemon =>
pokemon.name.toUpperCase().includes(searchTerm.toUpperCase())
);
return (
<div>
<input value={searchTerm} onChange={this.changeSearchTerm} />
{filteredList.map(pokemon => <div>{pokemon.name}</div>)}
</div>
);
}
}
I think the problem is that you call this.updateResults();
and then calling this.setState({ searchTerm: event.target.value }); instead of using the callback function for setState.
For example:
this.setState({ searchTerm: event.target.value }, () => this.updateResults());
Hope I got it right.
Update:
Also I see many problems in your code, for example, why you update the list with a filtered list? you don't need to do that:
this.setState({
pokemonList: filteredList
});
Instead of updating the results in the state, you simply need to render the filtered list... meaning your state stay with the original list, also your filterd value, just in the render you pass the filtered list..