Can not set and then modify the state of component - javascript

I've read similar error messages but the difference is most of them are centered around event binding.
I get this error: Cannot read property 'setState' of undefined
I get the error message on this.setState(state => {
I've tried to eliminate that, before I moved to that code I was simply using this.state.finalText = translation;
No matter what I do I can't get the translated text to render without state errors.
This is my second day working with react, so a good explanation would be highly appreciated.
import React from 'react';
class CardComponent extends React.Component {
constructor(props) {
super(props);
let data = this.props.data;
this.state = {finalText: ""};
let truncated = data.truncated;
}
componentDidMount() {
let data = this.props.data;
let truncated = data.truncated;
var googleTranslate = require('google-translate')('apikey');
googleTranslate.translate(data.text, 'en', function(err, translation) {
// by calling set state, React will know to render again
console.log(translation);
this.setState(state => {
state.finalText = translation;
return state;
});
});
}
render() {
let data = this.props.data;
let truncated = data.truncated;
var test = new String(truncated);
return (
<div>
<div className="card-panel grey lighten-5 z-depth-3 hoverable thin">
<div className="row valign-wrapper">
<div className="col s2">
{/*} <img src={data.user.profile_image_url} alt={data.user.name} className="circle responsive-img" />-*/}
</div>
<div className="col s10 left-align">
{(() => {
if (test=='true') {
return ( <span>ok</span>
//<span className="black-text"> {data.extended_tweet.full_text}</span>
)
} else {
return (
<span className="black-text">translated text: {this.state.finalText}</span>
)
}
})()}
</div>
</div>
<div className="row valign-wrapper right-align chip hoverable">
{/*new Date(data.created_at).toLocaleTimeString()*/}
</div>
<div className="row valign-wrapper right-align chip hoverable">
{/* {`#${data.user.screen_name}`}*/}
</div>
</div>
</div>
);
}
}
export default CardComponent;

Your problem seems to be this. If you change your callback from function... to an arrow function () => ... then this will be bound to the enclosing lexical context's this instead of execution context's this. So the fix:
googleTranslate.translate(data.text, 'en', (err, translation) => {
this.setState({
finalText: translation
});
});

Related

React render new images on button click (new fetch call to retrieve images each time)

I have two .js files App, and Image. Essentially whenever the Find EPI button is clicked in App.js an Image component is created where a fetch call is made to retrieve a list of of image srcs so that I can display it.
The problem I am facing is that I will click the button with the right API parameters, and the image will display but when I try to click the button again with new parameters the images will first clear. I would have to press the button again for the new images to appear.
I know it has to do something with the way I am triggering the displayImage in App.js (handleClick()). How would I go about switching the images straight without having to click it twice (to clear the show the images again).
I haven't used React in a while, so sorry if this is something trivial and has already been answered.
Thanks
--- App.js ---
import React from 'react';
import './App.css';
import Image from './Image';
class App extends React.Component{
constructor(props) {
super(props);
this.state = {
imageDate: '',
displayImage: false
};
this.handleChange = this.handleChange.bind(this);
this.handleClick = this.handleClick.bind(this);
}
handleChange(event) {
this.setState({ imageDate: event.target.value });
};
handleClick() {
const {displayImage} = this.state;
this.setState({displayImage : !displayImage});
}
render(){
const isDisplayImage = this.state.displayImage;
return (
<div className="row">
<div className="col-md-6 mx-auto">
<div className="form-group mb-4">
<p className="text-center text-white"> Choose a date for Earth Polychromatic Image </p>
<div className="date input-group p-0 shadow-sm">
<input
type="date"
value = {this.state.imageDate}
onChange = {this.handleChange}
className="form-control py-4 px-4"
id="imageDate"/>
<div className ="input-group-append"><span className="input-group-text px-4"><i className="fa fa-clock-o"></i></span></div>
</div>
<div className="text-center" id="findEPI">
<button onClick={this.handleClick} type="button" className="btn btn-primary">Find EPI</button>
</div>
{isDisplayImage ? <Image imageDate = {this.state.imageDate}/> : ''}
</div>
</div>
</div>
);
}
}
export default App;
--- Image.js ---
import React from 'react';
import './Image.css';
class Image extends React.Component {
constructor(props) {
super(props);
const dateSplit = props.imageDate.split('-');
this.state = {
imageDate: props.imageDate,
year: dateSplit[0],
month: dateSplit[1],
days: dateSplit[2],
enhancedepiAPI: "https://epic.gsfc.nasa.gov/api/enhanced/date/" + props.imageDate,
retrieveImageAPI: "https://epic.gsfc.nasa.gov/archive/natural/" + dateSplit[0] +
"/" + dateSplit[1] + "/" + dateSplit[2] + "/png/epic_1b_",
epiDate : [],
displayImages: false
};
}
componentDidMount() {
const fetchPromise = fetch(this.state.enhancedepiAPI);
const epiDate = this.state.epiDate;
const retrieveImageAPI = this.state.retrieveImageAPI;
fetchPromise.then(response => {
return response.json();
}).then(images => {
epiDate.push(images.map(image => retrieveImageAPI + image.identifier + ".png"));
console.log(epiDate);
this.setState({displayImages : true});
});
}
render(){
const displayImages = this.state.displayImages;
const images = this.state.epiDate[0];
return (
<div id="carouselExampleControls" className="carousel slide" data-ride="carousel">
<div className="carousel-inner">
{displayImages && images.map(function(imageSrc) {
return (
<div className="carousel-item active">
<img className="d-block w-100" src={ imageSrc }/>
</div>
);
})} </div></div>
);
}
}
export default Image;
I couldn't see any issues with the code that makes it so you have to click twice.
But I found a few things you can improve to make the code a bit cleaner if you want.
In App.js you're destructuring off isDisplayImage from state, but not imageDate. You can change it to:
const { isDisplayImage, imageDate } = this.state;
The other thing is, you're binding both handleChange and handleClick in the constructor, if you use arrow functions instead you would get rid of the binding calls in constructor, which is why arrow functions exist.
handleChange = (event) => {
this.setState({ imageDate: event.target.value });
};
handleClick = () => {
const {displayImage} = this.state;
this.setState({displayImage : !displayImage});
}
Sorry that I couldn't resolve your issue.

React access parent's dom node from child component

I have a grid component as follow:
import React, { Component } from 'react';
import Action from './action.jsx';
class Grid extends Component {
constructor(props) {
super(props);
this.maxN = 110;
this.tempArray = [];
this.question;
}
getRandomN() {
var randomN = Math.floor((Math.random() * this.maxN) + 1);
if(this.tempArray.indexOf(randomN) === -1) {
this.tempArray.push(randomN);
}
else {
randomN = this.getRandomN();
}
return randomN;
}
getRandomQuestion() {
this.question = this.props.current.data.questions[this.getRandomN()];
return this.question;
}
render() {
this.getRandomQuestion();
return (
<section className="game">
<div className="grid">
<div className="row">
<div ref="n1"></div>
<div ref="n2"></div>
<div ref="n3"></div>
<div ref="n4"></div>
<div ref="n5"></div>
<div ref="n6"></div>
</div>
<div className="row">
<div ref="n7"></div>
<div ref="n8"></div>
<div ref="n9"></div>
<div ref="n10"></div>
<div ref="n11"></div>
<div ref="n12"></div>
</div>
<div className="row">
<div ref="n13"></div>
<div ref="n14"></div>
<div ref="n15"></div>
<div ref="n16"></div>
<div ref="n17"></div>
<div ref="n18"></div>
</div>
<div className="row">
<div ref="n19"></div>
<div ref="n20"></div>
<div ref="n21"></div>
<div ref="n22"></div>
<div ref="n23"></div>
<div ref="n24"></div>
</div>
</div>
<Action question={this.question} getRandomQuestion={this.getRandomQuestion.bind(this)}/>
</section>
);
}
}
export default Grid;
inside the "Action" component, based on the correct or wrong answer coming from "getNewQuestion" I need to access a random grid element from the grid component. (any random going from "n1" to "n24" as assigned to each ref attribute)
import React, { Component } from 'react';
class Action extends Component {
constructor(props) {
super(props);
this.state = {
question: props.question
}
}
getNewQuestion(e) {
console.log(this.state.question.correct_option);
let answerId = "option_" + this.state.question.correct_option;
if(e.target.getAttribute('data-question') == answerId) {
this.setState({
question: this.props.getRandomQuestion()
});
}
else {
console.log('wrong');
React.findDOMNode(this.refs.n1).classList.add('fdsdfsdfsdfsdfsfsdf');
}
}
render() {
let state = this.state;
return(
<div className="action">
<div className="action-question">
<h3>{state.question.question}</h3>
</div>
<div className="action-answers">
<p data-question="option_1" onClick={this.getNewQuestion.bind(this)}>{state.question.option_1}</p>
<p data-question="option_2" onClick={this.getNewQuestion.bind(this)}>{state.question.option_2}</p>
<p data-question="option_3" onClick={this.getNewQuestion.bind(this)}>{state.question.option_3}</p>
<p data-question="option_4" onClick={this.getNewQuestion.bind(this)}>{state.question.option_4}</p>
</div>
</div>
);
}
}
export default Action;
inside the "if" statment of the "getNewQuestion" I would like to do something like:
n2.classList.addClass('hidden');
I can't figure out how to access a parent's dom node from the "Action" component
Does the child really need to access the parent DOM directly? Shouldn't the parent Component know how to present itself? If so, then you can use callbacks that you pass down to the children, so that the children have the possibility to notify the parent when it should change.
const Child = ({modifyParent}) => (
<div onClick={ modifyParent } >Click me!</div>
);
const Parent = () => {
const modifyMyOwnStyle = event => {
// here you have easy access
// if you want to style the parent.
alert('Modifying style, based on child call');
}
return (
<Child modifyParent={ modifyMyOwnStyle }/>
);
};
ReactDOM.render(<Parent />, document.getElementById('root'));
Runnable JSFiddle demo here
You can get the ref of a component and pass this to its children like so:
render() {
return (
<div ref={node => this.node = node}>
<SomeChild parent={this.node} />
</div>
)
}
read more about it here: https://facebook.github.io/react/docs/refs-and-the-dom.html
However I have to say that doing this is usually a bad idea, and I would reconsider if you really need to pass the node, or if there is another way around the problem.
EDIT: As jonahe's comment shows you can usually get around the problem by passing a callback to the child component that you can fire when something needs to happen in the parent component
Better than accessing parent's DOM node directly, you can use a callback prop that does it for you.
Something like:
class Grid extends Component {
constructor(props) {
super(props)
this.accessRandomElement = this.accessRandomElement.bind(this)
}
accessRandomElement() {
// do you thing
}
render() {
this.getRandomQuestion()
return (
<section className="game">
...
<Action
question={this.question}
onAccessYourRandomElement={this.accessRandomElement}
///
/>
</section>
)
}
}
and then from inside Action you call this.props.onAccessYourRandomElement()

Update component when prop value changes

I have an object with the property home.ready = false. When the object is done getting data, cleaning it etc it changes to home.ready= true.
I need my component to register the change and update. My component:
class HomeNav extends React.Component {
render() {
let data = this.props.data;
let uniqueTabs = _.uniq(_.map(data, x => x.tab)).sort();
let tabs = uniqueTabs.map((tab, index) => {
let itemsByTab = _.filter(data, (x => x.tab == tab));
return <Tabs key={tab} tab={tab} index={index} data={itemsByTab} />;
});
console.log(this.props)
return (
<section>
<div className="wb-tabs">
<div className="tabpanels">
{ this.props.ready ? {tabs} : <p>Loading...</p> }
</div>
</div>
</section>
)
}
};
ReactDOM.render(
<HomeNav data={home.data.nav} ready={home.ready}/>,
document.getElementById('home-nav')
);
This is the home object. It's a simple object that gets data and once the data is ready the property ready changes from false to true. I can't get React to recognize that change. And at times React will say home is undefined.
Since you didn't post any code around the request, or data formatting, I will assume you got all that figured out. So, for your component to work the way it is currently written, you need to drop the curly braces around tabs ({ this.props.ready ? tabs : <p>Loading...</p> }), then, this.props.data should always contain a valid Array, otherwise it will break when you try to sort, filter, etc.
Or, you can do an early dropout, based on the ready property:
class HomeNav extends React.Component {
render() {
if(!this.props.ready){
return <section>
<div className="wb-tabs">
<div className="tabpanels">
<p>Loading...</p>
</div>
</div>
</section>
}
let data = this.props.data;
let uniqueTabs = _.uniq(_.map(data, x => x.tab)).sort();
let tabs = uniqueTabs.map((tab, index) => {
let itemsByTab = _.filter(data, (x => x.tab == tab));
return <Tabs key={tab} tab={tab} index={index} data={itemsByTab} />;
});
console.log(this.props)
return (
<section>
<div className="wb-tabs">
<div className="tabpanels">
{tabs}
</div>
</div>
</section>
)
}
};

Undefined error inside function of Order component

I have developed a page which contains a single recipe page and there is an order button. When order button is clicked the order of that recipe should be shown along with the price just side of recipe section. But I get sorry recipe no longer available. In the render section inside Order Component i get an id of order. But why i get an undefined error inside renderOrder function(this.props.order[key] and this.props.recipe[key]). Why is that so?
export default class SingleRecipe extends Component {
constructor(){
super();
this.state = { order: {} };
}
getMeteorData(){
let data = {};
let recipehandle = Meteor.subscribe('singlerecipe',this.props.slug);
if(recipehandle.ready()){
data.recipe = Recipes.findOne({_id:this.props.slug});
}
return data;
}
addToOrder(key){
this.state.order[key] = this.state.order[key]+1 || 1;;
this.setState({
order:this.state.order
});
}
render() {
const {recipe}=this.data;
const {order}=this.state;
return (
<div className="container">
<div className="row">
<div className="col s6">
{recipe.nameOfRecipe}
</div>
<div className="col s6">
<Order order={order} recipe={recipe} removeFromOrder = {this.removeFromOrder} />
</div>
</div>
</div>
);
}
}
export default class Order extends Component {
renderOrder(key){
let order = this.props.order[key];
let recipe = this.props.recipe[key]; // get undefined
let removeButton = <button onClick={this.props.removeFromOrder.bind(this,key)}>×</button>;
if(!recipe){
return <li key={key}>Sorry, recipe no longer available! {removeButton}</li>
}
return(
<li key={key}>
<span>
<CSSTransitionGroup component="span" transitionName="count" transitionLeaveTimeout={250} transitionEnterTimeout={250} className="count">
<span key={count}>{count}</span>
</CSSTransitionGroup>
{recipe.nameOfRecipe}{removeButton}
</span>
</li>
)
}
render() {
let orderId = Object.keys(this.props.order); // returns the id
return (
<div className="order-wrap">
<h2 className="order-title">Your Order</h2>
<CSSTransitionGroup
className="order"
component="ul"
transitionName="order"
transitionEnterTimeout={100}
transitionLeaveTimeout={500}
>
{orderId.map(item=>this.renderOrder(item))}
</CSSTransitionGroup>
</div>
);
}
}
{orderId.map(this.renderOrder)}
change to
{orderId.map(item=>this.renderOrder(item))}
changing this.props.recipe[key] to this.props.recipewill work.

Why does my small React project respond terribly when the window is resized?

I have made a small project to learn more about react and have noticed that the view responds very poorly when the window size is changed.
I feel i must be doing something somewhere i shouldn't and that is creating this cumbersome experience.
Here's my app:
import ColourCard from "./components/colour-card";
const url = "https://raw.githubusercontent.com/mdn/data/master/css/syntaxes.json";
class App extends React.Component {
constructor() {
super();
this.state = {
error: null,
colours: []
};
}
componentDidMount() {
fetch(url)
.then( response => response.json() )
.then( data => {
let colours = data['named-color']['syntax'].split(' | ');
colours = colours.filter((colour) => {
return !colour.includes('gray') && !colour.includes('transparent');
});
this.setState({ colours });
let clipboard = new Clipboard('.js-copy');
clipboard.on('success', function(e) {
const el = e.trigger.closest('.card').parentNode.getElementsByClassName('card-flash')[0];
el.getElementsByTagName('strong')[0].innerHTML = e.text;
el.classList.add('active');
setTimeout(() => el.classList.remove('active'), 1000);
});
})
.catch( e => this.setState({ error: 'Ooops, error' }) )
}
render() {
const { error, colours } = this.state;
if ( error ) {
return <div>{error}</div>
}
if ( !colours.length ) {
return <div>Loading...</div>
}
return (
<div className="grid">
{colours.map((colour, index) => {
return <ColourCard colour={colour} key={index}></ColourCard>
})}
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById("app"));
and heres my card component:
class ColourCard extends React.Component {
render() {
const colour = tinycolor(this.props.colour);
const style = {
backgroundColor: colour.toHexString()
};
return (
<div className="grid__item size-6#m size-4#l">
<div className="card">
<div className="card__colour" style={style}></div>
<div className="card__meta">
<div className="card__meta-item js-copy" data-clipboard-text={this.props.colour}>{this.props.colour}</div>
<div className="card__meta-item js-copy" data-clipboard-text={colour.toHexString()}>{colour.toHexString()}</div>
<div className="card__meta-item js-copy" data-clipboard-text={colour.toRgbString()}>{colour.toRgbString()}</div>
<div className="card__meta-item js-copy" data-clipboard-text={colour.toHslString()}>{colour.toHslString()}</div>
<div className="card__meta-item js-copy" data-clipboard-text={colour.toHsvString()}>{colour.toHsvString()}</div>
</div>
</div>
<div className="card-flash" style={style}>
<span className="card-flash__text">
<strong className="card-flash__strong"></strong>
<br />
Copied!
</span>
</div>
</div>
);
}
}
export default ColourCard;
https://codepen.io/matt3224/project/editor/ZvLGGA#
Any help much appreciated!
This whole block of code here:
let clipboard = new Clipboard('.js-copy');
clipboard.on('success', function(e) {
const el = e.trigger.closest('.card').parentNode.getElementsByClassName('card-flash')[0];
el.getElementsByTagName('strong')[0].innerHTML = e.text;
el.classList.add('active');
setTimeout(() => el.classList.remove('active'), 1000);
});
You are never supposed to manually manipulate the DOM with react. That is really the one golden rule when using this library. This is the same reason why libraries like d3 have trouble with react, because it wants to get its hand into the DOM. React manages a virtual DOM, and any interference with that is not good. It can lead to performance issues and generally speaking, will break your app more times than not.

Categories