How to wait state change to render? - javascript

So, I need to get the response from a request and then send it to another component. The problem is that my request isn't finished when the component call happens. So what I end up getting on the "TableComponent" is an empty array
This is the component I'm making the request at:
class Carrinho extends Component {
constructor(props) {
super(props);
this.getMateriais()
}
async getMateriais() {
let service = new MateriaisService();
service.getMateriais().then(res => res.json()).then((result) => {
this.setState({materiais: result})
})
}
render() {
return (
<div>
<TableComponent materiais={this.state.materiais} itens={this.state.array_teste}></TableComponent>
</div>
);
}
And this is how I'm setting my state on TableComponent.js :
constructor(props) {
super(props);
this.state = {
materiais : props.materiais,
}

This won't work, because this.getMateriais() call in the constructor, won't trigger a new render. You'll need to use componentDidMount life cycle and async/await syntax.
class Carrinho extends Component {
constructor(props) {
super(props);
this.getMateriais()
}
async componentDidMount(){
await this.getMateriais();
}
async getMateriais() {
let service = new MateriaisService();
const result = await service.getMateriais();
const data = await result.json();
this.setState({ materiais: result });
}
render() {
return (
<div>
<TableComponent materiais={this.state.materiais} itens={this.state.array_teste}></TableComponent>
</div>
);
}
However, async/await is not recommendable to deal with promises in React programming model. Instead, you should render a different component or a loading, while waiting.
class Carrinho extends Component {
constructor(props) {
super(props);
}
componentDidMount(){
this.getMateriais();
}
getMateriais() {
let service = new MateriaisService();
service.getMateriais().then(res => res.json()).then((result) => {
this.setState({materiais: result})
})
}
render() {
return (
<div>
{this.state.materiais && <TableComponent materiais={this.state.materiais} itens={this.state.array_teste}></TableComponent>}
</div>
);
}

Related

Pass this.state variable through App Context

When I set the array data using the function getData() then try to call it in the function updateData() I get an error saying the this.state.data is undefined. Any thoughts on how I can pass a this.state variable from one function to another function in the app context provider?
Example code is below:
Any thoughts? Thank you!
export class AppProvider extends React.Component {
constructor(props) {
super(props);
(this.state = {
data: [],
});
}
getData = async () => {
const data = "abc"
this.setState({
data,
});
}
updateData = async () => {
console.log(this.state.data)
}
render() {
return (
<AppContext.Provider value={this.state}>
{this.props.children}
</AppContext.Provider>
);
}
}
Three Things i would like to say,
you want to add the state variables separately so you want to do value={{data:this.state.data}}
if you plan on using these functions in another component you want to add these functions to the value prop as well
remove the async from the functions since there is no Promise to be resolved
export class AppProvider extends React.Component {
constructor(props) {
super(props);
this.state = {
data: []
};
}
getData = () => {
const data = "abc";
this.setState({
data
});
};
updateData = () => {
console.log(this.state.data);
};
render() {
return (
<AppContext.Provider
value={{
data: this.state.data,
getData: this.getData,
updateData: this.updateData
}}
>
{this.props.children}
</AppContext.Provider>
);
}
}
checked this in a small example, CodeSandbox here

Local JSON file is not parsing in React

I have a large JSON file which has around 5000 entries and when I parse it using fetch(), it doesn't show up in browser.
Here's my code:
import React from 'react';
import './Box.css';
class Box extends React.Component {
constructor() {
super()
this.state = {movieName: []}
}
componentDidMount() {
fetch('./MovieDatabaseShort.json')
.then(a => a.json())
.then(movieName => this.setState({movieName}));
}
renderMovies() {
const { movieName } = this.state;
return movieName.map(a => {
<h1 key={ a.id } className='heading'>{a.title}</h1>;
});
}
render() {
return <div className="box">{this.renderMovies()}</div>;
}
}
export default Box;
I just want to put all the movies titles.
import React from 'react';
import './Box.css';
class Box extends React.Component {
constructor() {
super()
this.state = {movieName: []}
}
componentDidMount() {
fetch('https://support.oneskyapp.com/hc/en-us/article_attachments/202761627/example_1.json')
.then(a => a.json())
.then(movieName => this.setState({movieName: movieName.color}));
}
render() {
console.log( this.state );
return <div className="box">{this.state.movieName}</div>;
}
}
export default Box;
EDIT- In second code, I just copied random json file from net and it works fine. I think its's due to size of the json file I have. It's 250k+ lines.
Update- This works. I think problem is due to fetch()
import React from 'react';
import './Box.css';
import a from './MovieDatabaseShort.json'
class Box extends React.Component {
constructor() {
super()
this.state = {movieName: []}
}
componentDidMount() {
this.setState({movieName: a});
}
renderBox() {
const { movieName } = this.state;
return movieName.map(k => {
return <h1 className='heading'>{k.title}</h1>;
})
}
render() {
return (
<div className='box'>{this.renderBox()}</div>
);
}
}
export default Box;`
First of all, there are some places you should change in your code.
You should keep an array property in your state for all movies: movies: []
You should map this state value, then render some JSX.
Use componentDidMount instead of componentWillMount since it will be deprecated in a future release.
Here is the example code:
class Box extends React.Component {
constructor() {
super();
this.state = { movies: [] };
}
componentDidMount() {
fetch("./MovieDatabaseShort.json")
.then(res => res.json())
.then(movies => this.setState({ movies }));
}
renderMovies() {
const { movies } = this.state;
return movies.map(movie => (
<h1 key={movie.title} className="heading">
{movie.title}
</h1>
));
}
render() {
return <div className="box">{this.renderMovies()}</div>;
}
}
If you still don't see anything maybe fetch would the problem here. Then, try this:
class Box extends React.Component {
constructor() {
super();
this.state = { movies: [] };
}
componentDidMount() {
import("./MovieDatabaseShort.json").then(movies =>
this.setState({ movies })
);
}
renderMovies() {
const { movies } = this.state;
return movies.map(movie => (
<h1 key={movie.title} className="heading">
{movie.title}
</h1>
));
}
render() {
return <div className="box">{this.renderMovies()}</div>;
}
}
Again, if nothing is shown up please share you JSON file with us as well as check your console if there is any error.
What it looks like you want to do is to save all movies into an array on your state. That would look more like this:
constructor() {
super()
this.state = {movies: []}
}
componentWillMount() {
fetch('./MovieDatabaseShort.json')
.then(a => a.json())
.then(b => this.setState({movies: b}));
}
Then in your render function you would loop over your movies and display the title:
render() {
const { movies } = this.state;
return (
<div className='box'>
{movies.map(movie => <h1 className='heading'>{movie.title}</h1>)}
</div>
);
}
Another way using hook can be the following. In my case I need to take configuration data from a json file
import _data from '../../json/config.json';
export const Mapa = () => {
const [config, setConfig] = useState(null);
useEffect(()=>{
setConfig(_data );
},[]);
}

Use data outside fetch from API ( React )

I receive an array of objects from the API fetch, but i can't pass it on < ResponseTable data={} /> if i pass the car, it works.
import React from 'react'
import ResponseTable from './responsetable'
var car = [{type:"Fiat", model:"500", color:"white"}];
class Table extends React.Component{
constructor(props){
super(props);
this.state = {};
}
fetchData() {
return fetch('http://localhost:8000/sprints/23')
.then(function(response) {
console.log(response.json);
})
.then(function(myJson) {
return myJson;
});
}
componentDidMount(){
this.fetchData();
}
render(){
return(
<div>
<ResponseTable data={} />
</div>
);
}
}
export default Table;
Any help is welcome!
Set the response in state. car works because it comes from global scope.
class Table extends React.Component{
constructor(props){
super(props);
this.state = {
data: {}
}
}
fetchData() {
return fetch('http://localhost:8000/sprints/23')
.then(function(response) {
console.log(response.json);
})
.then((myJson) => {
this.setState({data: myJson});
});
}
componentDidMount(){
this.fetchData();
}
render(){
return(
<div>
<ResponseTable data={this.state.data} />
</div>
);
}
}
When you resolve your fetch, you will want to set your state component and then pass that state to your ResponseTable data
class Table extends React.Component{
constructor(props){
super(props);
this.state = {
myJson: null // define as null
}
}
fetchData() {
return fetch('http://localhost:8000/sprints/23')
.then((response) => {
console.log(response.json);
})
.then((myJson) => {
this.setState({myJson: myJson})
});
}
componentDidMount(){
this.fetchData();
}
render(){
return(
<div>
<ResponseTable data={this.state.myJson} />
</div>
);
}
}
export default Table;
Note we set the myJson state as null.
We then fetch the data. I have changed the .then functions to arrow functions so that this is scoped to the component.
We then pass this.state.myJson as a property to your child component
Why not throw the response into a state object to pass as a prop?
import React from 'react'
import ResponseTable from './responsetable'
var car = [{type:"Fiat", model:"500", color:"white"}];
class Table extends React.Component{
constructor(props){
super(props);
this.state = {
data: {}
}
}
fetchData() {
return fetch('http://localhost:8000/sprints/23')
.then(function(response) {
this.setState({data: response.json})
console.log(response.json);
})
}
componentDidMount(){
this.fetchData();
}
render(){
return(
<div>
<ResponseTable data={this.state.data} />
</div>
);
}
}
export default Table;

React Native async execution

I'd like to execute some async function to fetch some data from db without freezing the UI.
This is the code I wrote
export default class CustomComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
value:0
};
}
componentWillMount() {
this.fetchData().then(result => { this.setState(value:result);});
}
async fetchData() {
var appState = await someMethod()
return appState;
}
someMethod() {
return new Promise(resolve => {
resolve(queryFromDB())
});
}
queryFromDB() {
// Returns a value fetched from Realm
let events = this.realm.objects("Event");
return events.length;
}
render() {
return (
<Text> {this.state.value} </Text>
);
}
}
The problem is that it does execute on the main thread, freezing the app.
What's the error?
Seems like your code has syntax errors. You have written all your code inside the constructor. Try this.
export default class CustomComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
value:0
};
}
componentWillMount() {
this.fetchData().then(result => { this.setState(value:result);});
}
async fetchData() {
var appState = await someMethod()
return appState;
}
someMethod() {
return new Promise(resolve => {
resolve(queryFromDB())
});
}
queryFromDB() {
// Returns a value fetched from Realm
let events = this.realm.objects("Event");
return events.length;
}
render() {
return (
<Text> {this.state.value} </Text>
);
}
}

Getting a JSON asynchronously and THEN rendering the component

I have a component, which has to download a JSON file and then iterate over it and display each element from the JSON on the screen.
I'm kinda new with React, used to be ng dev. In Angular, I used to do it with lifecycle hooks, e.g. ngOnInit/ngAfterViewInit (get some JSON file and then lunch the iteration func). How can I achieve it in React? Is it possible to reach it with lifecycle hooks, like ComponentWillMount or ComponentDidMount.
My code (it's surely wrong):
export default class ExampleClass extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
}
}
componentWillMount(){
getData();
}
render() {
return (
<ul>
{this.state.data.map((v, i) => <li key={i}>{v}</li>)}
</ul>
)
};
}
const getData = () => {
axios.get(//someURL//)
.then(function (response) {
this.setState({data: response.data});
})
.catch(function (error) {
console.log(error);
})
};
How to force React to get the JSON before rendering the component?
Thank you so much.
Making an AJAX request in ComponentWillMount works. https://facebook.github.io/react/docs/react-component.html#componentwillmount
You could also just work that logic into your constructor depending on your exact needs.
https://facebook.github.io/react/docs/react-component.html#constructor
export default class ExampleClass extends React.Component {
constructor(props) {
super(props)
this.state = {
data: [],
}
axios.get(/*someURL*/)
.then(function (response) {
this.setState({data: response.data});
})
.catch(function (error) {
console.log(error);
})
}
}
You can do a simple if statement in your render function.
render () {
if (Boolean(this.state.data.length)) {
return <ul>{this.state.data.map((v, i) => <li key={i}>{v}</li>)}</ul>
}
return null
}
You can also use a higher order component to do the same thing.
const renderIfData = WrappedComponent => class RenderIfData extends Component {
state = {
data: []
}
componentWillMount() {
fetchData()
}
render() {
if (Boolean(this.state.data.length)) {
return <WrappedComponent {...this.state} />
}
return null
}
}
Then you can wrap the presentational layer with the HOC.
renderIfData(ExampleClass)
Not sure what version of React you are using but you may need to use <noscript> instead of null.
This is essentially preventing your component from rendering until it has all the data.

Categories