Undefined props in componentDidMount - reactjs

This is starting to get really frustrating. Basically, I cannot access props in my subcomponents. if I try to render them directly using this.props- it works, but if I need to do additional processes with them, or save them into state, I get undefined props all the time. I have a parent component, which looks something like this:
import React from 'react';
import Title from './EventSubComponents/Title';
import SessionInfo from './EventSubComponents/SessionInfo';
import SessionTime from './EventSubComponents/SessionTime';
import Location from './EventSubComponents/Location';
import Subscribers from './EventSubComponents/Subscribers';
class EventNode extends React.Component {
constructor(props) {
super(props);
this.state = {
'event': [],
}
}
componentDidMount() {
this.getEvent(this.props.location.selectedEventId);
}
getEvent(eventId) {
fetch('/api/v.1.0/event/' + eventId, {mode: 'no-cors'})
.then(function(response) {
if(!response.ok) {
console.log('Failed to get single event.');
return;
}
return response.json();
})
.then((data) => {
if (!data) {
return;
}
this.setState({
'event': data
})
});
}
render() {
return(
<div className="event-wrapper">
<Title
title = { this.state.event.title }
date = { this.state.event.start }
/>
<SessionInfo
distance = { this.state.event.distance }
type = { this.state.event.type }
/>
<SessionTime
start = { this.state.event.start }
end = { this.state.event.end }
/>
<Location location = { this.state.event.start_location }/>
<Subscribers
subscribers = { this.state.event.subscribers }
eventId = { this.state.event._id }
/>
</div>
);
}
}
export default EventNode;
And my sub-component SessionTime, which looks like this:
import React from 'react';
import moment from 'moment';
class Title extends React.Component {
constructor(props) {
super(props);
this.state = {
'title': '',
'date': '',
}
}
componentDidMount() {
console.log(this.props.title);
console.log(this.props.date);
// undefined both props.
this.convertToTitleDate(this.props.date);
this.setState({
'title': this.props.title
})
}
convertToTitleDate(date) {
var newDate = moment(date).format('dddd, Do MMMM')
this.setState({
'date': newDate,
});
}
render() {
return (
<div className="event-title-wrapper">
<h1> { this.state.title } </h1>
<div className="event-title-date"> { this.state.date } </div>
</div>
);
}
}
export default Title;
Could anyone explain, why both this.props.date and this.props.title are undefined in my componentDidMount function? I have couple more components in my EventNode and I have the same problems in them as well.
Changing componentDidMount to componentWillMount does not help. I am fairly certain I have problems in my parent EventNode component, but I cannot figure out where. Inside EventNode render() all the state variables are defined.

You initialize event to an empty array and pass down this.state.event.start and this.state.event.end to SessionTime, which will both be undefined on first render since event has not been loaded yet and there are no start and end properties on the array.
You could instead e.g. set event to null initially, and return null from the render method until the event has been loaded.
Example
class EventNode extends React.Component {
state = {
event: null
};
// ...
render() {
const { event } = this.state;
if (event === null) {
return null;
}
return (
<div className="event-wrapper">
<Title title={event.title} date={event.start} />
<SessionInfo distance={event.distance} type={event.type} />
<SessionTime start={event.start} end={event.end} />
<Location location={event.start_location} />
<Subscribers
subscribers={event.subscribers}
eventId={this.state.event._id}
/>
</div>
);
}
}

Related

Child component not triggering rendering when parent injects props with differnt values

Here are my components:
App component:
import logo from './logo.svg';
import {Component} from 'react';
import './App.css';
import {MonsterCardList} from './components/monster-list/monster-card-list.component'
import {Search} from './components/search/search.component'
class App extends Component
{
constructor()
{
super();
this.state = {searchText:""}
}
render()
{
console.log("repainting App component");
return (
<div className="App">
<main>
<h1 className="app-title">Monster List</h1>
<Search callback={this._searchChanged}></Search>
<MonsterCardList filter={this.state.searchText}></MonsterCardList>
</main>
</div>
);
}
_searchChanged(newText)
{
console.log("Setting state. new text: "+newText);
this.setState({searchText:newText}, () => console.log(this.state));
}
}
export default App;
Card List component:
export class MonsterCardList extends Component
{
constructor(props)
{
super(props);
this.state = {data:[]};
}
componentDidMount()
{
console.log("Component mounted");
this._loadData();
}
_loadData(monsterCardCount)
{
fetch("https://jsonplaceholder.typicode.com/users", {
method: 'GET',
}).then( response =>{
if(response.ok)
{
console.log(response.status);
response.json().then(data => {
let convertedData = data.map( ( el, index) => {
return {url:`https://robohash.org/${index}.png?size=100x100`, name:el.name, email:el.email}
});
console.log(convertedData);
this.setState({data:convertedData});
});
}
else
console.log("Error: "+response.status+" -> "+response.statusText);
/*let data = response.json().value;
*/
}).catch(e => {
console.log("Error: "+e);
});
}
render()
{
console.log("filter:" + this.props.filter);
return (
<div className="monster-card-list">
{this.state.data.map((element,index) => {
if(!this.props.filter || element.email.includes(this.props.filter))
return <MonsterCard cardData={element} key={index}></MonsterCard>;
})}
</div>
);
}
}
Card component:
import {Component} from "react"
import './monster-card.component.css'
export class MonsterCard extends Component
{
constructor(props)
{
super(props);
}
render()
{
return (
<div className="monster-card">
<img className="monster-card-img" src={this.props.cardData.url}></img>
<h3 className="monster-card-name">{this.props.cardData.name}</h3>
<h3 className="monster-card-email">{this.props.cardData.email}</h3>
</div>
);
}
}
Search component:
import {Component} from "react"
export class Search extends Component
{
_searchChangedCallback = null;
constructor(props)
{
super();
this._searchChangedCallback = props.callback;
}
render()
{
return (
<input type="search" onChange={e=>this._searchChangedCallback(e.target.value)} placeholder="Search monsters"></input>
);
}
}
The problem is that I see how the text typed in the input flows to the App component correctly and the callback is called but, when the state is changed in the _searchChanged, the MonsterCardList seems not to re-render.
I saw you are using state filter in MonsterCardList component: filter:this.props.searchText.But you only pass a prop filter (filter={this.state.searchText}) in this component. So props searchTextis undefined.
I saw you don't need to use state filter. Replace this.state.filter by this.props.filter
_loadData will get called only once when the component is mounted for the first time in below code,
componentDidMount()
{
console.log("Component mounted");
this._loadData();
}
when you set state inside the constructor means it also sets this.state.filter for once. And state does not change when searchText props change and due to that no rerendering.
constructor(props)
{
super(props);
this.state = {data:[], filter:this.props.searchText};
}
If you need to rerender when props changes, use componentDidUpdate lifecycle hook
componentDidUpdate(prevProps)
{
if (this.props.searchText !== prevProps.searchText)
{
this._loadData();
}
}
Well, in the end I found what was happening. It wasn't a react related problem but a javascript one and it was related to this not been bound to App class inside the _searchChanged function.
I we bind it like this in the constructor:
this._searchChanged = this._searchChanged.bind(this);
or we just use and arrow function:
_searchChanged = (newText) =>
{
console.log("Setting state. new text: "+newText);
this.setState({filter:newText}, () => console.log(this.state));
}
Everything works as expected.

react twilio video: first joiner screen black on participant join

i have made a twillio video app.i can show local video on Laptop website but when i join from another chrome tab or mobile phone chrome browser the video on laptop goes black and only one video is showing whereas both videos should show properly.i am following this tutorial
https://www.twilio.com/blog/build-a-custom-video-chat-app-with-react-and-twilio-programmable-video
here is my code
App.js
import './App.scss';
import React, {Component} from 'react';
import Room from './Components/Room';
const { connect } = require('twilio-video');
const Token = {"identity":"Jose Corkery","token":"...sioAMt4..."}
class App extends Component {
constructor(props) {
super(props)
this.state = {
identity: '',
room: null
}
this.inputRef = React.createRef();
this.joinRoom = this.joinRoom.bind(this);
this.returnToLobby = this.returnToLobby.bind(this);
this.updateIdentity = this.updateIdentity.bind(this);
this.removePlaceholderText = this.removePlaceholderText.bind(this)
}
async joinRoom() {
try {
// const response = Token
// const data = await response.json();
const room = await connect(Token.token, {
name: 'cool-room',
audio: true,
video: true
});
// alert(room)
this.setState({ room: room });
} catch(err) {
alert(err);
}
}
updateIdentity(event) {
this.setState({
identity: event.target.value
});
}
returnToLobby() {
this.setState({ room: null });
}
removePlaceholderText() {
this.inputRef.current.placeholder = '';
}
render() {
const disabled = this.state.identity === '' ? true : false;
return (
<div className="app">
{
this.state.room === null
? <div className="lobby">
<input
ref={this.inputRef}
onClick={this.removePlaceholderText}
placeholder="What's your name?"
onChange={this.updateIdentity}
/>
<button disabled = {disabled} onClick={this.joinRoom}>Join Room</button>
</div>
: <Room returnToLobby={this.returnToLobby} room={this.state.room} />
}
</div>
);
}
}
export default App;
Room.jsx
import React, { Component } from 'react';
import Participant from './Participant';
const { connect } = require('twilio-video');
class Room extends Component {
componentDidMount() {
this.props.room.on('participantConnected', participant => this.addParticipant(participant));
this.props.room.on('participantDisconnected', participant => this.removeParticipant(participant));
window.addEventListener("beforeunload", this.leaveRoom);
}
componentWillUnmount() {
this.leaveRoom();
}
addParticipant(participant) {
console.log(`${participant.identity} has joined the room.`);
alert(`+ Participant : ${participant.identity}`)
this.setState({
remoteParticipants: [...this.state.remoteParticipants, participant]
})
}
removeParticipant(participant) {
alert(`Leaving : ${participant.identity}`)
console.log(`${participant.identity} has left the room`);
this.setState({
remoteParticipants: this.state.remoteParticipants.filter(p => p.identity !== participant.identity)
});
}
leaveRoom() {
this.props.room.disconnect();
this.props.returnToLobby();
}
constructor(props) {
super(props)
this.state = {
remoteParticipants: Array.from(this.props.room.participants.values())
}
this.leaveRoom = this.leaveRoom.bind(this);
}
render() {
return (
<div className="room">
<div className="participants">
<Participant
key={this.props.room.localParticipant.identity}
localParticipant="true"
participant={this.props.room.localParticipant} />
{
this.state.remoteParticipants.map(participant =>
<Participant key={participant.identity} participant={participant} />
)
}
</div>
<button id="leaveRoom" onClick={this.leaveRoom}>Leave Room</button>
</div>
);
}
}
export default Room
Participant.jsx
import React, { Component } from 'react';
import Track from './Track';
const { connect } = require('twilio-video');
class Participant extends Component {
componentDidMount() {
if (!this.props.localParticipant) {
this.props.participant.on('trackSubscribed', track => this.addTrack(track));
}
}
constructor(props) {
super(props);
const existingPublications = Array.from(this.props.participant.tracks.values());
const existingTracks = existingPublications.map(publication => publication.track);
const nonNullTracks = existingTracks.filter(track => track !== null)
this.state = {
tracks: nonNullTracks
}
}
addTrack(track) {
this.setState({
tracks: [...this.state.tracks, track]
});
}
render() {
return (
<div className="participant" id={this.props.participant.identity}>
<div className="identity">{this.props.participant.identity}</div>
{
this.state.tracks.map(track =>
<Track key={track} filter={this.state.filter} track={track}/>)
}
</div>
);
}
}
export default Participant
Track.jsx
import React, { Component } from 'react';
class Track extends Component {
componentDidMount() {
if (this.props.track !== null) {
const child = this.props.track.attach();
this.ref.current.classList.add(this.props.track.kind);
this.ref.current.appendChild(child)
}
}
constructor(props) {
super(props)
this.ref = React.createRef();
}
render() {
return (
<div className="track" ref={this.ref}>
</div>
)
}
}
export default Track
demo:https://android-anime.web.app
i have only two video events onJoin and onLeave do i need additional events ?
what is the solution? if your solution works i will award you best answer.Thanks !!

How to show validation message on <TagsInput> react premade component on unique value

I have an input tag component from react-tagsinput as follows:
const onTagChange = (tags) => {
const noDuplicateTags = tags.filter((v, i) => tags.indexOf(v) === i);
const duplicateEntered = tags.length !== noDuplicateTags.length;
if (duplicateEntered) {
onTagChange(tags);
console.log('duplicate');
}
onTagChange(noDuplicateTags);
};
function TagContainer({
tags,
}) {
return (
<div>
<Header>Meta:</Header>
<TagsInput value={tags} onChange={onTagChange} />
</div>
);
}
TagContainer.propTypes = {
tags: PropTypes.arrayOf(PropTypes.string),
};
TagContainer.defaultProps = {
tags: [],
};
export default TagContainer;
and the implementation on the onTagChange method which is passed as a prop to the <TagContainer> component in another component.
export class Modal extends React.Component {
...
...
onTagChange = (tags) => {
this.props.onTagChange(tags);
}
...
...
render() {
return(
<TagContainer
tags={tags}
onTagChange={this.onTagChange}
/>
);
}
}
Problem: onlyUnique prop in the <TagsInput> component is set to true to avoid duplicate entries. But I need to display an error message saying "duplicate values" as soon as user enters a duplicate value. How can this be done especially on the third party component.
I think you're going to have to handle dealing with duplicates in your component because you are getting no feedback from <TagInput /> component.
At a higher level, I would do something like this
class Example extends React.Component {
constructor() {
super();
this.state = {
showDuplicateError: false
};
}
handleTagChange(tags) {
const uniqueTags = removeDuplicates(tags);
const duplicateEntered = tags.length !== uniqueTags.length;
if (duplicateEntered) {
this.showDuplicateError();
}
// add unique tags regardless, as multiple tags could've been entered
const { onTagChange } = this.props;
onTagChange(uniqueTags);
}
showDuplicateError() {
this.setState({
showDuplicateError: true
});
}
render() {
const { showDuplicateError } = this.state;
const { tags } = this.props;
return (
<React.Fragment>
{ showDuplicateError && <div>Duplicate entered</div>}
<TagsInput value={ tags } onTagChange={ this.handleTagChange } />
</React.Fragment>
);
}
}

React not reloading function in JSX

I am using react-redux.
I have the following JSX (only relevant snippets included):
getQuestionElement(question) {
if (question) {
return <MultiChoice questionContent={this.props.question.question} buttonClicked={this.choiceClicked} />
}
else {
return (
<div className="center-loader">
<Preloader size='big' />
</div>
)
}
}
render() {
return (
<div>
<Header />
{
this.getQuestionElement(this.props.question)
}
</div>
)
}
function mapStateToProps({ question }) {
return { question };
}
export default connect(mapStateToProps, questionAction)(App);
When the action fires, and the reducer updates the question prop
this.props.question
I expect
{this.getQuestionElement(this.props.question)}
to be reloaded and the new question rendered.
However this is not happening. Am I not able to put a function in this way to get it live reloaded?
My MultiChoice component:
import React, { Component } from 'react';
import ReactHtmlParser from 'react-html-parser';
import './questions.css';
class MultiChoice extends Component {
constructor(props) {
super(props);
this.state = {
question: this.props.questionContent.question,
answerArray : this.props.questionContent.answers,
information: null
}
this.buttonClick = this.buttonClick.bind(this);
}
createButtons(answerArray) {
var buttons = answerArray.map((element) =>
<span key={element._id} onClick={() => { this.buttonClick(element._id) }}
className={"span-button-wrapper-25 " + (element.active ? "active" : "")}>
<label>
<span>{element.answer}</span>
</label>
</span>
);
return buttons;
}
buttonClick(id) {
var informationElement;
this.props.buttonClicked(id);
var buttonArray = this.state.answerArray.map((element) => {
if (element._id === id ){
element.active = true;
informationElement = element.information;
return element;
}
else{
element.active = false;
return element;
}
});
this.setState({
answerArray: buttonArray,
information: informationElement
})
}
render() {
return (
<div className="question-container">
<div className="question-view">
<div className="icon-row">
<i className="fa fa-code" />
</div>
<div className="title-row">
{this.state.question}
</div>
<div className="button-row">
{this.createButtons(this.state.answerArray)}
</div>
<div className="information-row">
{ReactHtmlParser(this.state.information)}
</div>
</div>
</div>
);
}
}
export default MultiChoice;
QuestionAction.js
import axios from "axios";
import { FETCH_QUESTION } from "./types";
export const fetchQuestion = (questionId, answerId) => async dispatch => {
let question = null;
if (questionId){
question = await axios.get("/api/question/next?questionId=" + questionId + "&answerId=" + answerId);
}
else{
question = await axios.get("/api/question/next");
}
console.log("question", question);
dispatch({ type: FETCH_QUESTION, payload: question });
};
questionReducer.js
import {FETCH_QUESTION } from "../actions/types";
export default function(state = null, action) {
switch (action.type) {
case FETCH_QUESTION:
console.log("payload", action.payload.data);
return { question: action.payload.data, selected: false };
default:
return state;
}
}
index.js (Combined Reducer)
import { combineReducers } from 'redux';
import questionReducer from './questionReducer';
export default combineReducers({
question: questionReducer
});
and my entry point:
index.js
const store = createStore(reducers, {}, applyMiddleware(reduxThunk));
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
registerServiceWorker();
requested console.log response:
render() {
console.log("Stackoverflow:", this.props.question)
.....
and after clicking the button (and the reducer updating, the console.log is updated, but the
this.getQuestionElement(this.props.question)
does not get re-rendered
MultiChoice Component shouldn't store his props in his state in the constructor, you have 2 options here :
Handle props changes in componentWillReceiveProps to update the state :
class MultiChoice extends Component {
constructor(props) {
super(props);
this.state = {
question: this.props.questionContent.question,
answerArray : this.props.questionContent.answers,
information: null
}
this.buttonClick = this.buttonClick.bind(this);
}
componentWillReceiveProps(nextProps) {
this.setState({
question: nextProps.questionContent.question,
answerArray : nextProps.questionContent.answers,
information: null
});
}
We have to keep using the constructor to set an initial state as from docs :
React doesn’t call componentWillReceiveProps() with initial props
during mounting.
2nd Option : Make it as a "dumb component" by having no state and only using his props to render something (some more deep changes in your component to do, especially to handle the "active" element, it will have to be handled by the parent component).

How to pass function as props in React?

I have functional component GetWeather which I want to pass result of GetLocation function as props based on which GetWetaher will do something i.e. another get request (in the example below it only renders its props). I think it has to happen inside ComponentDidMount, not sure how to do it
function GetLocation() {
axios.get('http://ipinfo.io')
.then((res) => {
return res.data.loc;
})
}
function GetWeather(props) {
//more code here, including another get request, based on props
return <h1>Location: {props.location}</h1>;
}
class LocalWeather extends Component {
componentDidMount() {
//???
}
render() {
return (
<div >
<GetWeather location={GetLocation}/> //???
</div>
);
}
}
Update: So based on suggestion from Damian below is working for me
function GetWeather(props) {
return <h3>Location: {props.location}</h3>;
}
class LocalWeather extends Component {
constructor(props) {
super(props);
this.state = {
location: []
};
}
getLocation() {
axios.get('http://ipinfo.io')
.then((res) => {
this.setState({location:res.data.loc});
})
}
componentDidMount() {
this.getLocation();
}
render() {
return (
<div >
<GetWeather location={this.state.location}/>
</div>
);
}
}
You can do it alternatively also
constructor() {
super();
this.state = {
Location:[]
}
}
function GetLocation() {
axios.get('http://ipinfo.io').then((res) => {
this.setState ({
Location:res.data.loc;
});
});
}
function GetWeather(props) {
return <h1>Location: {this.props.location}</h1>;
}
class LocalWeather extends Component {
componentDidMount() {
//code
}
render() {
return (
<div >
<GetWeather location={this.GetLocation.bind(this)}/>
</div>
);
}
}

Resources