Why React doesn't render my result after setState? - javascript

Having the below jsx code:
import React, { Component } from 'react';
import RemoteAssets from '../modules/RemoteAssets';
class RemoteOptions extends Component {
constructor(props) {
super(props);
this.state = {
RemoteOptions: []
}
}
componentDidMount() {
const { api, locale } = this.props;
RemoteAssets.loadRemoteOptions({ api, locale }).then((RemoteOptions) => {
console.log( 'RemoteOptions', RemoteOptions);
this.setState((state, props) => ({
RemoteOptions
}), () => {
this.render()
});
})
}
render() {
return (
<div className="row">
<div className="col-4">
<label >Opt: </label>
</div>
<div className=" col-8">
{JSON.stringify(this.state.RemoteOptions)}
</div>
</div>
);
}
}
export default RemoteOptions;
This is what happens to me:
componentDidMount logs correctly the payload expected.
console.log( 'RemoteOptions', RemoteOptions);
So I believe that It will also set State as expected:
this.setState((state, props) => ({
RemoteOptions
}), () => {
this.render()
});
I also added above a this.render() stmt to be sure the component will be re-rendered after updating the state.
But :
{JSON.stringify(this.state.RemoteOptions)}
Will always return "[]" as the init state before componentDidMount happens and update the state.
How should I arrange this component to have my render update the with the payĆ²oad loaded async?

Name conflict
Your state name and class name are in conflict.
class RemoteOptions extends Component { // class name
constructor(props) {
super(props);
this.state = {
RemoteOptions: [] // state name
}
}
...
Call your state something different.

Why not simply using setState the way documentation suggests?
this.setState({ RemoteOptions });
Render method will be automatically called right after the state is set.

I implemented the skeleton of the problem, and everything works as expected.
const loadRemoteOptions = () => new Promise(resolve => {
setTimeout(() => resolve('myRemoteOptions'), 1000)
})
class App extends React.Component {
state = {
remoteOptions: null
}
componentDidMount(){
loadRemoteOptions().then(remoteOptions => this.setState({ remoteOptions }))
}
render(){
return this.state.remoteOptions || 'Loading...';
}
}
ReactDOM.render(<App />, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Related

react native call function from props constructor event listener

I have some event listeners on my props
constructor(props) {
super(props);
Tts.addEventListener("tts-start", event =>
console.log("started"),
this.setState({ ttsStatus: "started" })
//how to call stopTTS
); ...}
so if I have a function outside the constructor
stopTTS() {
console.log("cali");
}
how to call a function when the eventListener gets triggered? cheers
First: If you can, use hooks instead.
A functional component that can do what you want could be:
import React, { useEffect, useState } from 'react'
const Component = () => {
const [ttsStatus, setTtsStatus] = useState('')
const stopTTS = () => {
console.log("cali");
}
// This useEffect will work as a componentDidMount
useEffect(() => {
Tts.addEventListener("tts-start", event => {
console.log("started"),
setTtsStatus("started")
stopTTS() // You can call stopTTS here
})
}, [])
return null
}
export default Component
Try to avoid creating classes, the React Hooks were a new addition in React 16.8. They let you use state and other React features without writing a class, so you can have the power of a class in a cleaner function. You can know more about it in https://reactjs.org/docs/hooks-overview.html
As i mentioned in the comment, you can call the class methods inside constructor like below snippet.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
}
window.addEventListener('click', (e) => {
if (this.state.count > 4) {
this.alertCount();
}
});
}
alertCount = () => {
alert('count has become 5')
this.setState({
count: 0
})
}
clickHandler = () => {
this.setState({
count: this.state.count + 1
})
}
render() {
return (
<div >
<div>
The count is now {this.state.count}
</div>
<button onClick = {
this.clickHandler
} id = "btn" > Click Me < /button>
</div >
);
}
}
ReactDOM.render( < App / > , document.getElementById('root'))
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

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 );
},[]);
}

Need help on parsing JSON data from this API in a React app

I'm trying to get Bitcoin Price from Coindesk API. This is my app:
import React from 'react';
import ReactDOM from 'react-dom';
let bpiURL = 'http://api.coindesk.com/v1/bpi/currentprice.json';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
data: {}
};
}
componentDidMount() {
fetch(bpiURL)
.then(response => response.json())
.then(res => {
console.log(res);
return res;
})
.then(response => this.setState({ data: response }));
}
render() {
return (
<div>
<p>
{this.state.data.disclaimer}
</p>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
The problem is that it works with this.state.data.disclaimer and this.state.data.chartName but it doesn't work with this.state.data.bpi.USD.rate which is what I need. How can I get that value?
EDIT: this is what I get from this.state.data :
Objects are not valid as a React child (found: object with keys {}). If you meant to render a collection of children, use an array instead. in p (at index.js:42) in div (at index.js:37) in App (at index.js:49)
this.state.data.bpi.USD.rate works, but only once the request has completed. Before that this.state.data.bpi will give undefined, and trying to access USD on that will give rise to an error.
You could change your default data to null, and check if data is set before you use it in the render method.
Example
let bpiURL = "https://api.coindesk.com/v1/bpi/currentprice.json";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
data: null
};
}
componentDidMount() {
fetch(bpiURL)
.then(response => response.json())
.then(response => this.setState({ data: response }));
}
render() {
return (
<div>
<p>{this.state.data && this.state.data.bpi.USD.rate}</p>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
import React, {Component} from 'react';
const bpiURL = "https://api.coindesk.com/v1/bpi/currentprice.json";
class App extends Component {
// you should check component life cycle to prevent updating from fetch when component is unmounted
_isMounted = false;
state = {
data: null,
error: null
};
componentDidMount() {
this._isMounted = true;
fetch(bpiURL)
.then(response => response.json())
.then(response => this._isMounted && this.setState({data: response}))
// introduce an error catch
.catch(error => this._isMounted && this.setState({error}))
}
componentWillUnmount() {
this._isMounted = false;
}
render() {
const {data, error} = this.state;
return (
<div className="App">
{/* optional : you could show the error message */}
<p> {`USD rate : ${ (data && data.bpi.USD.rate) || error }`}</p>
</div>
);
}
}
export default App;

Update variable in React in class not extending component

I am trying to wrap my head around ReactJS and I am stumped with an issue where I want to be able to update the value of a local variable and return the updated value.
I've read about state and I've used that when working with React Components, however, this class is just defined as const and it doesn't extend React.Component.
Is there a different way I should be defining setting the variable?
Here is a simplified version of my code:
import React from 'react';
const WelcomeForm = ({welcome}) => {
var welcomeMsg = 'Test';
DynamicContentApi.loadDynamicContent('welcome_test').then((response) => {
// response.text has content
welcomeMsg = response.text;
}).catch(() => {
welcomeMsg = '';
});
return (
<p>{welcomeMsg}</p> // Returns 'Test'
);
};
export default WelcomeForm;
The easiest option here is to change your stateless component to a stateful component.
Stateless components are just JavaScript functions. They take in an
optional input, called prop.
Stateful components offer more features, and with more features comes more baggage. The primary reason to choose class components (stateful) over functional components (stateless) is that they can have state, that is what you want to update to re-render.
Here is what you can do:
class WelcomeForm extends React.Component {
state = {
welcomeMsg: ''
}
fetchFromApi() {
DynamicContentApi.loadDynamicContent("welcome_test")
.then(response => {
this.setState({welcomeMsg: response.text});
})
.catch((e) => console.log(e));
}
componentDidMount() {
fetchFromApi();
}
render() {
return (
<p>{welcomeMsg}</p>
);
}
};
If you want, for any reason, to keep your component stateless, you will have to put the loadDynamicContent() function on the Parent and pass the text to WelcomeForm as a prop. For example:
// Your WelcomeForm Component
const WelcomeForm = ({welcomeMsg}) => (
<p>{welcomeMsg}</p>
);
// Whatever it's Parent Component is
class Parent extends React.Component {
state = {
welcomeMsg: ''
}
fetchFromApi() {
DynamicContentApi.loadDynamicContent("welcome_test")
.then(response => {
// response.text has content
this.setState({welcomeMsg: response.text});
})
.catch((e) => console.log(e));
}
componentDidMount() {
fetchFromApi();
}
render() {
<WelcomeForm welcomeMsg={this.state.welcomeMsg} />
}
}
As suggested in the comments, you can pass the DynamicContentApi logic to outside:
import ReactDOM from 'react-dom'
DynamicContentApi.loadDynamicContent('welcome_test').then((response) => {
ReactDOM.render(<WelcomeForm data={response.text} />, document.getElementById('where you wanna render this'));
}).catch(() => {
console.log('error while fetching...');
});
And where you have your component:
import React from 'react';
export default class WelcomeForm extends React.Component {
render() {
return (
<p>{this.props.data}</p>
);
}
}

Asynchronously fire a shouldComponentUpdate?

Let's say I have a Timestamp component that should update every 6000 milliseconds. Here is the base:
class Timestamp extends Component {
static propTypes = {
timestamp: PropTypes.date.isRequired
};
render() {
const { timestamp } = this.props;
return (
<div className="timestamp">
{moment(timestamp).fromNow()}
</div>
)
}
}
I have read through the lifecycle of a react component, and it looks like shouldComponentUpdate is what I want -- however, there does not seem to be an asynchronous way to apply this. Eg:
shouldComponentUpdate() {
const { timestamp } = this.props;
this._timer = setInterval(() => {
// need to update
})
}
How should this be accomplished in react?
In this case better start timer in componentDidMount, and in setInterval call setState which triggers re-render
class Timestamp extends React.Component {
constructor() {
super();
this._timer = null;
this.state = { timestamp: Date.now() };
}
componentDidMount() {
this._timer = setInterval(() => this.onChangeTimestamp(), 6000);
}
componentWillUnmount() {
clearInterval(this._timer);
}
onChangeTimestamp() {
this.setState({ timestamp: Date.now() })
}
render() {
return (
<div className="timestamp">
{ new Date(this.state.timestamp).toString() }
</div>
)
}
}
ReactDOM.render(
<Timestamp />,
document.getElementById('container')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container"></div>
Considering only React, updating your component can be done either using setState() or using forceUpdate (that you should avoid).
In both cases, this is not in shouldComponentUpdate that this should be done. Put setInterval(() in componentDidMount(){}

Categories