using getOffsetHeight with async data after render in react - javascript

Below code is simple, but it's ok impropriate in my opinion. Basically I try to calculate the height of a list. But what's a better solution than doing a setTimeout hack? I tried componentDidMount, but that doesn't guarantee the data is loaded.
class App extends Component {
constructor() {
super();
this.state = {
users: null
};
}
componentWillMount() {
axios.get("https://randomapi.com/api/6de6abfedb24f889e0b5f675edc50deb?fmt=raw&sole")
.then(response => this.setState({ users: response.data }));
}
componentDidMount() {
setTimeout(() => {
console.log(this.userElem.offsetHeight);
}, 1000);
}
render() {
return (
<div style={styles}>
<div ref={elem => (this.userElem = elem)}>
{this.state.users &&
this.state.users.map(o =>
<p>
{o.first}
</p>
)}
</div>
</div>
);
}
}
demo https://codesandbox.io/s/j46o2656vy

Instead of making the api call inside componentWillMount do it inside componentDidMount method, and use setState callback method to check the element height.
Like this:
componentDidMount() {
axios.get("https://randomapi.com/api/6de6abfedb24f889e0b5f675edc50deb?fmt=raw&sole")
.then(response => {
this.setState({ users: response.data }, () => {
console.log(this.userElem.offsetHeight);
})
});
}
Or you can also use componentDidUpdate method, it is invoked immediately after updating occurs. This method is not called for the initial render.

You could use componentDidUpdate and check your this.userElem.offsetHeight variable there.Also, i'd change componentWillMount with componentDidMount. You can read more here.

Related

Rendering previous state instead of current value selected in dropdown

I wrote the dropdown component that passes a selected value back to parent via callback function. From there I would like to simply render the selected value below the dropdown. Instead I have rendered previous state. I have no idea why that works like that, could someone explain me my app's behaviour and maybe give a hint how to fix it? I don't even know where to look for the answers.
index.js
import React, { Component } from 'react';
import './App.css';
import { Dropdown } from './components/dropdown'
class App extends Component {
state = {
response: "",
currA: ""
};
componentDidMount() {
this.callApi()
.then(res => this.setState({ response: res.express }))
.catch(err => console.log(err));
}
callApi = async () => {
const response = await fetch('/main');
const body = await response.json();
if (response.status !== 200) throw Error(body.message);
return body;
};
calculateRate = (currA) => {
this.setState({currA: currA});
}
render() {
return (
<div className="App">
<div>
<Dropdown callbackFromParent={this.calculateRate}/>
</div>
<p>
{this.state.currA}
</p>
</div>
);
}
}
export default App;
dropdown.js
import React from 'react';
export class Dropdown extends React.Component {
constructor(props){
super(props);
this.state = {
list: [],
selected: ""
};
}
componentDidMount(){
fetch('https://api.fixer.io/latest')
.then(response => response.json())
.then(myJson => {
this.setState({ list: Object.keys(myJson.rates) });
});
}
change(event) {
this.setState({ selected: event.target.value });
this.props.callbackFromParent(this.state.selected);
}
render(){
var selectCurr = (curr) =>
<select
onChange={this.change.bind(this)}
value={this.state.currA}
>
{(this.state.list).map(x => <option>{x}</option>)}
</select>;
return (
<div>
{selectCurr()}
</div>
);
}
}
Since your setState() is not a synchronous call, it might be that your callback is firing before the state of your dropdown is actually modified. You could try using the callback on setState...
change(event) {
this.setState({
selected: event.target.value
}, () => {this.props.callbackFromParent(event.target.value)});
;
}
...Or if your parent component is the only thing that cares about the selected value (my guess from your snip), you don't need to update the dropdown state at all.
change(event) {
this.props.callbackFromParent(event.target.value;)
}
Good luck!
Documentation:
setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall. Instead, use componentDidUpdate or a setState callback (setState(updater, callback)), either of which are guaranteed to fire after the update has been applied. If you need to set the state based on the previous state, read about the updater argument below.

TypeError: this.props.propName.map is not a function react js

I want to fetch JSON data, store it in state and then pass it to component through props. In the component I want to use the map function but it shows me this error :
TypeError: this.props.dataQueries.map is not a function.
This is my code:
class App extends Component {
constructor(props) {
super(props);
this.state = {
dataQueries: ''
}
}
fetchData() {
fetch('https://jsonplaceholder.typicode.com/posts', {method: "GET"}).
then(res => res.json()).
then(result => this.setState({ dataQueries: result }));
}
componentWillMount() {
this.fetchData();
}
render() {
return (
<div>
<ShowPosts dataQueries={ this.state.dataQueries } />
</div>
);
}
}
And this is my component :
class ShowPosts extends Component {
render() {
return (
<div>
{
this.props.dataQueries.map((query, index) => {
return index;
})
}
</div>
);
}
}
Initially, you're setting dataQueries to ''. Which has no map, as it's a string. It's only an array later, when your fetchData async call has completed. Nothing prevents render from being called before the async call completes.
Initialize it to [] instead, or modify render to avoid trying to use it as an array when it's not an array.
You should call this.fetchData() inside componentDidMount() lifecycle method. So when component is mounted only then you update the state with response from API. Also, you should render ShowPosts component when there are posts to render using conditional rendering.
render() {
return (
<div>
{this.state.dataQueries.length && <ShowPosts dataQueries={ this.state.dataQueries } />}
</div>
);
}
And your initial dataQueries should be an empty array. dataQueries = []

React don't change state after first method trigger

In my React app i have components structure:
-AllElements
--SingleElement
--SingleElementDetails
I am passing method See to SingleElement component where I invoke seefunc to invoke see method from AllElements component. The problem i my state (name) in AllElements not change after first onClick trigger, it changes after secund click. Could you tell my why ?
class AllElements extends Component {
constructor(props) {
super(props);
this.state = {
myData: [],
viewingElement: {
name:""
}
}
this.see = this.see.bind(this);
console.log('Initial Sate',this.state.viewingElement);
}
see(name) {
this.setState({
viewingElement: {
name:name
}
});
console.log('State after SEE',this.state.viewingElement);
}
render() {
const { myData, viewingElement } = this.state;
return (
<div>
{myData.map(se => (
<SingleElement
key={se.id}
name={se.name}
see={this.see}
/>
))}
<SingleElementDetails viewingElement={viewingElement}/>
</div>
);
}
}
class SingleElement extends Component {
constructor(props) {
super(props);
}
seefunc(name) {
this.props.see(this.props.name);
console.log('Name in seefunc props',this.props.name);
}
render() {
return (
<div onClick={this.seefunc.bind(this)}>
DIV CONTENT
</div>
)
}
}
The problem you have here is that setState is asynchronous. It does work the first time but you do not see it in your console.log because the console.log happens before the state is updated.
To see the updated state use the second argument of setState which is a callback function (https://reactjs.org/docs/react-component.html#setstate):
this.setState({
viewingElement: {
name:name
}
}, () => {
console.log('State after SEE',this.state.viewingElement);
});
And in SingleElement use the componentWillReceiveProps(nextprops) (https://reactjs.org/docs/react-component.html#componentwillreceiveprops) method from react lifecycle to see the updated props:
seefunc(name) {
this.props.see(this.props.name);
}
componentWillReceiveProps(nextprops) {
console.log('Name in props',nextProps.name);
}
It does change. However setState is an aync process so you're only logging the previous state to the console. setState does provide a callback that allows you to run code after the async process has finished, so you can do:
this.setState({
viewingElement: {
name:name
}
}, () => console.log('State after SEE',this.state.viewingElement));
DEMO

Functions inside componentDidMount are undefined

I have the following block of code:
class App extends Component {
constructor(props) {
super(props);
this.state = {
avatar: '',
...some more data...
}
this.fetchUser = this.fetchUser.bind(this);
}
render() {
return (
<div>
<SearchBox onSubmit={this.fetchUser}/>
<Card data={this.state} />
<BaseMap center={this.state.address}/>
</div>
);
}
componentDidMount() {
function fetchUser(username) {
let url = `https://api.github.com/users/${username}`;
this.fetchApi(url);
};
function fetchApi(url) {
fetch(url)
.then((res) => res.json())
.... some data that is being fetched ....
});
};
let url = `https://api.github.com/users/${this.state.username}`;
}
}
export default App;
However, I get a TypeError: Cannot read property 'bind' of undefined for the following line: this.fetchUser = this.fetchUser.bind(this); in the constructor where I bind the function to this.
How can I make the function, which is inside componentDidMount method, visible and available for binding?
EDIT:
The reason I'm putting the functions inside componentDidMount was because of a suggestion from another user on StackOverflow. He suggested that:
#BirdMars that because you don't really fetch the data in the parent,
and the state doesn't really hold the address object. call the fetch in
componentDidMount of the parent and update the state there. this will
trigger a second render that will pass in the new state with the
address object (the first render will be with an empty state.address
of course until you finish the fetch and update the state)
There is some fundamental misunderstanding here, you can still call the functions inside componentDidMount, however you should not define them inside it.
Simply define the functions outside componentDidMount and this will solve your problems, here is a short example
componentDidMount() {
this.fetchUser(this.state.username);
}
function fetchUser(username) {
let url = `https://api.github.com/users/${username}`;
this.fetchApi(url);
};
function fetchApi(url) {
fetch(url)
.then((res) => res.json())
.... some data that is being fetched ....
});
};
Its a simple matter of scope:
function outer(){
function inner(){
}
}
inner(); // error does not exist
As birdmars suggested you should call this.fetchUser() inside component did mount. but declare the function outside!
class App extends Component {
render() {
return (
<div>
<SearchBox onSubmit={this.fetchUser}/>
<Card data={this.state} />
<BaseMap center={this.state.address}/>
</div>
);
}
fetchUser(username) {
let url = `https://api.github.com/users/${username}`;
this.fetchApi(url);
};
fetchApi(url) {
fetch(url)
.then((res) => res.json())
.... some data that is being fetched ....
});
};
componentDidMount() {
let url = username; //frome somewhere, most probably props then use props Changed function instead!
var user = his.fetchUser(url)
this.setState(() => {user: user});
}
}
export default App;
If you declare the functions inside componentDidMount they are only visible in that scope and get be accessed by the component itself. Declare them in your component.
What he was talking about in this post was to call the functions from componentDidMount, but not to declare them in there.
"Another guy from StackOverflow" suggest you to call functions in componentDidMount() method, but not to define them there.
So you should do this instead
class App extends Component {
constructor(props) {
...
this.fetchUser = this.fetchUser.bind(this);
this.fetchApi= this.fetchApi.bind(this);
}
...
fetchUser(username) {...}
fetchApi(url) {...}
componentDidMount() {
this.fetchUser(...);
...
}
}

what to do with response object in react js

i'm working with react to complete the front end of a rest application.
I have json being sent to the front end, and I use fetch .
fetch('/task')
.then(function(data) {
return data.json();
})
.then(function(json) {
json.tasks.forEach(function(task) {
console.log(task.name)
})
});
So i'm able to console.log each task.name, but where to now? How do I get my component to display each task as a ?
Basically, where in a component does this type of logic go? Do i save the fetch request to a variable and then setState = variable?
this is my component:
class Task extends React.Component {
render() {
return <p> hey </p>
}
}
You need to initialize a state object, which you can update when the fetch is complete:
class Task extends React.Component {
constructor () {
super()
this.state {
tasks: null
}
}
componentDidMount () {
fetch('/task')
.then((data) => {
return data.json()
})
.then((json) => {
this.setState({ tasks: json.tasks })
})
}
renderTaskList () {
if (this.state.tasks) {
return (
<ul>
{this.state.tasks.map((task, i) => <li key={i}>{task.name}</li>)}
</ul>
)
}
return <p>Loading tasks...</p>
}
render () {
return (
<div>
<h1>Tasks</h1>
{this.renderTaskList()}
</div>
)
}
}
Edit: Re-reading this answer, I just wanted to note that it is not necessary to initialize the tasks property of the state object in this case. You could also just do something like:
this.state = {}
However, I think there is some value in explicitly naming the various properties of your state object, even if they are initialized as null. This allows you to write components whose state is documented in the constructor, and will prevent you or your teammates from later guessing how a component's state is modeled.

Categories