I'm calling an API (in Node) from my react component (Stats.js)
This function getName is taking a passed in prop (called 'value')
in order to look up a value in MongoDB. See code below:
/* Stats React Component --Stats.js*/
class Stats extends Component {
constructor(props) {
super(props);
this.state = {
marketdata: [],
value: '',
name: ''
}
}
componentDidMount() {
const {value, name} = this.state;
this.getName(value);
this.getData(name);
}
getName = (value=this.props.value) => {
value = value.replace(/"/g,"");
console.log('Value', value);
fetch(`/stats?ticker=${value}`)
.then(res => console.log('Response', res))
.then(results => {
this.setState({
name: results,
})
})
}
componentWillReceiveProps(nextProps) {
this.setState({value: nextProps.value });
}
getData = (name=this.state.name) => {
fetch(`https://api.coinmarketcap.com/v1/ticker/${name}/?convert=USD`)
.then(res => res.json())
.then(results => {
this.setState({
marketdata: results,
})
})
render() {
const {marketdata} = this.state;
return (
<div className="App">
{marketdata.map(data =>
<div key={data.id}>
<span>Price: {numeral(data.price_usd).format('$0.00')} </span>
<span>24 Hour Volume: {numeral(data["24h_volume_usd"]).format('$0.0a')} </span>
<span>Pct Change (24 Hrs): {(numeral((data.percent_change_24h)/100).format('0.0%'))} </span>
<span>Market Cap: {numeral(data.market_cap_usd).format('$0.0a')}</span>
</div>
)}
</div>
);
}
}
export default Stats;
As an example, this passed in prop (value) looks like this "BTC"--i can verify in developer console that the value of state is working correctly
I'm trying to set the state of the 'name' variable to the data returned from
the API lookup, the method called getName.
I can also verify in express that the data is being retrieved correctly on the backend. This is my express file that is called by getName
/* GET coin stats. stats.js */
router.get('/', (req, res, next) => {
let ticker = req.query.ticker;
console.log(ticker);
Stat.findOne({symbol:ticker})
.then(function(result) {
var stats = result;
console.log(stats.name);
res.json(stats.name);
})
.catch(function(err) {
console.log('caught error', err.stack)
res.send({ error: err })
})
});
Note my data in the database looks like this: symbol: "BTC", name: "Bitcoin", thus when a user enters BTC i need name to set state to 'Bitcoin"
If I do a http://localhost:3001/stats?ticker=BTC is does indeed return "Bitcoin"
However for some reason the state of 'name' is not changing when a user inputs a value and my Stats.js component receives a new value.
Anyone see any problems here?
getName and getData are only called, in componentDidMount which is called only once.
You need to call them when you update the value in componentDidUpdate too, when the state updates with the new value from componentWillReceiveProps function. Also provide a check in componentWillReceiveProps function before setting state
componentWillReceiveProps(nextProps) {
if(this.props.value !== nextProps.value) {
this.setState({value: nextProps.value });
}
}
componentDidMount() {
const {value, name} = this.state;
this.getName(value);
this.getData(name);
}
componentDidUpdate(prevProps, prevState) {
if(this.state.value !== prevState.value) {
this.getName(this.state.value);
}
if(this.state.name !== prevState.name) {
this.getData(this.state.name);
}
}
Related
I'm using mockapi.io to practice with Axios API call
After I make a POST request, which create a new data, I want to render FlatList with the updated data. I'm thinking of making a new GET request to do that, but I'm not succeeded with it.
I need help
Here is where I call GET request, which already have mock data, and use FlatList to view it
ListScreen.js
class ListScreen extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
}
}
componentDidMount() {
axios.get('insert url')
.then(res => {
this.setState({
data: res && res.data ? res.data : []
})
})
.catch(err => {
console.log('Run into problem')
})
}
render() {
const { data } = this.state;
return (
<View>
<FlatList
data={data}
renderItem={({ item }) => {
return (
<Item
name={item.lastName}
phone={item.phoneNumber}
/>
);
}}
keyExtractor={(item) => item.id}
/>
</View>
And here is where I call POST request
class Create extends Component {
handleSubmitData = (value) => {
console.log('check value: ', value)
axios.post('insert url', {
lastName: `${value.Name}`,
phoneNumber: `${value.Phone}`,
})
.then((response) => {
console.log('here is what you upload: ', response.data)
})
.catch((err) => {
console.log('Run into problem')
})
}
render() {
return (
<CreateForm
handleSubmitData={this.handleSubmitData}
/>
)
}
}
The CreateForm component looks something like this
class CreateForm extends Component {
render() {
const { handleSubmit } = this.props;
return (
<View>
<View>
<Field
name="Name"
component={}
/>
<Field
name="Phone"
component={}
/>
</View>
<View>
<Button
title='Save'
onPress={handleSubmit(this.props.handleSubmitData)}
/>
</View>
</View>
)
This can be done by lifting the state up, from ListScreen to ListScreen's parent.
In ListScreen, take out the state and move into its parent:
this.state = {
data: [],
}
Pass the state down to ListScreen as a prop, also pass the fetch function (see below):
<ListScreen data={this.state.data} fetchListData={this.fetchListData} />
Change ListScreen's render function to access the data from props:
const { data } = this.props;
Move the axios.get() out of ListScreen and into a method on the parent component.
class App extends Component {
fetchListData() {
axios.get('insert url')
.then(res => {
this.setState({
data: res && res.data ? res.data : []
})
})
.catch(err => {
console.log('Run into problem')
})
}
}
In ListScreen, call the function in componentDidMount:
componentDidMount() {
this.props.fetchListData();
}
Alternatively, you could call it in the parent component's componentDidMount(). This might be preferred if you render multiple ListScreen's for example.
Finally, in the Create component, pass the fetch function:
<Create fetchListData={this.fetchListData} />
Call it on successful creation:
.then((response) => {
this.props.fetchListData();
console.log('here is what you upload: ', response.data)
})
As I see in your code, when you are making a POST request it wont display an updated data to your screen because there is no GET request callback upon the success result.
Suggesting you are not using redux , in ListScreen.js you could wrap the GET request into a function and call it in componentDidMount(). It should be look like:
const getData () => {
axios.get('insert url')
.then(res => {
this.setState({
data: res && res.data ? res.data : []
})
})
.catch(err => {
console.log('Run into problem')
})
}
componentDidMount() {
this.getData()
}
Therefore, you need to pass or drill the GET request function into your child component as a props and use it in a POST request callback. The final POST request method should be look like:
handleSubmitData = (value) => {
console.log('check value: ', value)
axios.post('insert url', {
lastName: `${value.Name}`,
phoneNumber: `${value.Phone}`,
})
.then((response) => {
console.log('here is what you upload: ', response.data)
getData()
})
.catch((err) => {
console.log('Run into problem')
})
}
As you can see, after your POST request is finished, it will trigger a GET request to update your parent state, resulting a screen with an updated data. However, you have to make sure to pass your parameters correctly based on how your component structure is.
I'm trying to change one value inside a nested state.
I have a state called toDoItems that is filled with data with componentDidMount
The issue is that changing the values work and I can check that with a console.log but when I go to setState and then console.log the values again it doesn't seem like anything has changed?
This is all of the code right now
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
toDoItems: null,
currentView: "AllGroup"
};
}
componentDidMount = () => {
fetch("/data.json")
.then(items => items.json())
.then(data => {
this.setState({
toDoItems: [...data],
});
})
};
changeToDoItemValue = (givenID, givenKey, givenValue) => {
console.log(this.state.toDoItems);
let newToDoItems = [...this.state.toDoItems];
let newToDoItem = { ...newToDoItems[givenID - 1] };
newToDoItem.completedAt = givenValue;
newToDoItems[givenID - 1] = newToDoItem;
console.log(newToDoItems);
this.setState({
toDoItems: {newToDoItems},
})
console.log(this.state.toDoItems);
};
render() {
if (this.state.toDoItems) {
// console.log(this.state.toDoItems[5 - 1]);
return (
<div>
{
this.state.currentView === "AllGroup" ?
<AllGroupView changeToDoItemValue={this.changeToDoItemValue}/> :
<SpecificGroupView />
}
</div>
)
}
return (null)
};
}
class AllGroupView extends Component {
render() {
return (
<div>
<h1 onClick={() => this.props.changeToDoItemValue(1 , "123", "NOW")}>Things To Do</h1>
<ul className="custom-bullet arrow">
</ul>
</div>
)
}
}
So with my console.log I can see this happening
console.log(this.state.toDoItems);
and then with console.log(newToDoItems)
and then again with console.log(this.state.toDoitems) after setState
State update in React is asynchronous, so you should not expect updated values in the next statement itself. Instead you can try something like(logging updated state in setState callback):
this.setState({
toDoItems: {newToDoItems},// also i doubt this statement as well, shouldn't it be like: toDoItems: newToDoItems ?
},()=>{
//callback from state update
console.log(this.state.toDoItems);
})
I am working on a hacker news clone I am trying to get the ids of the top stories from their api using axios in componentDidMount and then making another axios call to get the stories and push them in a state array but when I try to map over and render that array nothing shows up
class App extends Component {
constructor(props) {
super(props);
this.state = {
posts: []
}
}
componentDidMount() {
axios.get('https://hacker-news.firebaseio.com/v0/topstories.json')
.then( result => {
result.data.slice(0, 10).forEach(element => {
axios.get('https://hacker-news.firebaseio.com/v0/item/' + element + '.json')
.then( value => {
this.state.posts.push(value)
})
.catch(err =>{
console.log(err)
})
})
})
.catch(err => {
console.log(err);
})
}
render() {
return (
<div>
<Header title="Hacker News" />
{this.state.posts.map( (element, index) => <Post key={element.data.id} serialNum={index} postTitle={element.data.title} postVotes={element.data.score} postAuthor={element.data.by} />) }
</div>
)
}
}
Try setting the state like this:
axios.get('https://hacker-news.firebaseio.com/v0/item/' + element + '.json')
.then( value => {
this.setState({
posts: [value, ...this.state.posts]
})
})
.catch(err =>{
console.log(err)
})
})
This way you're using setState and appending every new value to the existing state.
As stated in the comments, don't use push for set state. In your code when you make the second request you must change the setState method to spread out the new value.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
posts: []
}
}
componentDidMount() {
axios.get('https://hacker-news.firebaseio.com/v0/topstories.json')
.then( result => {
result.data.slice(0, 10).forEach(element => {
axios.get('https://hacker-news.firebaseio.com/v0/item/' + element + '.json')
.then( value => {
this.setState(prevState => ({posts: [ value.data, ...prevState.posts]}))
})
.catch(err =>{
console.log("err");
console.log(err);
})
})
})
.catch(err => {
console.log(err);
})
}
render() {
return (
<div>
{this.state.posts && this.state.posts.map( (element, index) =>
<div key={element.id}>
{element.title}
</div>
)}
</div>
);
}
}
componentDidMount() is called after Render() only once. React doesn't know about the state changes unless you use setState().
componentDidMount() {
axios.get('https://hacker-news.firebaseio.com/v0/topstories.json')
.then( result => {
result.data.slice(0, 10).forEach(element => {
axios.get('https://hacker-news.firebaseio.com/v0/item/' + element + '.json')
.then( value => {
this.setState({posts: [value, ...this.state.posts]})
})
})
})
}
Use this.setState({posts : [value, ...this.state.posts]}) instead of this.state.posts.push(value). using ... (spread operator) appends the value to the original posts array.
I am using react for front-end and node for back-end, what i'm trying to do is fetch data from server to update the user entries on the front-end. there is a solution that i can use Object.assign() to re-render user entries but the problem is I get
Warning: Functions are not valid as a React child. This may happen if you return a Component instead of from render. Or maybe you meant to call this function rather than return it.
when i added Object.assing() to code it results in warning but before that i had no issues. What could be the solution here to re-render without warrning?
here is the code regarding my problem
class App extends Component {
constructor() {
super();
this.state = {
input: '',
imgUrl: '',
box: { },
route: 'signin',
isSignedIn: false,
user: {
id: '',
name: '',
email: '',
entries: 0,
joined: ''
}
}
}
onButtonSubmit = () => {
this.setState({imgUrl: this.state.input});
fetch('http://localhost:3001/image', {
method: 'put',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
id: this.state.user.id
})
})
.then(response => response.json)
.then(count => {
this.setState(Object.assign(this.state.user, {entries: count}))
})
.catch(err => console.log(err));
}
render() {
return (
<div className="App">
<Navigation isSignedIn={this.state.isSignedIn} onRouteChange={this.onRouteChange} />
{ this.state.route === 'home'?
<div>
<Rank name={this.state.user.name} entries={this.state.user.entries} />
<ImageLinkForm onInputChange = {this.onInputChange} onButtonSubmit = {this.onButtonSubmit}/>
<FaceRecognition box = {this.state.box} imgUrl = {this.state.imgUrl} />
</div>
: ( this.state.route === 'signin'?
<Signin loadUser={this.loadUser} onRouteChange={this.onRouteChange}/>
: <Register loadUser={this.loadUser} onRouteChange={this.onRouteChange}/>
)
}
</div>
);
Here is component where entries gets printed
import React from 'react';
const Rank = ({ name, entries}) => {
return (
<div>
<div className='rank'>
{`${name} your current rank is...`}
</div>
<div className='white f1 '>
{entries}
</div>
</div>
);
}
export default Rank;
Here is server Side code where entries gets updated
app.put('/image', (req, res) => {
const { id } = req.body;
let found = false;
database.users.forEach(user => {
if(user.id === id){
found = true;
user.entries++;
return res.json(user.entries);
}
});
if (!found) {
res.status(400).json('not found');
}
});
Why do i get this warning only when i added Object.assign()? its been 2 days and i cant figure it out
In React you should never manually update the state of something programmatically outside of a setState call, which is what you're doing when you use Object.assign in your example.
https://daveceddia.com/why-not-modify-react-state-directly/
You can try using the spread operator instead:
.then(count => {
this.setState({...this.state.user, entries: count})
})
Background
I'm attempting to create a dropdown that retrieves State Codes (AZ, WI, WY, etc.) from a backend API and then populates an on-screen dropdown with the values.
I have a React component that looks like this (an ellipsis representing code that I'm omitting for clarity):
Person.jsx
export class Person extends React.Component {
constructor(props) {
super(props);
...
this.props.getStateCodes();
}
render(){
...
<select
id="personState"
name="personState"
className="form-control dropDownStyle"
onChange={this.handleChange}
value={this.props.person.personState}
>
{this.props.stateCodes && this.props.stateCodes.map((option) => (
<option key={option.id} value={option.data}>{option.data}</option>
))
}
</select>
...
}
}
I then have Redux action creators, including an excerpt like this:
personContract.js
export const actionCreators = {
...
getStateCodes: () => async (dispatch) => {
getStateCodesResponse(dispatch);
},
...
export function getStateCodesResponse(dispatch) {
const endpoint = window.location.origin + '/Home/GetStateCodes';
fetch(endpoint, {
credentials: 'same-origin'
})
.then(function (response) {
if (!response.ok) {
const errors = ['Unable to retrieve state codes.'];
dispatch({ type: "SET_ERROR", errors: errors });
document.body.style.cursor = 'default';
return;
}
return response.json();
}).then(function (data) {
if (data !== undefined) {
const stateCodes = data.stateCodes;
// const stateCodes = result.PayLoad.StateCodes;
document.body.style.cursor = 'default';
dispatch({ type: 'STATECODES', stateCodes });
}
});
}
...
}
Then a reducer that includes:
Contract.js
const initialState ={
...
stateCodes: [],
...
};
export const reducer = (state, action) => {
...
if (action.type == "STATECODES"){
const stateCodes = action.stateCodes;
return {
...state,
errors: [],
stateCodes: stateCodes
}
}
...
}
Problem
Initially, I did not include {this.props.stateCodes && in the Person.jsx file. The issue then, was that I'd get an error that this.props.stateCodes was not defined. I added in {this.props.stateCodes &&, however, now it never runs this.props.stateCodes.map at all. It's almost as if I need to render() to run again after the State Codes have been retrieved, but I don't know how to accomplish that.