ComponentDidUpdate throwing Cannot read property props nextProps.Value of undefined - javascript

I'm trying to update the styling of a <div> tag of my component based when data gets updated in the parent component. More specifically, it's a list of <div>: when one element of this list is onHover, I try to trigger other images to visibility: "hidden". Here's what I have so far (I've commented so that it helps in clarity):
componentDidUpdate(nextProps, prevState) {
// if a componnent has been hovevered
if (nextProps.isSelectedMouseEnterThumbnail) {
// trigger the function to updated the div located within this component
return this.updateOnHoverImage(nextProps, prevState)
} else {
return null;
}
};
updateOnHoverImage = (nextProps, prevState) => {
let componentIndex = this.props.index;
let onHoverComponentIndex = nextProps.selectedMouseEnterIndex;
// for each component, it displays:
// componentIndex: its index
// onHoverComponentIndex: the index of the hovered component;
// it does not start with null or undefined;
console.log(componentIndex, onHoverComponentIndex);
// if both matches, then trigger {visibility: "visible"}
if (onHoverComponentIndex === componentIndex) {
console.log(`visible for: ${componentIndex}`);
return {
visibility: "visible"
}
// Otherwise, trigger {visibility: "hidden"}
} else if (onHoverComponentIndex !== componentIndex) {
console.log(`hidden for: ${componentIndex}`);
return {
visibility: "hidden"
}
} else {
return null;
}
};
Then, in my parent component:
constructor(props) {
super(props);
this.state = {
counter: 0,
selectedSection: "all",
mock_data: mock_data,
dataOnView: null,
sections: ["all", "film", "music", "commercial"],
selectedMouseEnterIndex: null,
isSelectedMouseEnterThumbnail: false
};
};
<IndexImageComponent
onSelectedMouseEnterIndexImage={this.onSelectedMouseEnterIndexImage}
onSelectedMouseLeaveIndexImage={this.onSelectedMouseLeaveIndexImage}
index={index}
ele={ele}
{...this.state}
/>
onSelectedMouseEnterIndexImage = (index) => {
this.setState({
selectedMouseEnterIndex: index,
isSelectedMouseEnterThumbnail: true
})
};
onSelectedMouseLeaveIndexImage = (index) => {
this.setState({
selectedMouseEnterIndex: null,
isSelectedMouseEnterThumbnail: false
})
}
When I hover, the console.log(visible for: ${componentIndex}); and console.log(hidden for: ${componentIndex}); do work. But when I implement the style, such as this:
<div
style={this.updateOnHoverImage()}
onMouseEnter={() => this.onSelectedMouseEnterIndexImage(index)}
onMouseLeave={() => this.onSelectedMouseLeaveIndexImage(null)}
className="index_image_component">
this errror happens: TypeError: Cannot read property 'selectedMouseEnterIndex' of undefined.

Related

ReactJs: TypeError: Cannot read property 'ItemsServices' of undefined

Here I am getting some problems with AliceCarousel to map my response to display its images in the gallery.
I wanted to display the respective types of images for each gallery.
I am generally following SO example .
Any help or suggestion here to make it possible?
Thanks is advance.
//Js
class KitchenService extends Component {
constructor(props) {
super(props);
this.state = {
currentIndex: 0,
responsive: { 1024: { items: 3 } },
galleryItems: this.galleryItems(),
services : this.props.resume,
...props,
ItemsServices:[]
}
}
static propTypes = {
getService: PropTypes.func.isRequired,
resume: PropTypes.object.isRequired,
auth: PropTypes.object.isRequired,
loading: PropTypes.object.isRequired
}
UNSAFE_componentWillReceiveProps(nextProps) {
if(nextProps.resume !== this.props.resume){
var services = this.props.resume.services;
this.setState({
ItemsServices: services
})
}
}
componentDidMount() {
this.props.getService();
}
slideTo = (i) => this.setState({ currentIndex: i })
onSlideChanged = (e) => this.setState({ currentIndex: e.item })
galleryItems = () => {
return this.state.ItemsServices.map((brand, i) => {
var checkImage = brand.length !== 0 && brand.service_name === "Office";
console.log(checkImage, "checkImage")
return (
<div key={`key-${i}`} className="card-img-top"><img src={brand.service_image_url} /></div>
)
})
};
render() {
const { responsive, currentIndex } = this.state
const items = this.galleryItems();
return(
<div>
<Grid className ="col-12 service-kitchen-gallery-grid" >
<div className="service-gallery-headline">
Kitchen
</div>
<AliceCarousel
dotsDisabled={true}
buttonsDisabled={true}
items={items}
responsive={responsive}
slideToIndex={currentIndex}
onSlideChanged={this.onSlideChanged}
/>
</Grid>
</div>
)
}
}
const mapStateToProps = (state) => ({
resume: state.resume,
});
export default connect(mapStateToProps, {getService }) (KitchenService);
//Error
TypeError: Cannot read property 'ItemsServices' of undefined
service API response
(console.log(services))
[
{
_id: "5f1971da18ba2b04704d65c2",
service_name: "Other",
service_image_url:
"https://res.cloudinary.com/tammycloudinary/image/upload/v1595503076/nou0knjbtkujxwjktang.png",
date: "2020-07-23T11:17:46.928Z",
__v: 0,
},
{
_id: "5f1971b218ba2b04704d65c1",
service_name: "Bedroom",
service_image_url:
"https://res.cloudinary.com/tammycloudinary/image/upload/v1595503036/kfiteeilh4doytio6gs8.png",
date: "2020-07-23T11:17:06.742Z",
__v: 0,
}
];
The issue is not coming from const items = this.galleryItems(); like I originally thought. It is coming from the constructor.
You are attempting to use the state object in order to build the initial state object. This obviously will not work.
constructor(props) {
super(props);
this.state = {
currentIndex: 0,
responsive: { 1024: { items: 3 } },
galleryItems: this.galleryItems(), // <-- Here is the problem
services : this.props.resume,
...props,
ItemsServices:[]
}
}
You attempt to initialize state by calling this.galleryItems. But that function relies on this.state already being declared. Since it has not been created yet (but is in the process of being declared), it is undefined and you get this error.
I don't think gallaryItems really belongs in state at all. It's generally not recommended to store JSX in state anyway. Instead just use the function like you have in the render to compute the JSX needed each render.
Another note: Don't use this.props in the constructor. Instead use the props that are passed in to the constructor.
Y0u can solve this with this solution as well with filter.
render() {
const { services, loading} = this.props.resume;
var checkImage = services.length === 0 ? [] : services.filter((item) => item.service_name === "Kitchen")
return(
<div>
<OwlCarousel className="owl-theme" loop margin={10} nav>
{checkImage.map((item, i) => (
<div className="col-xs-12 item" key={item._id} data-id={item._id} >
<img className="service-gallery-images" src={item.service_image_url} alt=""/>
</div>
))}
</OwlCarousel>
</div>
)
}

element not updating value on html Polymer V3

I want to update the todoList in my PARENT COMPONENT after I have added a new item in my child using the AddItem() method. Nothing gets added the first time.
EX. if I add "take test" doesn't get render, then if I add "take shower" doesn't get rendered but now "take test" does. Then if I add "take a leak" "take shower" gets rendered.
PARENT COMPONENT
firstUpdated(changedProperties) {
this.addEventListener('addItem', e => {
this.todoList = e.detail.todoList;
});
}
render() {
return html`
<p>Todo App</p>
<add-item></add-item>//Child item that triggers the add
<list-items todoList=${JSON.stringify(this.todoList)}></list-items>
`;
}
CHILD COMPONENT
AddItem() {
if (this.todoItem.length > 0) {
let storedLocalList = JSON.parse(localStorage.getItem('todo-list'));
storedLocalList = storedLocalList === null ? [] : storedLocalList;
const todoList = [
...storedLocalList,
{
id: this.uuidGenerator(),
item: this.todoItem,
done: false
}
];
localStorage.setItem('todo-list', JSON.stringify(todoList));
this.dispatchEvent(
new CustomEvent('addItem', {
bubbles: true,
composed: true,
detail: { todoList: storedLocalList }
})
);
this.todoItem = '';
}
}
render() {
return html`
<div>
<input .value=${this.todoItem} #keyup=${this.inputKeyup} />
<button #click="${this.AddItem}">Add Item</button>
</div>
`;
}
You need to set properties for todoItem
static get properties() {
return {
todoItem: {
type: Array,
Observer: '_itemsUpdated'
}
}
constructor(){
this.todoItem = []
}
_itemsUpdated(newValue,oldValue){
if(newValue){
-- write your code here.. no event listeners required
}
}
In above code., We need to initialise empty array in constructor.
Observer observe the changes to array & triggers itemsUpdated function which carries oldValue & NewValue. In that function., you can place your logic.
No Event Listeners required as per my assumption
Found my error. I was passing to detail: { todoList : storedLocalList } which is the old array without the updated value.
AddItem() {
if (this.todoItem.length > 0) {
let storedLocalList = JSON.parse(localStorage.getItem('todo-list'));
storedLocalList = storedLocalList === null ? [] : storedLocalList;
const todoList = [
...storedLocalList,
{
id: this.uuidGenerator(),
item: this.todoItem,
done: false
}
];
localStorage.setItem('todo-list', JSON.stringify(todoList));
this.dispatchEvent(
new CustomEvent('addItem', {
bubbles: true,
composed: true,
detail: { todoList: todoList }
})
);
this.todoItem = '';
}
}

React search and filter methods issue

I'm working on a component that should be able to:
Search by input - Using the input field a function will be called after the onBlur event got triggered. After the onBlur event the startSearch() method will run.
Filter by a selected genre - From an other component the user can select a genre from a list with genres. After the onClick event the startFilter() method will run.
GOOD NEWS:
I got the 2 functions above working.
BAD NEWS:
The above 2 functions don't work correct. Please see the code underneath. The 2 calls underneath work, but only if I comment one of the 2 out. I tried to tweak the startSearch() method in various ways, but I just keep walking to a big fat wall.
//////Searching works
//////this.filter(this.state.searchInput);
//Filtering works
this.startFilter(this.state.searchInput);
QUESTION
How can I get the filter/search method working?. Unfortunately simply putting them in an if/else is not the solution (see comments in the code).
import { Component } from 'preact';
import listData from '../../assets/data.json';
import { Link } from 'preact-router/match';
import style from './style';
export default class List extends Component {
state = {
selectedStreamUrl: "",
searchInput: "",
showDeleteButton: false,
searchByGenre: false,
list: [],
}
startFilter(input, filterByGenre) {
this.setState({
searchByGenre: true,
searchInput: input,
showDeleteButton: true
});
alert("startFilter  ")
console.log(this.state.searchByGenre)
/////////---------------------------------
document.getElementById("searchField").disabled = false;
document.getElementById('searchField').value = input
document.getElementById('searchField').focus()
// document.getElementById('searchField').blur()
document.getElementById("searchField").disabled = true;
console.log(input)
this.filter(input);
}
//search
startSearch(input) {
alert("startSearch  ")
console.log(this.state.searchByGenre)
//komt uit render()
if (!this.state.searchByGenre) {
//check for input
this.setState({
searchInput: input.target.value,
showDeleteButton: true,
})
//Searching works
//this.filter(this.state.searchInput);
//Filtering works
this.startFilter(this.state.searchInput);
// DOESNT WORK:
// if (this.state.searchInput != "") {
// this.filter(this.state.searchInput);
// } else {
// this.startFilter(this.state.searchInput);
// }
}
}
setAllLists(allLists) {
console.log("setAllLists")
console.log(this.state.searchByGenre)
this.setState({ list: allLists })
//document.body.style.backgroundColor = "red";  
}
filter(input) {
let corresondingGenre = [];
let filteredLists = listData.filter(
(item1) => {
var test;
if (this.state.searchByGenre) {
alert("--this.state.searchByGenre")
//filterByGenre
//& item1.properties.genre == input
for (var i = 0; i < item1.properties.genre.length; i++) {
if (item1.properties.genre[i].includes(input)) {
corresondingGenre.push(item1);
test = item1.properties.genre[i].indexOf(input) !== -1;
return test;
}
this.setState({ list: corresondingGenre })
}
} else {
//searchByTitle
alert("--default")
test = item1.title.indexOf(input.charAt(0).toUpperCase()) !== -1;
}
return test;
})
console.log("filterdLists:")
console.log(filteredLists)
console.log("corresondingGenre:")
console.log(corresondingGenre)
//alert(JSON.stringify(filteredLists))
this.setState({ list: filteredLists })
}
removeInput() {
console.log("removeInput    ")
console.log(this.state.searchByGenre)
this.setState({ searchInput: "", showDeleteButton: false, searchByGenre: false })
document.getElementById("searchField").disabled = false;
this.filter(this.state.searchInput)
}
render() {
//alle 's komen in deze array, zodat ze gefilterd kunnen worden OBV title.
if (this.state.list === undefined || this.state.list.length == 0 && this.state.searchInput == "") {
//init list
console.log("render ")
console.log(this.state.searchByGenre)
this.filter(this.state.searchInput)
}
return (
<div class={style.list_container}>
<input class={style.searchBar} type="text" id="searchField" placeholder={this.state.searchInput} onBlur={this.startSearch.bind(this)} ></input>
{
this.state.searchByGenre ?
<h1>ja</h1>
:
<h1>nee</h1>
}
{
this.state.showDeleteButton ?
<button class={style.deleteButton} onClick={() => this.removeInput()}>Remove</button>
: null
}
{
this.state.list.map((item, index) => {
return <div>
<p>{item.title}</p>
</div>
})
}
</div>
);
}
}
SetState is an async operation that takes a callback function. I suspect that your second function runs before the first SetState is finished.
Also, you are modifying the DOM yourself. You need to let React do that for you just by modifying state. I don't have time to write up an example now, but hopefully this helps in the meantime.
can you modify your search func,
//search
startSearch(input) {
const { value } = input.target
const { searchInput } = this.state
if (!this.state.searchByGenre) {
this.setState(prevState => ({
searchInput: prevState.searchInput = value,
showDeleteButton: prevState.showDeleteButton = true,
}))
JSON.stringify(value) !== '' ? this.filter(value) : this.startFilter(searchInput)
}
}

Access property of React component based on another property

Ultimately I am trying to create a piano like application that you can control by click or key down. I want each keyboard key to control a certain note. Each "piano" key is a component which has a keycode property and a note property. When the event.keycode matches the keycode property I want the associated note to play. What is the best strategy to go about this?
I have tried using refs and playing around with focus on componentDidMount. I cant seem to wrap my head around how this should work.
class Pad extends React.Component {
constructor(props) {
super(props)
this.state = {
clicked: false
}
this.clickedMouse = this.clickedMouse.bind(this)
this.unClickedMouse = this.unClickedMouse.bind(this)
this.handleDownKeyPress = this.handleDownKeyPress.bind(this)
this.handleUpKeyPress = this.handleUpKeyPress.bind(this)
}
clickedMouse(e) {
this.setState({ clicked: true })
this.props.onDown(this.props.note)
console.log(e)
}
unClickedMouse(e) {
this.setState({ clicked: false })
this.props.onUp(this.props.note)
}
handleDownKeyPress(e) {
if (e.keyCode === this.props.keyCode && this.state.clicked === false) {
this.setState({ clicked: true })
this.props.onDown(this.props.note)
}
}
handleUpKeyPress(e) {
this.setState({ clicked: false })
this.props.onUp(this.props.note)
}
render() {
return (
<div
className='pad'
onMouseUp={this.unClickedMouse}
onMouseDown={this.clickedMouse}
onKeyDown={this.handleDownKeyPress}
onKeyUp={this.handleUpKeyPress}
tabIndex='0'
/>
);
}
}
export default Pad
class Pads extends React.Component {
constructor(props) {
super(props);
// tone.js build
this.synth = new Tone.Synth().toMaster()
this.vol = new Tone.Volume(0)
this.synth.chain(this.vol, Tone.Master)
// bindings
this.onDownKey = this.onDownKey.bind(this);
this.onUpKey = this.onUpKey.bind(this);
}
onDownKey(note) {
console.log(`${note} played`);
this.synth.triggerAttack(note);
}
onUpKey(note) {
this.synth.triggerRelease();
}
render() {
const { octave } = this.props
return (
<div className="pad-grid">
<Pad
keyCode={65}
note={`C${octave}`}
onDown={this.onDownKey}
onUp={this.onUpKey}
/>
<Pad
keyCode={70}
note={`Db${octave}`}
onDown={this.onDownKey}
onUp={this.onUpKey}
/>
<Pad
keyCode={83}
note={`D${octave}`}
onDown={this.onDownKey}
onUp={this.onUpKey}
/>
<Pad
keyCode={68}
note={`Eb${octave}`}
onDown={this.onDownKey}
onUp={this.onUpKey}
/>
</div>
);
}
}
export default Pads
Heres a codepen so you can check it out in action https://codepen.io/P-FVNK/pen/XopBgW
Final Code: https://codesandbox.io/s/5v89kw6w0n
So I made quite a number of changes to enable the functionality you wanted.
Is there a way to maybe loop through the repeated components and their properties to match event.keyCode to correct keycode property?
To enable this, I decided to create an object array containing the possible keys and their notes. I changed them from the now deprecated KeyboardEvent.keyCode to KeyboardEvent.key instead. You could use KeyboardEvent.code as well.
this.padCodes = [
{
key: "a",
note: "C"
},
{
key: "f",
note: "Db"
},
{
key: "s",
note: "D"
},
{
key: "d",
note: "Eb"
}
];
I moved the keydown event listeners from the individual <Pad />s to the parent component and attached the listener to the document instead.
document.addEventListener("keydown", e => {
let index = this.state.pads.findIndex(item => item.key === e.key);
let { octave } = this.context;
if (this.state.pressed === false) {
this.onDownKey(`${this.state.pads[index].props.note}${octave}`);
this.setState({
activeNote: this.state.pads[index].note,
pressed: true
});
}
});
document.addEventListener("keyup", e => {
this.onUpKey(this.state.activeNote);
this.setState({
activeNote: "",
pressed: false
});
});
You may also notice I decided to use the Context instead of props. That's just personal preference, but I rather have one main reference to the octave state rather than passing the prop to each child component.
After that it was just a matter of ensuring that the functions made the calls to the same function and passed both the note and octave.
To reduce some of the clutter I made an array of <Pad />s which I later rendered instead of typing them out one by one.
let newPadCodes = this.padCodes.map(x => (
<Pad
key={x.key.toString()}
note={`${x.note}`}
onDown={this.onDownKey}
onUp={this.onUpKey}
/>
));
this.setState({
pads: newPads
});
//And later
//Render if this.state.pads.length is > 0
{this.state.pads.length > 0 &&
this.state.pads}
That just about covers all I did. Here's the codesandbox with the modified code: https://codesandbox.io/s/5v89kw6w0n

ReactJS: how to map JSON elements sequentially and show the hidden div on click

I'm trying to load items from JSON and toggle a dropdown div with description on click. While I can display elements sequentially (ex: loc1 & desc1, loc2 & desc2) on static divs I'm having trouble finding out how to render it properly when the second part (desc) is hidden and only shows when the loc div is clicked.
What would be the best way to map the result so it doesn't show as loc1 & loc2, desc1 & desc2 but as loc1 & desc1, loc2 & desc2?
Code:
var places = {
library: {
location: [
{
loc_name: "library1",
"desc": "desc1 : Modern and spacious building"
},
{
loc_name: "library2",
"desc": "desc2 : A cosy small building"
}
]
}
};
function contentClass(isShow) {
if (isShow) {
return "content";
}
return "content invisible";
}
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = { isShow: false };
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(function (prevState) {
return { isShow: !prevState.isShow };
});
}
render() {
const libraries_desc = places.library.location.map((libr) =>
<div>
<p>{libr.desc}</p>
</div>
);
const lib_names = places.library.location.map((libr) =>
<div>
<p>{libr.loc_name}</p>
</div>
);
return (
<div>
<div className='control' onClick={this.handleClick}>
<h4>{lib_names}</h4>
<div className={contentClass(this.state.isShow)}>{libraries_desc}</div>
</div>
</div>
);
}
}
render((
<Toggle />
), document.getElementById('root'));
Current result:
library1
library2
desc1 : Modern and spacious building
desc 2 : A cosy small building
Desired Result:
library1
desc1 : Modern and spacious building (hidden but shown when clicked)
library2
desc 2 : A cosy small building (hidden but shown when clicked)
Codesandbox
I might try extracting a location into a separate component. By extracting it, each location is responsible for knowing its state. In your case, that means its visibility (controlled by this.state.isShow).
Here's how you could do it:
import React from 'react';
import { render } from 'react-dom';
var places = {
library: {
location: [
{
loc_name: "library1",
"desc": "Modern and spacious building"
},
{
loc_name: "library2",
"desc": "A cosy small building"
}
]
}
};
class Location extends React.Component {
constructor(props) {
super(props);
this.state = { isShow: false };
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(function (prevState) {
return { isShow: !prevState.isShow };
});
}
contentClass(isShow) {
if (isShow) {
return "content";
}
return "content invisible";
}
render() {
return (
<div className='control' onClick={this.handleClick}>
<h4>{this.props.desc}</h4>
<div className={this.contentClass(this.state.isShow)}>{this.props.loc_name}</div>
</div>
)
}
}
class Toggle extends React.Component {
constructor(props) {
super(props);
}
render() {
const locations = places.library.location.map(location => {
return <Location {...location} />
})
return (
<div>
{locations}
</div>
);
}
}
render((
<Toggle />
), document.getElementById('root'));
Your Toggle Component should be like this.
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {
isShow: false,
id: -1, // initial value
};
}
handleClick = (id) => {
this.setState({
isShow: !this.state.isShow,
id: id
});
}
render() {
const { location } = places.library;
const { isShow, id } = this.state;
return (
<div className="control">
{location.map((libr, index) => (
<div key={index} onClick={() => { this.handleClick(index) }}>
<p>{libr.loc_name}</p>
{(isShow && (id === index)) && <p>{libr.desc}</p>}
</div>
))}
</div>
);
}
}
So when you click on the div element. A click event will be triggered called handleClick which will pass the index as a param to the function. which will set isShow to false or truth and vice versa along with the current element you want to show which will be selected through this.state.id. So everytime isShow is true and this.state.id matched index element of the array. Your description will show otherwise it will be hidden as you want.
So your desired result will be something like this.
library1
desc1 : Modern and spacious building (hidden but shown when clicked)
library2
desc 2 : A cosy small building (hidden but shown when clicked)

Categories