React-Redux - passing down props before async data arrives - javascript

I receive the "Uncaught TypeError: Cannot read property 'data' of null" error in the console when I'm loading up my page.
In the constructor I call an external api for data with axios using redux-promise, then I pass props down to a stateless component (TranslationDetailBox).
The data arrives, and the child component will receive the props shortly (page looks ok), but first the error message appears.
this.props.translation is empty before the api call, and rendering happens before the state is updated with the external data. I believe this is the issue, but I have no clue on how to solve it.
class TranslationDetail extends Component {
constructor(props){
super(props);
this.props.fetchTrans(this.props.params.id);
}
render() {
return (
<div className="container">
<TranslationDetailBox text={this.props.translation.data.tech}/>
<TranslationDetailBox text={this.props.translation.data.en}/>
</div>
);
}
}
function mapState(state) {
const { translation } = state;
return { translation };
}
...

I find this to be a good use case for conditional rendering.
Your render could check to see whether the data has loaded or not and if not, render something indicating it is still loading.
A simple implementation could be:
render() {
return (
!this.props.data ?
<div>Loading...</div>
:
<div>{this.props.data}</div>
)
}

You could set defaultProps:
TranslationDetail.defaultProps = {
translation: {
data: {}
}
};

Related

How to prevent render method from throwing error on a state that is initially set to undefined

I am trying to render my state which updates by pulling data from an api so the initial value of the state is undefined. I am receiving the error TypeError: undefined is not an object (evaluating 'this.state.Pods[0].Name'). From what I understand about react, every time the state is updated the components that use that state re-renders, so the text that initially was undefined will then render the updated state value. My question is could you bypass the initial error from being thrown so it can render the value of state when its updated. Here's how im calling the state:
<Text>{this.state.Pods[0].Name}</Text>
Ive tried using getDerivedStateFromError but I it isn't working for me.
static getDerivedStateFromError(error) {
return { hasError: true };
}
render() {
if (this.state.hasError) {
return <Text>Something went Wrong</Text>
}
Well, verify it before you do anything.
So verify if this.state.Pods is there and this.state.Pods[0] is there.
Having said that, directly accessing an index and then only using that single one, begs the question, why would you need an array?
So, you can first verify if this.state.hasError is true, then return the error message, if it's not true, and you don't have the pods yet, then return null, otherwise return your actual render, like:
render() {
const { hasError, Pods = [] } = this.state;
if (hasError) {
return <div>Error occured</div>;
}
if (Array.isArray(Pods) || !Pods[0]) {
return null;
}
return <Text>{Pods[0].Name}</Text>;
}
When the state gets updated, your component will rerender and it will again check all the conditions, giving you either an error, again null (if Pods is not there yet or not what you expect it to be) or it will render your text element
Please use this
render() {
if (!this.state.Pods || !this.state.Pods[0] ) {
return <Text>Loading</Text>
}
That is not the correct way of using error boundary component. if you are expected
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return { hasError: true };
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
In your component.
render() {
<ErrorBoundary>
<Text>{this.state.Pods[0].Name}</Text>
</ErrorBoundary>
}
though you don't need error boundry in your case.
you should read your state safely as suggested in other answers.
You have to update your code as below.
<Text>{Array.isArray(this.state.Pods) && this.state.Pods.length > 0 && this.state.Pods[0].Name}</Text>
Hope this will work for you!
Try using constructor or instance variable like:
class A extends React.Component {
state = {
Pods: []
}
}
OR
class A extends React.Component {
constructor() {
this.state = {
Pods: []
}
}
}
This way your state will have a value intially, now inside render you can check if your data from the API has arrived:
render(){
if (this.state.Pods.length) {
return <div>{this.state.Pods}</div>
}
return ''
}

What component lifecycle to use to do something before render()?

I need to check whether some props (from redux store) is an empty object or not. If it is empty, I want the page to redirect to another page and not bother to call render().
The current flow is something like:
constructor(props) {
this.checkObject();
}
checkObject() {
if (Object.keys(someObj).length === 0 && someObj.constructor === Object) {
this.props.history.push("/some-other-route");
}
}
render() {
// some code
}
However, when I do a console.log, render() is being called after checkObject() which causes some errors because render() needs a non-empty object to display content properly. That's the reason I don't want react to even call render() if the object is empty (which I check through checkObject()) and just redirect to another page.
So is there a lifecycle method to use that will execute my redirection code before render() is called?
You could use the Redirect component of react-router within render.
import { Redirect } from 'react-router'
render(){
(checkIfObjectEmpty)?<Redirect to = '/some_route'/>:<JSX>
}
Return inside render
constructor(props) {
this.checkObject();
}
checkObject() {
return Object.keys(someObj).length === 0 && someObj.constructor === Object
}
render() {
if(this.checkObject()) return <Redirect to=/some-other-route" />
//rest of code not run if object is empty
}
Update:
Add super(props) to your constructor. I think it should solve the problem. In this case, no need to componentWillMount(). Your code should work just fine.
Unsafe and temporary solution:
You can use componentWillMount().
The first true life cycle method called is componentWillMount(). This method is only called one time, which is before the initial render.
constructor(props) {
}
checkObject() {
if (Object.keys(someObj).length === 0 && someObj.constructor === Object) {
this.props.history.push("/some-other-route");
}
}
componentWillMount() {
this.checkObject();
}
render() {
// some code
}
history.push() is a side effect that won't prevent the component to be initially rendered, so it belongs to componentDidMount.
Since the result depends on props, the check could go to getDerivedStateFromProps to provide redirect flag in a component with local state. In a component that is connected to Redux it can be performed in mapStateToProps:
connect(({ someObj }) => ({ redirect: !Object.keys(someObj) }))(...)
The component shouldn't be rendered if it will redirect:
componentDidMount() {
if (this.props.redirect)
this.props.history.push("/some-other-route");
}
render() {
return !this.props.redirect && (
...
);
}
As another answer correctly mentions, <Redirect> component is a declarative alternative to calling history.push() directly.

JS Promise is stuck to my code

Its been 48 hours and still couldn't figured it out.
I'm dealing with a promise using a Clarifai api for react native. Here is my code:
function updater(){
return ClarifaiApp.models.predict("coiner app", "https://www.coinsonline.com/wp-content/uploads/2015/11/Half-Dimes-and-Dimes.jpg").then(
function(response){
par1 = response['outputs'][0]['data']['concepts'][0];
return (par1.name);
});
}
export default class CameraScreen extends React.Component {
render(){
return(
function updater2(){
updater().then(function(getAlll){
<Text> {getAlll} </Text> //error: trying to add an object - invalid
});
);
}
}
Now, the thing is that I want to get the value of 'par1'. The problem is that whenever I try to get the value of getAlll in console which is par1, I can get the string I wanted but when I try to add it as {variable} inside text, it gives an error that I'm trying to an object inside .
I think the problem is you're trying to render a Promise inside render. I think the message may be a little misleading. Here is a codepen that explains one way to handle renders that depend on async data.
https://codepen.io/sscaff1/pen/bMVvgN
export default class CameraScreen extends React.Component {
state = { getAll: null }
componentDidMount() {
updater().then(getAll => this.setState({ getAll }));
}
render(){
const { getAll } = this.state;
return getAll ? <Text>{getAll}</Text> : null // you can also put a loader here
}
}

ReactJS: I can not get the state to update with API data when the asynchronous API data fetch is completed

I am having a bit of an issue rendering components before the state is set to the data from a returned asynchronous API request. I have a fetch() method that fires off, returns data from an API, and then sets the state to this data. Here is that block of code that handles this:
class App extends Component {
constructor() {
super();
this.state = {
currentPrice: null,
};
}
componentDidMount() {
const getCurrentPrice = () => {
const url = 'https://api.coindesk.com/v1/bpi/currentprice.json';
fetch(url).then(data => data.json())
.then(currentPrice => {
this.setState = ({
currentPrice: currentPrice.bpi.USD.rate
})
console.log('API CALL', currentPrice.bpi.USD.rate);
}).catch((error) => {
console.log(error);
})
}
getCurrentPrice();
}
You will notice the console.log('API CALL', currentPrice.bpi.USD.rate) that I use to check if the API data is being returned, and it absolutely is. currentPrice.bpi.USD.rate returns an integer (2345.55 for example) right in the console as expected.
Great, so then I assumed that
this.setState = ({ currentPrice: currentPrice.bpi.USD.rate }) should set the state without an issue, since this data was received back successfully.
So I now render the components like so:
render() {
return (
<div>
<NavigationBar />
<PriceOverview data={this.state.currentPrice}/>
</div>
);
}
}
export default App;
With this, I was expecting to be able to access this data in my PriceOverview.js component like so: this.props.data
I have used console.log() to check this.props.data inside my PriceOverview.js component, and I am getting 'null' back as that is the default I set intially. The issue I am having is that the components render before the API fetch has ran it's course and updated the state with the returned data. So when App.js renders the PriceOverview.js component, it only passes currentPrice: null to it, because the asynchronous fetch() has not returned the data prior to rendering.
My confusion lies with this.setState. I have read that React will call render any time this.setState is called. So in my mind, once the fetch() request comes back, it calls this.setState and changes the state to the returned data. This in turn should cause a re-render and the new state data should be available. I would be lying if I didn't say I was confused here. I was assuming that once the fetch() returned, it would update the state with the requested data, and then that would trigger a re-render.
There has to be something obvious that I am missing here, but my inexperience leaves me alone.. cold.. in the dark throws of despair. I don't have an issue working with 'hard coded' data, as I can pass that around just fine without worry of when it returns. For example, if I set the state in App.js to this.state = { currentPrice: [254.55] }, then I can access it in PriceOverview.js via this.props.data with zero issue. It's the async API request that is getting me here, and I am afraid it has gotten the best of me tonight.
Here App.js in full:
import React, { Component } from 'react';
import './components/css/App.css';
import NavigationBar from './components/NavigationBar';
import PriceOverview from './components/PriceOverview';
class App extends Component {
constructor() {
super();
this.state = {
currentPrice: null,
};
}
componentDidMount() {
const getCurrentPrice = () => {
const url = 'https://api.coindesk.com/v1/bpi/currentprice.json';
fetch(url).then(data => data.json())
.then(currentPrice => {
this.setState = ({
currentPrice: currentPrice.bpi.USD.rate
})
console.log('API CALL', currentPrice.bpi);
}).catch((error) => {
console.log(error);
})
}
getCurrentPrice();
}
render() {
return (
<div>
<NavigationBar />
<PriceOverview data={this.state.currentPrice}/>
</div>
);
}
}
export default App;
Here is PriceOverview.js in full:
import React, { Component } from 'react';
import './css/PriceOverview.css';
import bitcoinLogo from './assets/bitcoin.svg';
class PriceOverview extends Component {
constructor(props) {
super(props);
this.state = {
currentPrice: this.props.data
}
}
render() {
return (
<div className="overviewBar">
<div className="currentPrice panel">
{ this.state.currentPrice != null ? <div className="price">{this.state.currentPrice}</div> : <div className="price">Loading...</div> }
</div>
</div>
)
}
}
export default PriceOverview;
Thank you in advance to any help, it's much appreciated.
this.setState ({
currentPrice: currentPrice.bpi.USD.rate
})
Do not put an = in this.setState
Ok First thing, when you're writting code on React the components that hold state are the class base components so ... What I see here is that you're creating two class base components so when you pass down props from your app class component to your PriceOverview wich is another class base component you're essentially doing nothing... Because when your constructor on your PriceOverview get call you're creating a new state on that Component and the previous state ( that's is the one you want to pass down) is being overwritten and that's why you're seem null when you want to display it. So it should work if you just change your PriveOverview component to a function base component ( or a dumb component). So this way when you pass down the state via props, you're displaying the correct state inside of your div. This is how would look like.
import React from 'react';
import './css/PriceOverview.css';
import bitcoinLogo from './assets/bitcoin.svg';
const PriceOverview = (data) => {
return (
<div className="overviewBar">
<div className="currentPrice panel">
//Im calling data here because that's the name you gave it as ref
//No need to use 'this.props' you only use that to pass down props
{data != null ? <div className="price">
{data}</div> : <div className="price">Loading...</div>
}
</div>
</div>
)
}
}
export default PriceOverview;
Whenever you're writing new components start always with function base components if you component is just returning markup in it and you need to pass some data go to his parent component update it (making the api calls there or setting the state there) and pass down the props you want to render via ref. Read the React docs as much as you can, hope this explanation was useful (my apologies in advance if you don't understand quite well 'cause of my grammar I've to work on that)
The thing is constructor of any JS class is called only once. It is the render method that is called whenever you call this.setState.
So basically you are setting currentPrice to null for once and all in constructor and then accessing it using state so it will always be null.
Better approch would be using props.
You can do something like this in your PriceOverview.js.
import React, { Component } from 'react';
import './css/PriceOverview.css';
import bitcoinLogo from './assets/bitcoin.svg';
class PriceOverview extends Component {
constructor(props) {
super(props);
this.state = {
}
}
render() {
return (
<div className="overviewBar">
<div className="currentPrice panel">
{ this.props.data!= null ? <div className="price">{this.props.data}</div> : <div className="price">Loading...</div> }
</div>
</div>
)
}
}
export default PriceOverview;
Or you can use react lifecycle method componentWillReceiveProps to update the state of PriceOverview.js
componentWillReceiveProps(nextProps) {
this.setState({
currentPrice:nextProps.data
});
}
render() {
return (
<div className="overviewBar">
<div className="currentPrice panel">
{ this.state.currentPrice != null ? <div className="price">{this.state.currentPrice }</div> : <div className="price">Loading...</div> }
</div>
</div>
)
}
}

React Redux - Error on render because expecting props that didn't arrive yet

After fetching the data from an API, and putting that data on the Redux state, I'm using a helper function in mapStatetoProps to select and modify part of that data and pass it modified to the props.
So without the rendering I can see in the console.log that everything goes as it should.
Empty props: this.props.pageContent = {}
The data fetch and mapped to props: this.props.pageContent = { pageSection: [{something here that I don't want}, {}, {}...] }
The data as I want it selected and passed to the props: this.props.pageContent = { pageSection: [{card: 'my Data'}, {}, {}...] }
but when I pass some propsto a component it throws an error because those props that I'm trying to pass haven't arrived yet to this.props (in this case card.cardTitle)
This is my code so far:
class Page extends Component {
componentWillMount() {
this.props.fetchPageContent();
}
render() {
console.log(this.props.pageContent)
if (!this.props.pageContent.pageSection[0].card) return null;
return (
<div>
<PageSection
introSectionCardTitle={ this.props.pageContent.pageSection[0].card.cardTitle}
introSectionCardContent={ this.props.pageContent.pageSection[0].card.cardContent}
/>
</div>
);
}
Any ideas?
before the return I tried to have an if statement with diferent options, but the error keeps the same:
TypeError: Cannot read property '0' of undefined
You have a problem here if (!this.props.pageContent.pageSection[0].card)
replace
if (!this.props.pageContent.pageSection[0].card)
with
if(this.props.pageContent && this.props.pageContent.pageSection && this.props.pageContent.pageSection[0].card)
Because you are not sure that your props has pageContent and you are also not sure that pageSection exist, because before setting the props pageContent is undefined and you are trying to access an object inside it and then find element inside an array
Try the updated code below:
class Page extends Component {
componentWillMount() {
this.props.fetchPageContent();
}
render() {
console.log(this.props.pageContent)
if(this.props.pageContent && this.props.pageContent.pageSection && this.props.pageContent.pageSection[0].card)
{
return (
<div>
<PageSection
introSectionCardTitle={ this.props.pageContent.pageSection[0].card.cardTitle}
introSectionCardContent={ this.props.pageContent.pageSection[0].card.cardContent}
/>
</div>
);
}
else
{
return (<div></div>);
}
}

Categories