Why Cannot read property 'deleteModule' of undefined? - reactjs

I am trying to pass the function DeleteModule two levels down.
So I am passing that another component inside map as a prop.
Although I did binding in the constructor, the compiler still throws an error as given below.
I tried adding a binding like :
this.deleteModule = this.deleteModule.bind(this) inside the function renderListOfModules. Then too, it didnt work.
Can anyone point out what went wrong in my code?
import React from 'react';
import '../../node_modules/bootstrap/dist/css/bootstrap.css';
import ModuleListItem from '../components/ModuleListItem';
export default class ModuleList extends React.Component{
constructor(props) {
super(props);
this.state = {
module: { title: '' },
modules: [
{id: 1, title: "JQuery"},
{id: 2, title: "React"},
{id: 3, title: "Redux"},
{id: 4, title: "Angular"},
{id: 5, title: "Node"}
]
}
this.titleChanged = this.titleChanged.bind(this);
this.createModule = this.createModule.bind(this);
this.deleteModule = this.deleteModule.bind(this);
}
titleChanged(event) {
console.log(event.target.value);
this.setState({module: {title: event.target.value, id : this.state.modules.length +1}});
}
createModule(){
this.setState(
{ modules : this.state.modules.concat(this.state.module)});
this.setState({module: {title: ""}});
}
deleteModule=(index)=>{
console.log("index to remove : " + index);
if (index > -1) {
this.setState(
{ modules : this.state.modules.splice(index, 1) });
}
// /api/module/61
// module_id actual
}
editModule(index,title, updated){
console.log("Edit module ");
console.log("index : " + index);
console.log("title : " + title);
console.log("updated : " + updated);
if (index > -1 && index < this.state.modules.length) {
this.setState(
{ modules : this.state.modules.splice(index, 1) });
}
// to api :
//courseid
//title
// updatedAt:
}
renderListOfModules(){
let index = -1;
let modules = this.state.modules.map(function(module) {
index++;
return <ModuleListItem
key={module.id}
id={index}
index={index}
title={module.title}
inEditMode={false}
deleteModule={this.deleteModule}/>
}
)
return modules
}
render(){
return(
<div>
<input className="form-control"
onChange={this.titleChanged}
placeholder="title"
value={this.state.module.title}/>
<button
className="btn btn-primary btn-block"
onClick={this.createModule}>
<i className="fa fa-plus"></i>
</button>
<ul className="list-group">
{this.renderListOfModules()}
</ul>
</div>
);
}
}
Note : I am using arrow function to give the delete function a component scope.
What am I doing wrong ?
Error :
×
←→1 of 2 errors on the page
TypeError: Cannot read property 'deleteModule' of undefined
(anonymous function)
src/container/ModuleList.js:91
88 | index={index}
89 | title={module.title}
90 | inEditMode={false}
> 91 | deleteModule={this.deleteModule}/>
92 | }
93 | )
94 |
Github code link : Code

Your function renderListOfModules() and your map callback are not binded so your are not passing down the component scope and wont be able to access the functions declared on it. You should bind them or declare them as arrow functions also:
renderListOfModules = () => {
let index = -1;
let modules = this.state.modules.map((module) => {
index++;
return <ModuleListItem
key={module.id}
id={index}
index={index}
title={module.title}
inEditMode={false}
deleteModule={this.deleteModule}/>
}
)
return modules
}

You can either add a this as a second argument like this:
let modules = this.state.modules.map(function(module) {
index++;
return <ModuleListItem
key={module.id}
id={index}
index={index}
title={module.title}
inEditMode={false}
deleteModule={this.deleteModule}/>
}, this
)
Or you can just use fat arrow function like this:
let modules = this.state.modules.map((module) => {
index++;
return <ModuleListItem
key={module.id}
id={index}
index={index}
title={module.title}
inEditMode={false}
deleteModule={this.deleteModule}/>
}

Related

Applying multiple dynamic filters

After now two full days of trial and error and googling, I am starting to lose my mind and really could use some help. This is my second React hobby-project, so please bear with me if it contains any "no-go's".
Situation:
I call an API, store the data in state (hotel rooms), display all rooms at first. After applying a search, I want to narrow it down further - and that works (with hardcoded values for testing).
I take all available rooms, filter them, store them into another array and let that array then get displayed.
The Problem:
My search is not dynamic. I can narrow the results down, but I can't "bring them back up". For example: if a user wants the rooms narrowed down by price and by "pets allowed", it gets displayed. But if he decides that bringing his pet is not that important and unchecks the filter, the results stay the same as before.
The handleClicks and Buttons are just there to quickly test things, they're not how the end result will be. Also, I've left out the fetchRoomsData(), as it's not important here.
If anyone could help me out here, it would be highly appreciated!
Thanks in advance :)
import React, { Component } from "react";
import Roomcard from "./RoomCard.js";
export default class AllRooms extends Component {
constructor() {
super();
this.state = {
data: undefined,
fetched: false,
roomsToDisplay: [],
hasFilter: {
price: 300,
capacity: 3,
pets: true,
breakfast: false,
},
};
}
componentDidMount() {
this.fetchRoomsData();
}
handleClick1() {
this.filterByPrice();
}
handleClick2() {
this.filterByPets();
}
handleClick3() {
this.filterByCapacity();
}
handleClick4() {
this.filterByBreakfast();
}
handleClick5() {
this.generateAllRooms();
}
filterByPrice() {
let tempArr = [];
this.state.roomsToDisplay.map((room) =>
room.props.price < this.state.hasFilter.price ? tempArr.push(room) : null
);
if (tempArr.length > 0) {
this.setState({ roomsToDisplay: tempArr });
} else {
this.setState({
roomsToDisplay: <h1>There are no matching results.</h1>,
});
}
}
filterByPets() {
let tempArr = [];
this.state.roomsToDisplay.map((room) =>
room.props.pets ? tempArr.push(room) : null
);
if (tempArr.length > 0) {
this.setState({ roomsToDisplay: tempArr });
} else {
this.setState({
roomsToDisplay: <h1>There are no matching results.</h1>,
});
}
}
filterByBreakfast() {
let tempArr = [];
this.state.roomsToDisplay.map((room) =>
room.props.breakfast ? tempArr.push(room) : null
);
if (tempArr.length > 0) {
this.setState({ roomsToDisplay: tempArr });
} else {
this.setState({
roomsToDisplay: <h1>There are no matching results.</h1>,
});
}
}
filterByCapacity() {
let tempArr = [];
this.state.roomsToDisplay.map((room) =>
room.props.capacity > this.state.hasFilter.capacity
? tempArr.push(room)
: null
);
if (tempArr.length > 0) {
this.setState({ roomsToDisplay: tempArr });
} else {
this.setState({
roomsToDisplay: <h1>There are no matching results.</h1>,
});
}
}
generateAllRooms() {
let finalDiv = [];
this.state.data.items.map((room) =>
finalDiv.push(
<Roomcard
price={room.fields.price}
titleImage={`https:${room.fields.images[0].fields.file.url}`}
allImages={room.fields.images.map((image) => image.fields.file.url)}
name={room.fields.name.toUpperCase()}
slug={room.fields.slug}
capacity={room.fields.capacity}
type={room.fields.type}
size={room.fields.size}
pets={room.fields.pets}
breakfast={room.fields.breakfast}
featured={room.fields.featured}
description={room.fields.description}
extras={room.fields.extras}
key={Math.random() * 1000}
/>
)
);
this.setState({ roomsToDisplay: finalDiv });
}
render() {
return (
<>
<div className="search-field-outer-box">
<button onClick={() => this.handleClick1()}> Filter By Price </button>
<button onClick={() => this.handleClick2()}> Filter By Pets </button>
<button onClick={() => this.handleClick3()}> Filter By capacity </button>
<button onClick={() => this.handleClick4()}> Filter By breakfast </button>
<button onClick={() => this.handleClick5()}> Reset Filter </button>
</div>
{this.state.data ? (
<div className="room-card-container">{this.state.roomsToDisplay}</div>
) : undefined}
</>
);
}
}
👋 Welcome to SO
First of all, don't store jsx elements in the state, prefer to store only values and create the jsx at render time.
Now, what I would do is to have the whole dataset in a state variable (and never modify it) and another for the filtered data
this.state = {
data:[],
filteredData:[]
};
// Here at some point when retrieving the data,
// this.setState({data: fetchedData, filteredData: fetchedData});
filterByBreakfast() {
const dataFiltered = // your code to filter
this.setState({
filterdData: dataFiltered,
});
}
resetFilters() {
// Reset the Filtered Data
this.setState({
filterdData: this.state.data,
});
}
render() {
return {
<div>
<div>
<button onClick={this.filterByBreakfast}> Filter By breakfast </button>
<button onClick={this.resetFilters}> Reset Filter </button>
</div>
<div>
{filteredData.length > 0 ? filteredData.map(item => <div>{item}</div>) : <div>No results</div>}
</div>
</div>
}
}

How to render one object from map instead all of them?

I have a profile object in the state of my react app, which contains an array of 6 objects.
I want to be able to render these objects separately.
{
this.state.profiles.map(profile => (
<div key={profile.mainCompanyID}>
{profile.name}
{profile.description}
</div>
))
}
The code above will display all 6 names/descriptions. But I want the power to be able to only map through one of the objects in the array, not all of them.
Any ideas?
filter the array before map
renderBasedOnId = id =>{
const { profiles } = this.state
return profiles.filter(x => x.id === id).map(item => <div>{item.name}</div>)
}
render(){
return this.renderBasedOnId(this.state.selectedId) //just an example of usage
}
You can filter out the data and then apply map.
Working Example - https://codesandbox.io/s/funny-shirley-q9s9j
Code -
function App() {
const profileData = [
{ id: 1, name: "Tom" },
{ id: 2, name: "Dick" },
{ id: 3, name: "Harry" },
{ id: 4, name: "Nuts" }
];
const selectedProfile = profileData.filter(x => x.name === "Harry");
return (
<div className="App">
<h1>Test Filter and map in jsx</h1>
{selectedProfile.map(x => (
<li>
{x.id} - {x.name}
</li>
))}
</div>
);
}
okay you can do it this way
{
this.state.profiles.map(profile => {
if (profile.mainCompanyID === id) { // id you want to match to
return (
<div key={profile.mainCompanyID}>
{profile.name}
{profile.description}
</div>)
} else {
return null
}
})
}
Hope it helps
{ this.state.profiles.map((profile, key) => {
(key===0)
?
return(
<div key={profile.key}>
<p>Name:{profile.name}</p>
<p>Description:{profile.description}</p>
</div>
)
:
return null;
})
}

Shuffle.js implementation with React.js

I'm trying to activate shuffle.js component functionality (search, filter and sort) with react.js. However, the documentation on the website is very limited. I know that I need to add a search input and some buttons to do what I want, yet I'm not sure how to connect my search box input and other button events to manipulate the photogrid (or other elements within a container) that is being rendered by react.
I have imported shuffle.js as node module and initialised it on the react page. The basic code that they provide seems to be working and displays the photo grid, however, that's pretty much it. I also want to implement the search, filtering and sorting functionality but there isn't documentation on how to do that in react.js. The code below shows the photogrid implementation but nothing else.
import React, {Component} from "react";
import Shuffle from 'shufflejs';
class PhotoGrid extends React.Component {
constructor(props) {
super(props);
const grayPixel = 'data:image/gif;base64,R0lGODlhAQABAIAAAMLCwgAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==';
const blackPixel = 'data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs=';
const greenPixel = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mO02Vz4HwAE9AJhcLBN6AAAAABJRU5ErkJggg==';
this.state = {
photos: [{
id: 4,
src: grayPixel
},
{
id: 5,
src: blackPixel
},
{
id: 6,
src: greenPixel
},
],
searchTerm: '',
sortByTitle: '',
sortByDate: '',
sortByPopularity: '',
filterCategory: ''
};
this.filters = {
cat1: [],
cat2: [],
};
this.wb = this.props.dataWB;
this.element = React.createRef();
this.sizer = React.createRef();
this._handleSearchKeyup = this._handleSearchKeyup.bind(this);
this._handleSortChange = this._handleSortChange.bind(this);
this._handleCategory1Change = this._handleCategory1Change.bind(this);
this._handleCategory2Change = this._handleCategory2Change.bind(this);
this._getCurrentCat1Filters = this._getCurrentCat1Filters.bind(this);
this._getCurrentCat2Filters = this._getCurrentCat2Filters.bind(this);
}
/**
* Fake and API request for a set of images.
* #return {Promise<Object[]>} A promise which resolves with an array of objects.
*/
_fetchPhotos() {
return new Promise((resolve) => {
setTimeout(() => {
resolve([{
id: 4,
username: '#stickermule',
title:'puss',
date_created: '2003-09-01',
popularity: '233',
category1:'animal',
category2:'mammals',
name: 'Sticker Mule',
src: 'https://images.unsplash.com/photo-1484244233201-29892afe6a2c?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=800&h=600&fit=crop&s=14d236624576109b51e85bd5d7ebfbfc'
},
{
id: 5,
username: '#prostoroman',
date_created: '2003-09-02',
popularity: '232',
category1:'industry',
category2:'mammals',
title:'city',
name: 'Roman Logov',
src: 'https://images.unsplash.com/photo-1465414829459-d228b58caf6e?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=800&h=600&fit=crop&s=7a7080fc0699869b1921cb1e7047c5b3'
},
{
id: 6,
username: '#richienolan',
date_created: '2003-09-03',
popularity: '231',
title:'nature',
category1:'art',
category2:'insect',
name: 'Richard Nolan',
src: 'https://images.unsplash.com/photo-1478033394151-c931d5a4bdd6?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=800&h=600&fit=crop&s=3c74d594a86e26c5a319f4e17b36146e'
}
]);
}, 300);
});
}
_whenPhotosLoaded(photos) {
return Promise.all(photos.map(photo => new Promise((resolve) => {
const image = document.createElement('img');
image.src = photo.src;
if (image.naturalWidth > 0 || image.complete) {
resolve(photo);
} else {
image.onload = () => {
resolve(photo);
};
}
})));
}
_handleSortChange(evt) {
var value = evt.target.value.toLowerCase();
function sortByDate(element) {
return element.getAttribute('data-created');
}
function sortByPopularity(element) {
return element.getAttribute('data-popularity');
}
function sortByTitle(element) {
return element.getAttribute('data-title').toLowerCase();
}
let options;
if (value == 'date-created') {
options = {
reverse: true,
by: sortByDate,
};
} else if (value == 'title') {
options = {
by: sortByTitle,
};
} else if (value == 'popularity') {
options = {
reverse: true,
by: sortByPopularity,
};
} else if (value == 'default') {
this.shuffle.filter('all');
} else {
options = {};
}
this.shuffle.sort(options);
};
_getCurrentCat1Filters = function () {
return this.filters.cat1.filter(function (button) {
return button.classList.contains('active');
}).map(function (button) {
console.log('button value: '+button.getAttribute('data-value'))
return button.getAttribute('data-value');
});
};
_getCurrentCat2Filters = function () {
return this.filters.cat2.filter(function (button) {
return button.classList.contains('active');
}).map(function (button) {
console.log('button value: '+button.getAttribute('data-value'))
// console.log('button value: '+button.getAttribute('data-value'))
return button.getAttribute('data-value');
});
};
_handleCategory1Change = function (evt) {
var button = evt.currentTarget;
console.log(button)
// Treat these buttons like radio buttons where only 1 can be selected.
if (button.classList.contains('active')) {
button.classList.remove('active');
} else {
this.filters.cat1.forEach(function (btn) {
btn.classList.remove('active');
});
button.classList.add('active');
}
this.filters.cat1 = this._getCurrentCat1Filters();
console.log('current cat contains : '+this.filters.cat1);
this.filter();
};
/**
* A color button was clicked. Update filters and display.
* #param {Event} evt Click event object.
*/
_handleCategory2Change = function (evt) {
var button = evt.currentTarget;
// Treat these buttons like radio buttons where only 1 can be selected.
if (button.classList.contains('active')) {
button.classList.remove('active');
} else {
this.filters.cat2.forEach(function (btn) {
btn.classList.remove('active');
});
button.classList.add('active');
}
this.filters.cat2 = this._getCurrentCat2Filters();
console.log('current cat contains : '+this.filters.cat2);
this.filter();
};
filter = function () {
if (this.hasActiveFilters()) {
this.shuffle.filter(this.itemPassesFilters.bind(this));
} else {
this.shuffle.filter(Shuffle.ALL_ITEMS);
}
};
itemPassesFilters = function (element) {
var cat1 = this.filters.cat1;
var cat2 = this.filters.cat2;
var cat1 = element.getAttribute('data-category1');
var cat2 = element.getAttribute('data-category2');
// If there are active shape filters and this shape is not in that array.
if (cat1.length > 0 && !cat1.includes(cat1)) {
return false;
}
// If there are active color filters and this color is not in that array.
if (cat2.length > 0 && !cat2.includes(cat2)) {
return false;
}
return true;
};
/**
* If any of the arrays in the `filters` property have a length of more than zero,
* that means there is an active filter.
* #return {boolean}
*/
hasActiveFilters = function () {
return Object.keys(this.filters).some(function (key) {
return this.filters[key].length > 0;
}, this);
};
_handleSearchKeyup(event) {
this.setState({
searchTerm: event.target.value.toLowerCase()
}, () => {
this.shuffle.filter((element) => {
return element.dataset.name.toLowerCase().includes(this.state.searchTerm) || element.dataset.username.toLowerCase().includes(this.state.searchTerm);
})
})
}
componentDidMount() {
// The elements are in the DOM, initialize a shuffle instance.
this.shuffle = new Shuffle(this.element.current, {
itemSelector: '.js-item',
sizer: this.sizer.current,
});
// Kick off the network request and update the state once it returns.
this._fetchPhotos()
.then(this._whenPhotosLoaded.bind(this))
.then((photos) => {
this.setState({
photos
});
});
}
componentDidUpdate() {
// Notify shuffle to dump the elements it's currently holding and consider
// all elements matching the `itemSelector` as new.
this.shuffle.resetItems();
}
componentWillUnmount() {
// Dispose of shuffle when it will be removed from the DOM.
this.shuffle.destroy();
this.shuffle = null;
}
render() {
return (
<div>
<div id='searchBar'>
<input type="text" className='js-shuffle-search' onChange={ this._handleSearchKeyup } value={ this.state.searchTerm } />
</div>
<div id='gridActions'>
<h2>Filter By cat 1</h2>
<button onClick={ this._handleCategory1Change } value='all'>All</button>
<button onClick={ this._handleCategory1Change } value='art'>Art</button>
<button onClick={ this._handleCategory1Change } value='industry'>Industry</button>
<button onClick={ this._handleCategory1Change } value='animal'>Animal</button>
<h2>Filter By cat 2</h2>
<button onClick={ this._handleCategory2Change } value='all'>All</button>
<button onClick={ this._getCurrentCat1Filters } value='mammals'>Mammals</button>
<button onClick={ this._getCurrentCat2Filters } value='insects'>Insects</button>
<h2>Sort By</h2>
<button onClick={ this._handleSortChange } value='default'>Default</button>
<button onClick={ this._handleSortChange } value='date-created'>By Date</button>
<button onClick={ this._handleSortChange } value='title'>By Title</button>
<button onClick={ this._handleSortChange } value='popularity'>By Popularity</button>
</div>
<div ref={ this.element } id='grid' className="row my-shuffle-container shuffle"> {
this.state.photos.map(image =>
<PhotoItem { ...image } />)}
<div ref={ this.sizer } className="col-1#xs col-1#sm photo-grid__sizer"></div>
</div>
</div>
);
}
}
function PhotoItem({id, src, category1, category2, date_created, popularity, title, name, username }) {
return (
<div key={id}
className="col-lg-3 js-item"
data-name={name}
data-title={title}
data-date-created={date_created}
data-popularity={popularity}
data-category1={category1}
data-cetagory2={category2}
data-username={username}>
<img src={src} style={{width : "100%",height :"100%"}}/>
</div>
)
}
export default PhotoGrid;
The photogrid right now does nothing, just displays photos which I'm unable to search, filter and sort.
Only judging by the documentation, I haven't tried it yet, but should work.
The instance of Shuffle has a filter method, which takes a string, or an array of strings, to filter the elements by "groups", or a callback function to perform more complicated search. You should call this.shuffle.filter after updating the state of your component, i.e.:
_handleSearchKeyup(event){
this.setState({searchTerm : event.target.value}, () => {
this.shuffle.filter((element) => { /* use this.state.searchTerm to return matching elements */ } );
})
}
Edited after building a fiddle.
The callback function looks at data-name and data-username attributes to check if they contain the search string
_handleSearchKeyup(event){
this.setState({searchTerm : event.target.value.toLowerCase()}, () => {
this.shuffle.filter((element) => {
return (
element.dataset.name.toLowerCase().includes(this.state.searchTerm) ||
element.dataset.username.toLowerCase().includes(this.state.searchTerm)
);
})
})
}
For the above to work you also need to add these attributes to the DOM nodes, so update the PhotoItem component:
function PhotoItem({ id, src, name, username }) {
return (
<div key={id}
className="col-md-3 photo-item"
data-name={name}
data-username={username}>
<img src={src} style={{width : "100%",height :"100%"}}/>
</div>
)
}
In opposition to pawel's answer I think that this library operates on DOM. It makes this not react friendly.
Classic input handlers saves values within state using setState method. As an effect to state change react refreshes/updates the view (using render() method) in virtual DOM. After that react updates real DOM to be in sync with virtual one.
In this case lib manipulates on real DOM elements - calling render() (forced by setState()) will overwritte earlier changes made by Shuffle. To avoid that we should avoid using setState.
Simply save filter and sorting parameters directly within component instance (using this):
_handleSearchKeyup(event){
this.searchTerm = event.target.value;
this.shuffle.filter((element) => { /* use this.searchTerm to return matching elements */ } );
}
Initialize all the params (f.e. filterCategories, searchTerm, sortBy and sortOrder) in constructor and use them in one this.shuffle.filter() call (second parameter for sort object) on every parameter change. Prepare some helper to create combined filtering function (mix of filtering and searching), sorting is far easier.
setState can be used for clear all filters button - forced rerendering - remember to clear all parameters within handler.
UPDATE
For sorting order declare
this.reverse = true; // in constructor
this.orderBy = null;
handlers
_handleSortOrderChange = () => {
this.reverse = !this.reverse
// call common sorting function
// extracted from _handleSortChange
// this._commonSortingFunction()
}
_handleSortByChange = (evt) => {
this.orderBy = evt.target.value.toLowerCase();
// call common sorting function
// extracted from _handleSortChange
// this._commonSortingFunction()
}
_commonSortingFunction = () => {
// you can declare sorting functions in main/component scope
let options = { reverse: this.reverse }
const value = this.orderBy;
if (value == 'date-created') {
options.by = sortByDate // or this.sortByDate
} else if (value == 'title') {
options.by = sortByTitle
//...
//this.shuffle.sort(options);
You can also store ready options sorting object in component instance (this.options) updated by handlers. This value can be used by _commonSortingFunction() to call this.shuffle.sort but also by filtering functions (second parameter).
reversing button (no need to bind)
<button onClick={this._handleSortOrder}>Reverse order</button>
UPDATE 2
If you want to work with 'normal' react, setState you can move (encapsulate) all the filtering (searchBar, gridActions) into separate component.
State update will force rerendering only for 'tools', not affecting elements managed in real DOM by shuffle (parent not rerendered). This way you can avoid manual css manipulations ('active') by using conditional rendering (plus many more possibilities - list active filters separately, show order asc/desc, show reset only when sth changed etc.).
By passing this.shuffle as prop you can simply invoke search/filter/sort in parent.

How to properly get message count on form submission using reactjs

How to properly get message count on form submission using reactjs.
For hours now am on this trying to find the possible solution.
The Application below submits and displays message info and everything works fine.
Now I have task of implementing a message counter each time message is submitted by a user.
This is what I have done to implement counter for the user
var senderId ='Nancy101';
const {textCount} = this.state;
var count = textCount[senderId] == undefined ? 1 : textCount[senderId] + 1;
textCount[senderId] = count;
alert('am counting: ' +count);
Here is my issue after adding the textcount method above
Each time I submit the form am having error
Uncaught TypeError: Cannot read property 'Nancy101' of undefined
at bundle.js:109988
here is the line of code that causes the issue
var count = textCount[senderId] == undefined ? 1 : textCount[senderId] + 1;
Here is the code
import React, { Component, Fragment } from "react";
import { render } from "react-dom";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
message: '',
};
this.sendMessage = this.sendMessage.bind(this);
}
componentDidMount() {
var textCount = [];
}
sendMessage = (message_text) => {
alert(message_text);
this.setState({
data: [
{ id: "1", name: "Nancy101", message: "Hello from User1"}
]
});
}
render() {
return (
<div>
<input type="text" placeholder="Message" value={this.state.message} onChange={ev => this.setState({message: ev.target.value})}/>
<br/>
<span onClick={ () => this.sendMessage(this.state.message)}>Submit</span>
{this.state.data.map((message, i) => {
if (message.message !=='' && message.name !=='') {
//var senderId=message.name;
var senderId ='Nancy101';
const {textCount} = this.state;
var count = textCount[senderId] == undefined ? 1 : textCount[senderId] + 1;
textCount[senderId] = count;
alert('am counting: ' +count);
return (
<div key={i}>
<div>
{message.name}: {message.message}
</div>
</div>
)
} else {
//alert nothing.
}
})}
</div>
);
}
}
The problem here is happening because you trying to get textCountfrom state. However, your state doesn't have a key named textCount.
This is what you are doing.
const { textCount } = this.state;
It's mean this.
const textCount = this.state.textCount;
Which is return you an undefined because your state object doesn't have that key.
Then you are trying to get the value of the key named Nancy101 from undefined object that's why you get that error.
You can fix it by add textCount in your initial state inside constructor like this.
constructor(props) {
super(props);
this.state = {
data: [],
message: '',
textCount: {},
};
Instead of get undefined here const {textCount} = this.state;, now you got an object. {}
Also, you can update this line.
`var count = textCount[senderId] == undefined ? 1 : textCount[senderId] + 1;`
To this.
`let count = !textCount[senderId] ? 1 : textCount[senderId] + 1;`
with this !textCount[senderId] it gonna check your data that is it equal 0, undefined or ''.

ReactJS - Construct html from mapping

I found the solution I wanted and it works, see below.....
I know I am doing something stupid but I am having a mental block this morning and would appreciate some help. I am trying to construct html from a mapping:
In my render:
< ul>
{ arryItems.map(this.foo.bind(this)) }
< /ul>
Calling function:
foo(arry, i) {
let result = '';
if (arry.children != undefined && arry.children.length > 0) {
result= (
<div key= {i}>{arry.name}
{arry.children.map(function(child, ix){
<p key= {ix}>{child.name}</p>
})}
</div>
)
} else {
result = (<div>{arry.name}</div>)
}
return result;
};
I am trying to return back html.
While I am receiving no error, the < p>{child.name}< /p> is not in the return html result.
Can anyone point out what is so blatantly obvious with my thinking that I cannot figure this out?
So you want to render a component to an html string? React does this with renderToString. e.g:
var Task = React.createClass({
render() {
return (
<li>{this.props.task.text}</li>
);
}
});
var Tasks = React.createClass({
getTasks() {
return [
{ _id: 1, text: 'This is task 1' },
{ _id: 2, text: 'This is task 2' },
{ _id: 3, text: 'This is task 3' }
];
},
renderTasks() {
return this.getTasks().map(task => {
return <Task key={task._id} task={task} />;
});
},
render() {
return (
<ul>
{this.renderTasks()}
</ul>
);
}
});
var html = ReactDOMServer.renderToString(Tasks);
Note that since 0.14 this method has been split out to react-dom/server package, earlier versions it's just in the react package.
I appreciate Dominic's help but I like my solution better, as it is compact and it checks if there are no children:
foo(arry, index) {
let children = this.getChildren(arry)
return (
<div key={index}>{arry.name}
{ children }
</div>
)
}
getChildren(arry){
let result = [];
if(arry.children != undefined && arry.children.length > 0){
arry.children.map(function(child, ix){
result.push(<p key={ix}>{child.name}</p>)
})
}
return result;
}
render() {
return (
<section>
{ arryItems.map(this.foo.bind(this)) }
</section>
);
}

Resources