'This' is undefined when click event method is launched - reactjs

I am very confused, because when I fill this.state.seats by values, it correctly render all components in DOM. But when I click on that component (button), it returns back to App.js and shows me:
Uncaught TypeError: Cannot read property 'state' of undefined
even though, the components from this state property are displayed in DOM!
Please, does anyone know what happens?
App.js
import React, { Component } from "react";
import "./App.css";
import Seats from "./Seats";
class App extends Component {
state = {
seats: this.initializeSeats()
};
initializeSeats() {
let seats = [];
let count = 5;
for (let a = 0; a < count; a++) {
for (let b = 0; b < count; b++) {
seats.push({ key: '' + a + b, reserved: false });
}
}
seats.find(s => s.key === '00').reserved = true;
return seats;
}
onClickSeat(e) {
const seats = [...this.state.seats];
let seat = seats.find(s => s.key === e.target.value);
seat.reserved = !seat.reserved;
console.log(seat.reserved);
this.setState({ seats: seats });
}
render() {
return (
<div>
<h3>Kinosál</h3>
<Seats
seats={this.state.seats}
onClickSeat={this.onClickSeat}
/>
</div>
);
}
}
export default App;
Seats.jsx
import React, { Component } from "react";
import Seat from "./Seat";
class Seats extends Component {
render() {
const result = [];
for (let seat of this.props.seats) {
if (!seat.reserved) {
result.push({ key: seat.key, reserved: seat.reserved });
}
}
return (
<div>
{result.map(seat => (
<Seat
key={seat.key}
onClick={this.props.onClickSeat}
seat={seat}
/>
))}
</div>
);
}
}
export default Seats;
Seat.jsx
import React, { Component } from "react";
import uuid from 'uuid';
class Seat extends Component {
render() {
const { seat, onClick } = this.props;
return (
<div>
<button onClick={onClick} key={uuid.v4()} value={seat.key}>{seat.key}</button>
</div>
);
}
}
export default Seat;

take a look at https://reactjs.org/docs/handling-events.html
You have to be careful about the meaning of this in JSX callbacks. In JavaScript, class methods are not bound by default. If you forget to bind this.handleClick and pass it to onClick, this will be undefined when the function is actually called.
You have to bind onClickSeat to the App class instance of this you can use the class arrow syntax below to do so.
onClickSeat = (e) => {
const seats = [...this.state.seats];
let seat = seats.find(s => s.key === e.target.value);
seat.reserved = !seat.reserved;
console.log(seat.reserved);
this.setState({ seats: seats });
}
Once you do that, everything should work! It also explains why you can see the components in the DOM, but onClickSeat has its state undefined (it's because this in onClickSeat is NOT referring to the class instance as you were expecting)

Related

Play songs one after another with Howler and NextJS

I have a list of songs on github, the goal is to play all songs one by one and post messages in console. But I faced a problem, how to find if the song is finished? Otherwise my code tries to play all songs without waiting the song's end.
import { Howl } from 'howler'
import { useState } from 'react'
export default function PlaySound() {
let [initSong, updatedSong] = useState(0)
const songs = [
'https://raw.githubusercontent.com/Sound/master/play1.mp3',
'https://raw.githubusercontent.com/Sound/master/play2.mp3',
'https://raw.githubusercontent.com/Sound/master/play3.mp3',
'https://raw.githubusercontent.com/Sound/master/play4.mp3',
'https://raw.githubusercontent.com/Sound/master/play5.mp3',
]
var sound = new Howl({
src: songs[initSong],
})
function postAMessage() {
for (let i = 0; i < songs.length; i++) {
if (initSong >= songs.length) return
console.log('New song ' + i)
sound.play()
updatedSong(initSong++)
i++
}
}
return (
<div>
<button onClick={postAMessage}>Play</button>
</div>
)
}
can you try react-Howler
import React from "react";
import ReactHowler from "react-howler";
import Button from "../components/Button";
class OnlyPlayPauseButton extends React.Component {
constructor(props) {
super(props);
this.state = {
playing: false
};
this.handlePlay = this.handlePlay.bind(this);
this.handlePause = this.handlePause.bind(this);
}
handlePlay() {
this.setState({
playing: true
});
}
handlePause() {
this.setState({
playing: false
});
}
render() {
return (
<div>
<ReactHowler
src={["sound.ogg", "sound.mp3"]}
playing={this.state.playing}
/>
<Button onClick={this.handlePlay}>Play</Button>
<Button onClick={this.handlePause}>Pause</Button>
</div>
);
}
}
export default OnlyPlayPauseButton;
refer: https://www.npmjs.com/package/react-howler

Dynamic Buttons creation React

I am trying to fetch the firebase data into a array and for each element create a button with that element as an id and name.
import React, { Component } from 'react'
import app from './firebase'
import firebase from "firebase/app";
import "firebase/database"
import { BsFillSquareFill } from "react-icons/bs";
import { Container,Row, Col } from "react-bootstrap";
import { withRouter } from 'react-router-dom';
var chambers = []
export default class ChamberClass extends Component {
constructor(props) {
super(props);
}
getButtonsUsingMap = () => {
return chambers.map((number) => {
return <button id={number} onClick={this.routeChange} className="btn"><BsFillSquareFill key = {number} color='green' className="icon "/>
<center>{number}</center>
</button>
})
}
componentDidMount(){
var chamberListen = firebase.database().ref()
chamberListen.on('value', snapshot => {
snapshot.forEach((cham) => {
var chamKey = cham.key;
var chamData = cham.val();
chambers.push(chamKey)
// document.getElementById("Chambers").innerHTML = chambers
console.log(chambers)
})
})
}
render() {
return (
<div>
<h4 className='RoomsTitle'>Rooms</h4>
<hr></hr>
{this.getButtonsUsingMap()}
</div>
)
}
}
I do get the console log which probably means that the firebase data is being accessed properly. However no buttons are being created.
Also when i move the code block inside componentDidMount() to the top of the ChamberClass, the buttons do get displayed but only once. After every successive attempt after reload or manually going to the route doesnt help either.
You need to put the array chambers into the component state. Without that you change the value but your component doesn't know that something changed and wont render as you expect it. By putting it into the component state it will know when it changes:
import React, { Component } from "react";
import app from "./firebase";
import firebase from "firebase/app";
import "firebase/database";
import { BsFillSquareFill } from "react-icons/bs";
import { Container, Row, Col } from "react-bootstrap";
import { withRouter } from "react-router-dom";
export default class ChamberClass extends Component {
state = {
chambers: [],
};
constructor(props) {
super(props);
}
getButtonsUsingMap = () => {
return this.state.chambers.map((number) => {
return (
<button id={number} onClick={this.routeChange} className="btn">
<BsFillSquareFill key={number} color="green" className="icon " />
<center>{number}</center>
</button>
);
});
};
componentDidMount() {
var chamberListen = firebase.database().ref();
chamberListen.on("value", (snapshot) => {
var chambers = [];
snapshot.forEach((cham) => {
var chamKey = cham.key;
var chamData = cham.val();
chambers.push(chamKey);
// document.getElementById("Chambers").innerHTML = chambers
console.log(chambers);
});
this.setState({ chambers });
});
}
render() {
return (
<div>
<h4 className="RoomsTitle">Rooms</h4>
<hr></hr>
{this.getButtonsUsingMap()}
</div>
);
}
}

ReactJS -- Unable to find latest title from an api causing error

I have a Landing component and a NewsLatest component. I am hitting on an api and trying to find the article with the latest timestamp but iam unable to get it done in reactJS.I checked the js code its working fine but in react it is not rendering. Kindly suggest something.
import React, { Component } from 'react'
import NewsSearch from '../NewsSearch/NewsSearch';
import NewsLatest from '../NewsLatest/NewsLatest';
import './Landing.css';
import axios from 'axios';
class Landing extends Component {
state={
newsList: []
}
componentDidMount(){
axios.get(`https://api.nytimes.com/svc/topstories/v2/home.json?api-key=7cK9FpOnC3zgoboP2CPGR3FcznEaYCJv`)
.then(res=> {
this.setState({newsList: res.data.results});
});
}
render() {
// console.log(this.state.newsList);
return (
<div className="landing text-center text-white">
<h1>News Portal</h1>
<div className="news-search">
<NewsSearch />
</div>
<div className="news-latest">
<NewsLatest newsList={this.state.newsList}/>
</div>
</div>
)
}
}
export default Landing;
import React, { Component } from 'react';
// import PropTypes from 'prop-types';
class NewsLatest extends Component {
constructor(props){
super(props);
this.state = {
newsTitle:'',
abstract:'',
newsUrl:'',
}
// this.newsLatest = this.newsLatest.bind(this);
}
newsLatest = (e)=>{
// e.preventDefault();
const {newsList} = this.props;
let maxTime = newsList.map(function(o) {
return new Date(o.updated_date);
});
let maximumValue = Math.max(...maxTime);
let latestnews = newsList.filter(function (el) {
return maximumValue === new Date(el.updated_date).getTime();
})[0];
if(latestnews){
this.setState({newsTitle: latestnews.title});
return (<h4>{this.state.newsTitle}</h4>);
}
}
newsTitle = () => (
this.props.newsList.map(item => (<h2 key={item.title}>{item.title}</h2>))
)
render() {
console.log(this.props.newsList);
return (
<div>
<h2>News Latest....</h2>
{this.newsLatest()}
</div>
);
}
}
export default NewsLatest;
There is some issue in rendering in NewsLatest component. KIndly suggest something.
Try this:
You must probably be getting a maximum depth error, use a lifecycle method instead like componentDidUpdate. Update your component state only if the previous props are different from the newer ones.
Read more here: https://reactjs.org/docs/react-component.html
import React, { Component } from "react";
// import PropTypes from 'prop-types';
class NewsLatest extends Component {
constructor(props) {
super(props);
this.state = {
newsTitle: "",
abstract: "",
newsUrl: ""
};
// this.newsLatest = this.newsLatest.bind(this);
}
componentDidUpdate(prevProps, prevState) {
if (prevProps.newsList !== this.props.newsList) {
const { newsList } = this.props;
let maxTime = newsList.map(function(o) {
return new Date(o.updated_date);
});
let maximumValue = Math.max(...maxTime);
let latestnews = newsList.filter(function(el) {
return maximumValue === new Date(el.updated_date).getTime();
})[0];
this.setState({ newsTitle: latestnews.title });
}
}
// newsLatest = e => {
// // e.preventDefault();
// const { newsList } = this.props;
// let maxTime = newsList.map(function(o) {
// return new Date(o.updated_date);
// });
// let maximumValue = Math.max(...maxTime);
// let latestnews = newsList.filter(function(el) {
// return maximumValue === new Date(el.updated_date).getTime();
// })[0];
// console.log(latestnews)
// if (latestnews && latestnews.hasOwnProperty('length') && latestnews.length>0) {
// return <h4>{this.state.newsTitle}</h4>;
// }
// };
newsTitle = () =>
this.props.newsList.map(item => <h2 key={item.title}>{item.title}</h2>);
render() {
console.log(this.props.newsList);
return (
<div>
<h2>News Latest....</h2>
<h4>{this.state.newsTitle}</h4>
</div>
);
}
}
export default NewsLatest;
Also, a sandbox: https://codesandbox.io/s/hungry-frog-z37y0?fontsize=14

ReactJS Toggling Props Length

I have a show/hide functionality that I am building out for a component and I am wondering how I can toggle the length of a string from 5 characters to its full length and back based on a button click and the previous state. I have the button click and a boolean indicating a true/false state, but I am not sure how I can switch between the 5 character limit and full length. I can only get the expanded text and not the original state.
Based on a state boolean (showFullText) I thought of this solution:
if (this.state.showFullText == false){
partialText = this.props.discovery.slice(0, this.state.characterLimit);
} else {
partialText = this.props.discovery;
}
but it is not working within the context of this code. No error message.
import React from 'react';
//Annotation Card - Body
export default class Body extends React.Component {
constructor(props){
super(props);
this.state = { showFullText: false, characterLimit: 10 };
this.showHideFullText = this.showHideFullText.bind(this);
}
showHideFullText(){
console.log(this.state.showFullText);
this.setState({
showFullText: !this.state.showFullText,
characterLimit: this.props.discovery.length,
expandButtonText: "Show Less"
});
}
render() {
var partialText;
if (this.state.showFullText == false){
partialText = this.props.discovery.slice(0, this.state.characterLimit);
} else {
partialText = this.props.discovery;
}
var textExpandButton;
if(this.props.discovery.length >= this.state.characterLimit) {
textExpandButton = <TextExpandButton showHide={this.showHideFullText} showFullText={this.state.showFullText} />;
} else {
return this.props.discovery;
}
return (
<div>
<p>{partialText}</p>
{textExpandButton}
</div>
)
}
}
//Annotation Card - Body - Text Expand Link
export const TextExpandButton = props => {
var buttonText;
if(props.showFullText === true){
buttonText = "Show Less";
} else {
buttonText = "Show More...";
}
return <a href="#" onClick={props.showHide}>{buttonText}</a>
}
The approach to use a toggled boolean in state is a great one because of its simplicity.
In Body.showHideFullText, characterLimit is being set to the length of discovery and this.props.discovery.slice(0, this.state.characterLimit) really is this.props.discovery.slice(0, discovery.length).
characterLimit should possibly be a write once value (set to 5 as the initial state of Body component). I'll suggest making this a property of Body and setting its default value to 5. There is no reason to keep it in state with your present requirements for the component.
In Body.showHideFullText, only toggle the boolean value for showFullText
showHideFullText(){
this.setState(
prevState => ({
...prevState,
showFullText: !prevState.showFullText,
})
);
}
I don't find it necessary to store expandButtonText in the component state because its value can be decided from the value for showFullText.
Update: Code for affected components (Run on StackBlitz)
Body.js
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import TextExpandButton from './TextExpandButton';
class Body extends Component {
constructor(props) {
super(props);
this.state = { showFullText: false };
this.toggleFullTextDisplay = this.toggleFullTextDisplay.bind(this);
}
toggleFullTextDisplay() {
this.setState(prevState => ({
...prevState,
showFullText: !prevState.showFullText,
}));
}
render() {
const { discovery, characterLimit } = this.props;
const { showFullText } = this.state;
return (
<div>
<p>{showFullText ? discovery : discovery.slice(0, characterLimit)}</p>
{discovery.length >= characterLimit && <TextExpandButton showFullText={showFullText} toggleFullTextDisplay={this.toggleFullTextDisplay} />}
</div>
)
}
}
Body.defaultProps = {
characterLimit: 5
};
Body.propTypes = {
characterLimit: PropTypes.number,
discovery: PropTypes.string.isRequired,
};
export default Body;
TextExpandButton.js
import React, { Component } from 'react';
import PropTypes from 'prop-types';
const TextExpandButton = props => {
const { showFullText, toggleFullTextDisplay } = props;
const buttonText = showFullText ? "Show Less" : "Show More...";
return (
<a href="#"
onClick={props.toggleFullTextDisplay}
>
{buttonText}
</a>
);
}
TextExpandButton.propTypes = {
showFullText: PropTypes.bool.isRequired,
toggleFullTextDisplay: PropTypes.func.isRequired,
};
export default TextExpandButton;

Reactjs. Counter of renders

How to make counter of renders the child component in parent?
I have 2 components Widget (parent) and Message(child). I passed counter from child to parent and trying to set getting value from child set to state. And I getting err: Maximum update depth exceeded.
There is child component Message:
import React, { Component } from "react";
export default class Message extends React.Component {
constructor(props) {
super(props);
this.changeColor = this.changeColor.bind(this);
this.changeCount = this.changeCount.bind(this);
this.state = { h: 0, counter: 0 };
}
changeColor = () => {
this.setState(state => ({
h: Math.random()
}));
};
changeCount = () => {
this.setState(state => ({
counter: ++state.counter
}));
};
componentDidUpdate(prevProps) {
this.props.getColor(this.color);
this.changeCount();
this.props.getCount(this.state.counter);
}
render() {
const { children } = this.props;
const { s, l, a } = this.props.color;
this.color = `hsla(${this.state.h}, ${s}%, ${l}%, ${a})`;
return (
<p
className="Message"
onClick={this.changeColor}
style={{ color: this.color }}
>
{children}
</p>
);
}
}
There is parent component:
import React, { Component } from "react";
import Message from "./Message/Message";
export default class Widget extends React.Component {
constructor(props) {
super(props);
this.state = {
color: {
s: 30,
l: 60,
a: 1
},
counter: 0
};
}
getCount = count => this.setState(state => ({
counter: state.counter
}));
getColor = color => {
console.log(`the color is ${color}`);
};
render() {
const counter = this.state.counter;
return (
<div>
<Message
getColor={this.getColor}
getCount={this.getCount}
color={this.state.color}
>
{undefined || `Hello World!`}
</Message>
{counter}
</div>
);
}
}
What I do wrong?
The answer by #Yossi counts total renders of all component instances. This solution counts how many renderes and re-renders an individual component has done.
For counting component instance renders
import { useRef } from "react";
export const Counter = props => {
const renderCounter = useRef(0);
renderCounter.current = renderCounter.current + 1;
return <h1>Renders: {renderCounter.current}, {props.message}</h1>;
};
export default class Message extends React.Component {
constructor() {
this.counter = 0;
}
render() {
this.counter++;
........
}
}
In order to count the number of renders, I am adding a static variable to all my components, and incrementing it within render().
For Class components:
import React, { Component } from 'react';
import { View, Text } from 'react-native';
let renderCount = 0;
export class SampleClass extends Component {
render() {
if (__DEV__) {
renderCount += 1;
console.log(`${this.constructor.name}. renderCount: `, renderCount);
}
return (
<View>
<Text>bla</Text>
</View>
)
}
}
For functional Components:
import React from 'react';
import { View, Text } from 'react-native';
let renderCount = 0;
export function SampleFunctional() {
if (__DEV__) {
renderCount += 1;
console.log(`${SampleFunctional.name}. renderCount: `, renderCount);
}
return (
<View>
<Text>bla</Text>
</View>
)
}
The componentDidUpdate is calling this.changeCount() which calls this.setState() everytime after the component updated, which ofcourse runs infinitely and throws the error.
componentDidUpdate(prevProps) {
this.props.getColor(this.color);
// Add a if-clause here if you really want to call `this.changeCount()` here
// For example: (I used Lodash here to compare, you might need to import it)
if (!_.isEqual(prevProps.color, this.props.color) {
this.changeCount();
}
this.props.getCount(this.state.counter);
}

Resources