React rendering component before ComponentWillMount function / setState not working - javascript

I'm new to React and been struggling with my code and can't find what I'm doing wrong,
This is my code
export class MainPage extends Component {
constructor(props){
super(props);
this.state = {
pn_list : []
}
}
componentWillMount() {
axios.get('http://localhost:8080/pn_info/over_30')
.then((response) => {
this.setState(
{pn_list : response.data}
)
})
}
render() {
return(
<div>
<SelectionMenu data={this.state.pn_list}/>
</div>
)
};
};
export default MainPage;
Basically my code is trying to render the SelectionMenu component before the componentWillMount finishes and I don't get why this is happening.
I tried to console.log(this.state.pn_list) after axios gets the response but it looks like the setState method is not updating it since it still logs the empty array.
Can you give me a help please?

Instead of componentWillMount, you should use componentDidMount for AJAX calls and populate state.
See more at: https://reactjs.org/docs/faq-ajax.html#where-in-the-component-lifecycle-should-i-make-an-ajax-call

Related

React not loading state properly on mount during axios call

Basically I am making a basic react App that is grabbing some data from a DB and I am stuck on the basic setup.
My intention is to have my state contain the response from my server querying my databse.
My response is 100% working and sending the data back as expected from the axios call, however the state is never getting update.
EDIT : I am attempting to pass the movies down the chain to a Component called MovieList, I have provided the code for that as well.
App.jsx
import React from 'react';
import axios from 'axios';
import MovieList from './MovieList.jsx';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {allMovies: []};
}
componentDidMount() {
var that = this;
axios.get('http://localhost:3000/api/movies')
.then( res => {
that.setState({allMovies: res.data});
})
.catch( err => {
console.log(`Err # [ App.jsx - componentDidMount ] ::: ${err}`);
});
}
render() {
return (
<div>
<h1>Hello World!</h1>
<MovieList movies={this.state.allMovies} />
</div>
)
}
}
MovieList.jsx
import React from 'react';
class MovieList extends React.Component {
constructor(props) {
super(props);
this.state = {};
console.log(this.props); //EMPTY OBJECT MOVIES DIDN'T GET INTO PROPS
}
render() {
return (
<div>
<h1>Test</h1>
</div>
)
}
}
export default MovieList;
NOTE : I also logged the props on mount and attempted to render them and they were empty.
Basically if I try to pass down this.state.allMovies or console.log it, its always just the initial empty array.
Maybe I don't understand how async setting the state can be done? I took a similar approach on my last school project and it seemed to work fine.
You don't await the axios promise to resolve, so you simply are logging what the state is when the component mounts. Use one of the following to log updated react state.
Use componentDidUpdate to log the updated state.
componentDidMount() {
console.log(this.state);
}
Use the setState callback function to log the state
componentDidMount() {
var that = this;
axios.get('http://localhost:3000/api/movies')
.then( res => {
console.log(res.data);
that.setState(
{ allMovies: [res.data] },
() => console.log(this.state), // <-- setState callback function
);
})
.catch( err => {
console.log(`Err # [ App.jsx - componentDidMount ] ::: ${err}`);
});
}
You'll never see it in your constructor, because when your component is instantiated, it's done so with an empty array.
You will see it if you do a console.log(this.props) in componentDidUpdate or render however.
This is because when App is mounted, your component passes a movies prop of [] to MovieList. After the movies return from the server (and you update the state of App), App will render again and pass the array returned from the server, causing your MovieList component to render again. It's constructor won't be called, because it's already instantiated, but MovieList will call componentDidUpdate and render again.
class MovieList extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
console.log(this.props); // you'll see this get logged twice - once with an empty array for movies and once with the movies returned from the server.
return (
<div>
<h1>Test</h1>
</div>
)
}
}

Call a function in react component, WITHOUT event handlers or props

sorry if this question appeared somewhere else, but it's getting extremely frustrating to find answers where every question involves event handler or child element method calling.
I need to call a function when component is initialized, basically when window loads, or instantly.
On initialization I want to call a getGameMeta() to update Game state, if I'm trying to call it in jsx either I make a loop or get an error saying "Functions are not valid as a React child. This may happen if you return a Component instead of from render...."
class Game extends React.Component{
constructor(props) {
super(props);
this.state = {name: undefined,};
this.getGameMeta = this.getGameMeta.bind(this);
}
getGameMeta(){
fetch(Url).then(data => {
console.log(data);
this.setState({
name: data[0].name
});
});
};
render(){
return (
<div>
{/* {this.getGameMeta()} */} causes loop
{/* {this.getGameMeta} */} causes error
<p>{this.state.name}</p>
</div>
);
};
};
Using the componentDidMount hook is a great way to load data from a remote endpoint when the component is first mounted.
Example
class Game extends React.Component {
constructor(props) {
super(props);
this.state = { name: undefined };
this.getGameMeta = this.getGameMeta.bind(this);
}
componentDidMount() {
this.getGameMeta();
}
getGameMeta() {
fetch(Url).then(data => {
console.log(data);
this.setState({
name: data[0].name
});
});
}
render() {
return (
<div>
<p>{this.state.name}</p>
</div>
);
}
}
You can call it in componentDidMount. It guarantees that it will be called once and right after when component will be mounted. More over from React Docs:
If you need to load data from a remote endpoint, this is a good place
to instantiate the network request.
getGameMeta(){
fetch(Url).then(data => {
console.log(data);
this.setState({
name: data[0].name
});
});
};
componentDidMount(){ this.getGameMeta() }
So seems like this is the way you are looking for
You can simply use useEffect if you are using a functional component.
basically, it loads the data before rendering your UI.
import {useEffect} from "react";
const GameData=()=>{
const [fetchD,setFetchD]=useState("");
useEffect(()=>{
fetch(Url).then(data => {
console.log(data);
setFetchD(data[0].name);
});
});
})
}
export default GameData;
//you can also check react documentation at https://reactjs.org/docs/hooks-effect.html

Rendering component with its state

I'm really confused now about lifecycle hooks. Here's my code:
App.js:
class App extends Component {
constructor(props){
super(props);
this.state = {
arrayOfComponents: []
}
}
componentDidMount(){
//i get the properties from the server which responds with the database's elements
fetch('http://localhost:3001/')
.then(res => res.json())
.then(arrayOfData => this.setState({arrayOfComponents: arrayOfData}))
.catch(err => console.log(err))
}
render() {
console.log(this.state) //first returns empty array, after the mount returns the db's array of elements
return (
<div className="App">
<Component name='First' id={1} componentsComponents={this.state.arrayOfComponents} />
</div>
);
}
}
Component.js:
class Component extends React.Component{
constructor(props){
super(props);
this.state={
componentsComponents: []
}
}
//here i was tried with componentDidMount, componentWillMount to set the
//this.props.componentsComponents to this.state.componentsComponents
//but it didn't work
renderComponents = () => {
if(this.state.componentsComponents.length){
return this.state.componentsComponents.filter(c => c.inhertedFromId === this.props.id).map(c => {
return <Component name={c.name} id={c.id} componentsComponents={this.props.componentsComponents} />
})
}
}
render(){
return(
<div>
{this.renderComponents()}
</div>
)
}
}
So what i want to do is to the components renders themselves, depending on the array they get from the App.js. But how to set the state before the render happens? Can i somehow ask the component to render again if it did mount? Or any other solutions?
You can simply assign this.props.componentsComponents in constructor itself only.
constructor(props){
super(props);
this.state={
componentsComponents: this.props.componentsComponents||[]
}
}
Bring Filter Up To App
Here it appears you are not calling renderComponents, and you are also trying to render a Component inside itself, which is difficult to reason about. Bring the renderComponents function up to App, and render the data using Component inside of App, and simply pass props down to a stateless Component, which may be a simpler solution to represent the data.
If a recursive call is indeed the best way to represent this data, may need to use getDerivedStateFromProps to move props into state on update, if you wish to store that data in state - something like:
static getDerivedStateFromProps(nextProps) {
return {
componentsComponents: nextProps.componentsComponents
}
}
Added to Component.

How can I set initial React state from API data?

I have a rest API back end set up. I want to set values inside the getInitialState function for a component, but I can't figure out how to populate the object I need to return because I am using an asynchronous http request. The object I return has undefined values, as expected. How do I get around this?
I'm using fetch (can switch to any other library, honestly) as of now. I can't figure out how to call getInitialState after the asynchronous call has returned some value instead of before it happens.
import React from 'react';
import 'whatwg-fetch';
export default class IndexPage extends React.Component {
render() {
// I basically need to call getInitialState after the last promise has been resolved
fetch('https://localhost:3000/api/aye')
.then(function(response) {
return response.json();
})
.then(function(json) {
// Need to return some values from this.
});
return (
<div>
<h1>{this.state.jsonReturnedValue}</h1>
</div>
);
}
}
Thanks in advance!
You should call this.setState in order to change state values
export default class IndexPage extends React.Component {
constructor() {
super();
this.state = {
jsonReturnedValue: null
}
}
componentDidMount() {
fetch('https://localhost:3000/api/aye')
.then(response => response.json())
.then(json => {
this.setState({ jsonReturnedValue: json });
});
}
render() {
return (
<div>
<h1>{ this.state.jsonReturnedValue }</h1>
</div>
);
}
}
In your situation -
It's better to get the rendering done for the first time with empty state data lets say
constructor(props){
super(props);
this.state = {
data : []
};
}
and make ajax call in componentDidMount, this is the place where you can perform dom manipulation and send ajax request to fetch data via REST.
After new data is fetched from server set the state with new data
this.setState({data:newDataFromServer});
e.g In componentDidMount
componentDidMount() {
sendAjaxRequest()
.then(
(newDataFromServer) => {
this.setState({data : newDataFromServer });
});
}
This will cause the re-rendering to happen with latest data fetched from server and new state changes will be reflected.
You should do something along the following. Firstly, create the state in the constructor method. This will then allow you to use this value in your render method by referencing {this.state.jsonReturnedValue}. The constructor() method is the ES6 version of getInitialState() in case you are not aware of this. This is where you should set the state of the component.
Then in the componentDidMount which will run after the render method you can make the API call api and use React's setState method to update the value of this.state.jsonReturnedValue.
When this.setState() runs and sets a new value it will then run the render method again and update the view accordingly.
export default class IndexPage extends React.Component {
constructor(){
super();
this.state = {
jsonReturnedValue: null
}
}
componentDidMount() {
this.jsonList();
}
jsonList() {
fetch('https://localhost:3000/api/aye')
.then(function(response) {
this.setState({
jsonReturnedValue: response.json()
})
})
}
render() {
return (
<div>
<h1>{this.state.jsonReturnedValue}</h1>
</div>
);
}
}

React: componentDidMount + setState not re-rendering the component

I'm fairly new to react and struggle to update a custom component using componentDidMount and setState, which seems to be the recommended way of doing it. Below an example (includes an axios API call to get the data):
import React from 'react';
import {MyComponent} from 'my_component';
import axios from 'axios';
export default class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
data: []
};
}
GetData() {
return axios.get('http://localhost:5000/<route>');
}
componentDidMount() {
this.GetData().then(
(resp) => {
this.setState(
{data: resp.data}
)
}
)
}
render() {
return (
<MyComponent data={this.state.data} />
);
}
}
Doing console.log(this.state.data) just below render() shows that this.state.data does indeed get updated (from [] to whatever the API returns). However, the problem appears to be that MyComponent isn't rendered afresh by componentDidMount. From the Facebook react docs:
Setting state in this method will trigger a re-rendering.
This does not seem to be the case here: The constructor of MyComponent only gets called once (where this.props.data = []) and the component does not get rendered again. I'd be great if someone could explain why this is and whether there's a solution or a different way altogether to get the updating done.
UPDATE
I've added the code for MyComponent (minus some irrelevant features, as indicated by ...). console.log(data_array) prints an empty array.
import React from 'react';
class DataWrapper {
constructor(data) {
this._data = data;
}
getSize() {
return this._data.length;
}
...
}
export class MyComponent extends React.Component {
constructor(props) {
super(props);
this._dataWrapper = new DataWrapper(this.props.data);
this.state = {
data_array: this._dataWrapper,
};
}
render() {
var {data_array} = this.state;
console.log(data_array);
return (
...
);
}
}
You are falling victim to this antipattern.
In MyComponent constructor, which only gets called the first time it mounts, passed your empty array through new DataWrapper and now you have some local state which will never be updated no matter what your parent does.
It's always better to have one source of truth, just one state object anywhere (especially for things like ajax responses), and pass those around via props. In fact this way, you can even write MyComponent as a simple function, instead of a class.
class Example extends Component {
state = { data: [] }
GetData() { .. }
componentDidMount() {
this.GetData().then(res =>
this.setState({data: new DataWrapper(res.data)})
)
}
render() { return <MyComponent data={this.state.data} /> }
}
...
function MyComponent (props) {
// props.data will update when your parent calls setState
// you can also call DataWrapper here if you need MyComponent specific wrapper
return (
<div>..</div>
)
}
In other words what azium is saying, is that you need to turn your receiving component into a controlled one. Meaning, it shouldn't have state at all. Use the props directly.
Yes, even turn it into a functional component. This helps you maintain in your mind that functional components generally don't have state (it's possible to put state in them but ... seperation of concerns).
If you need to edit state from that controlled component, provide the functions through props and define the functions in the "master" component. So the master component simply lends control to the children. They want anything they talk to the parent.
I'm not posting code here since the ammendment you need to make is negligible. Where you have this.state in the controlled component, change to this.props.

Categories

Resources