Why do I get React error "Cannot read propery" - reactjs

I am getting the "Cannot read property" error when using refs inside a .map function.
What I am trying to do:
Change the background colour of specific values in the .map, so for example if the customer has autorenew on the background colour is green. If not the background colour is red.
The problem I am facing. I can make the code work on a button onClick function but as soon as I put it inside a componentDidMount I get the error cannot read property "style" of undefined.
My code:
export default class Dash extends React.Component {
constructor(props) {
super(props);
this.accordionContent = [];
}
accordionToggle = (value) => {
this.accordionContent[value].style.backgroundColor = "red";
};
render() {
return (
<>
{this.state.valueToMap.map((index, value) => {
var autoRenew = "on";
if (autoRenew === "on") {
this.accordionToggle(value);
}
return (
<div
ref={(ref) => (this.accordionContent[value] = ref)}
className="mini-lower"
>
BACKGROUND I AM TRYING TO CHANGE
</div>
//other bits of code here
);
})}
</>
);
}
}
I can call
<button onClick={() => this.accordionToggle(value)} >Click</button>
inside the .map and it works fine.

I ended up finding a really clean way to do what I was trying to achieve.
Instead of using refs i just used this below
className={this.state.renewalStatus[value] === 'EXPIRED' ? 'class-one' : 'class-two'}
Wish I had worked it out sooner!

Related

onClick for Cruise List Heading using React

At the moment I am trying to do a website on cruise ships using React in my spare time.
I have a working version on my Reviews branch, here https://github.com/RobertWSON/Personal-ship-project/tree/reviews.
However I am wanting to change how the Cruise Lines Page is displayed.
I would like to have Cruise Line Headings across the page.
When a Cruise Line Heading is clicked it expands to show a List of Ships for that Cruise Line and if you click again, it collapses to show just the Cruise Line Heading.
At the moment I am a bit confused, as to how I can make this work and I have not got it working just yet.
I have been working on this, on a different branch called robs-shipslist-under-cruiselines: here https://github.com/RobertWSON/Personal-ship-project/tree/robs-shipslist-under-cruiselines .
I have components called CruiseListHeader.jsx and ListofShips.jsx.
Just wondering if anyone can give me any advice on whether it's possible to do a ternary operator for this handleClick, that I have in my CruiseListHeader component?
It seems to me that the code inside my handleClick function is the code that causes the errors.
I think my state for opening and closing the ShipsList, so that's OpenshipsList and CloseshipsList, needs to be handled better.
How can I better deal with this?
Does anyone have any ideas that may help me solve this problem and make it work.
The following code is from my CruiseListHeader component
import React from 'react'
import {getCruiseLines } from '../api/api';
class CruiseListHeader extends React.Component {
constructor(props) {
super(props)
//setting intial state for cruise heading and shipsList and initialize cruiseHeaders as an empty array
this.state = {
cruiseHeaders: [],
shipsList: {isOpen:false}
}
//binding methods for Cruise Line Headers and Handle Click Function
this.setUpCruiseLines = this.setUpCruiseLines.bind(this),
this.handleClick = this.handleClick.bind(this)
}
componentDidMount() {
console.log('cdm')
this.setUpCruiseLines()
}
setUpCruiseLines() {
console.log('getcruiselines')
getCruiseLines()
.then(res => {
this.setState({
cruiseHeaders: res
})
})
}
/* There will be Headings for all the Cruise Lines.
When a Cruise Line Heading is clicked, it goes to ListofShips Component and the Ships List opens up for that Heading.
When user clicks on a Cruise Line Heading, when a Ships List is open, the Ships List Collapses.*/
handleClick(event) {
// Maybe do a ternary operator here before open and close functions
this.state.shipsList === isOpen ? OpenShipsList : CloseshipsList
OpenshipsList(event) {
this.setState = {shipsList: {isOpen:true}}
return
<div>
<ListofShips/>
</div>
}
CloseshipsList(event) {
this.setState = {shipsList: {isOpen: false}}
render()
}
}
// This renders at the start when the page loads and also when you close a list
render() {
return (
<React.Fragment>
<h3><button onClick = {this.handleClick}>{ship.cruise_line}</button></h3>
</React.Fragment>
)
}
}
export default CruiseListHeader
At the moment, when I do a yarn dev I am getting the following error
ERROR in ./client/components/CruiseListHeader.jsx Module build failed:
SyntaxError: Unexpected token, expected ; (42:29)
I would like to get rid of this error and display the page like I have described above.
As a beginning, to set isOpen correctly on the state, modify the onClick function handler as this:
handleClick(event) {
// this handleClick function should only handle the `isOpen` value in the state.
// Any renders supposibly to be made on the `render` method instead.
this.setState(prevState => ({
shipsList: {
isOpen: !prevState.shipsList.isOpen, //will reverse the prevState of isOpen.
}
}));
}
Now, Going to your render, we can handle the way you renderthe component that depends on the this.state.shipsList.isOpen this way:
render() {
//destructive declaration for isOpen from inside the shipsList in the state.
const { shipsList: { isOpen } } = this.state;
return (
<React.Fragment>
<h3>
<button onClick={this.handleClick}>
{ship.cruise_line}
</button>
</h3>
{
// Usually modals are shown at the bottom of the render return.
// it's better to use tenary `val ? component : null` rather than: (val && component)
// React accepts a component, or a null as return value, the second will return false if val was false.
isOpen ? <OpenShipsList /> : null
}
</React.Fragment>
)
}
PS: Please follow the comments inside the code above of each line, they should be enough illustrating what happened, if something was ambiguos, just let me know.
Hard to tell with the indentations, but is this.state.shipsList === isOpen ? OpenShipsList : CloseshipsList supposed to really be this.state.shipsList.isOpen ? OpenShipsList() : CloseshipsList();? Note that isOpen is a property of state.shipsList, and then the parens to invoke the calls to open/close the list, and also the semi-colon to end the line.
I think you probably really want your handleClick to simply toggle the open state and then use that state value to selectively render the list.
const handleClick = event => this.setState(
prevState => ({ shipsList: {isOpen: !prevState.shipsList.isOpen} })
);
render() {
const { shipsList: { isOpen } } = this.state;
return (
<Fragment>
{isOpen && <ListofShips />}
<h3>
<button onClick={this.handleClick}>{ship.cruise_line}</button>
</h3>
</Fragment>
)
}

Does it anti-pattern to change state parameter in render?

I need to make simple toggle animation, if state in component was changed. But I don't want to show this animation each time when react calls component render. I found a way how to do it, but I don't like that I need to change state variable directly without setState and make it in render. Also I don't like idea to generate new key each time on animation. I think it it anti-pattern. How can I achieve same result but with different way?
onClick() {
if(this.props.items.length>=5) {
this.setState({
toggleWarning: true
});
}else {
this.props.onStepAdd();
}
}
render() {
let toggle = "";
let toggleKey = null;
if(this.state.toggleWarning) {
toggle = " "+style.toggleMsg;
toggleKey = {key:Math.random()};
this.state.toggleWarning = false;
}
return (
<div>
<h1>Add New</h1>
<div>
<Button
onClick={this.onClick}
label="Add Step"
/>
</div>
<div
className={style.noteMsg + toggle}
{...toggleKey} >
<h3>Note: You can create only up to 5 steps for form wizard</h3>
</div>
</div>
);
}
not sure I understand the objective, but if you want to add a class after a click and then remove the class after some time:
previousTimeout = undefined
onClick = () => {
if(this.props.items.length>=5) {
this.setState({toggleWarning: true});
clearTimeout(this.previousTimeout);
this.previousTimeout = setTimeout(() => {
this.setState({toggleWarning: false})
}, 500);
}
render() {
...className={style.noteMsg + this.state.toggleWarning ? ' '+style.toggleMsg : ''}
}

Show data on buttonclick

I am very new to react ,Trying to build a app . The basic functionality i am trying to implement is
I will have some text ,with a button called show details ,clicking the button will show details regarding the text .
I have the text as well as the details saved as a object array in state
Watcher_list :[
{
id:uuid.v4(),
name : "Memory",
showdetails : "False"
},
{
id:uuid.v4(),
name :"Network",
showdetails : "False"
}
]
for the component to render the page the code is as follows
class Watchers extends Component
{
showdetails(id)
{
this.props.showonclick(id);
}
render() {
if (this.props.item.showdetails ==="True") {
return (
<div className="Watchers" >
<li>{this.props.item.name}</li>
<p1>{this.props.item.id} </p1>
<button ref="show details" onClick={this.showdetails.bind(this,this.props.item.id)} >
Show Details
</button>
</div>
);}
else {
return (
<div className="Watchers" >
<li>{this.props.item.name}</li>
<button ref="show details" onClick={this.showdetails.bind(this,this.props.item.id)} >
Show Details
</button>
</div>
);
}
}
}
export default Watchers;
the handler for show click just updates the value of showdetails in the state ,ad upon re rendering the details are displayed.
I just wanted to know wether this is the best way to do this ,or is there a much smarter way that I can do the same thing ?
There are a couple of things that you can improve on,
First: you have a lot of redundant code that can be avoided by using conditional rendering like
class Watchers extends Component {
showdetails(id){
this.props.showonclick(id);
}
render() {
return (
<div className="Watchers" >
<li>{this.props.item.name}</li>
{this.props.item.showdetails ==="True" ? <p1>{this.props.item.id} </p1> : null }
<button ref={(ref) => this.showDetails = ref} onClick =
{this.showdetails.bind(this,this.props.item.id)} > Show Details </button>
</div>
);
}
}
export default Watchers;
Second: bind inside render is not a good pattern since it creates a new function everytime render is called. Check this answer on how to avoid it.
Third: I don't see any point on why you are using ref looking at the code in your question, but even if you need it, you need to use ref callback like ref={(ref) => this.showDetails = ref} instead of string refs since string refs are a legacy
You need not bind inside render function.
No need to set ref
For a change in ID, you do not require separate conditions. You can use the short circuit operator.
class Watchers extends Component {
constructor(props) {
super(props);
this.showdetails = this.showdetails.bind(this);
}
showdetails(id) {
this.props.showonclick(id);
}
render() {
return (
<div className = "Watchers">
<li> {this.props.item.name}</li>
{
this.props.item.showdetails === "True" &&
<p> {this.props.item.id} < /p>
}
<button onClick = {this.showdetails}>Show Details</button>
</div>
);
}
}
export default Watchers;
If you are setting state locally, you should probably set showDetails to Boolean true instead of a String like you have provided.

Trigger Re-Render of Child component

I'm new to React and am running into the same problem a few times. In this particular situation, I'm trying to get an option in a select dropdown to update when I update a text input.
I have a parent, App, with the state attribute "directions", which is an array. This gets passed as a property to a child, GridSelector, which creates the text field and dropdown. When the text field is changed, a function triggers to update the parent state. This in turn causes the GridSelector property to update. However, the dropdown values, which are originally generated from that GridSelector property, do not re-render to reflect the new property value.
I'm trying to figure out the most React-ful way to do this and similar manuevers. In the past, I've set a state in the child component, but I think I've also read that is not proper.
My working site is at amaxalaus.bigriverwebdesign.com
Here's the pertinent code from each file:
App.js
class App extends React.Component {
constructor(props){
super(props);
this.state = {
directions: [],
dataRouteDirections: '/wp-json/wp/v2/directions',
currentDirectionsIndex: 0
}
this.addImageToGrid = this.addImageToGrid.bind(this);
this.changeTitle=this.changeTitle.bind(this);
}
componentDidMount(){
fetch(this.state.dataRouteDirections)
.then(data => data=data.json())
.then(data => this.setState({directions:data}));
}
addImageToGrid(image) {
this.refs.grid.onAddItem(image); //passes image add trigger from parent to child
}
createNewDirections(){
var directions= this.state.directions;
var index = directions.length;
var lastDirections = directions[directions.length-1];
var emptyDirections= {"id":0,"acf":{}};
emptyDirections.acf.grid="[]";
emptyDirections.acf.layout="[]";
emptyDirections.title={};
emptyDirections.title.rendered="New Directions";
if (lastDirections.id!==0 ) { ///checks if last entry is already blank
this.setState({
directions: directions.concat(emptyDirections), //adds empty directions to end and updates currentdirections
currentDirectionsIndex: index
});
}
}
changeTitle(newTitle){
var currentDirections = this.state.directions[this.state.currentDirectionsIndex];
currentDirections.title.rendered = newTitle;
}
render() {
var has_loaded; //has_loaded was added to prevent double rendering during loading of data from WP
this.state.directions.length > 0 ? has_loaded = 1 : has_loaded = 0;
if (has_loaded ) {
/* const currentGrid = this.state.directions;*/
return ( //dummy frame helpful for preventing redirect on form submit
<div>
<div className="fullWidth alignCenter container">
<GridSelector
directions={this.state.directions}
currentDirectionsIndex={this.state.currentDirectionsIndex}
changeTitle={this.changeTitle}
/>
</div>
<Grid ref="grid"
currentGrid={this.state.directions[this.state.currentDirectionsIndex]}
/>
<ImageAdd addImageToGrid={this.addImageToGrid}/>
<div className="fullWidth alignCenter container">
<button onClick={this.createNewDirections.bind(this)}> Create New Directions </button>
</div>
</div>
)
} else {
return(
<div></div>
)
}
}
}
GridSelector.js
class GridSelector extends React.Component {
constructor(props) {
super(props);
var currentDirections = this.props.directions[this.props.currentDirectionsIndex];
this.state = {
currentTitle:currentDirections.title.rendered
}
}
createOption(direction) {
if (direction.title) {
return(
<option key={direction.id}>{direction.title.rendered}</option>
)
} else {
return(
<option></option>
)
}
}
handleChangeEvent(val) {
this.props.changeTitle(val); //triggers parent to update state
}
render() {
return(
<div>
<select name='directions_select'>
{this.props.directions.map(direction => this.createOption(direction))}
</select>
<div className="fullWidth" >
<input
onChange={(e)=>this.handleChangeEvent(e.target.value)}
placeholder={this.state.currentTitle}
id="directionsTitle"
/>
</div>
</div>
)
}
}
You made a very common beginner mistake. In React state should be handled as an immutable object. You're changing the state directly, so there's no way for React to know what has changed. You should use this.setState.
Change:
changeTitle(newTitle){
var currentDirections = this.state.directions[this.state.currentDirectionsIndex];
currentDirections.title.rendered = newTitle;
}
To something like:
changeTitle(newTitle){
this.setState(({directions,currentDirectionsIndex}) => ({
directions: directions.map((direction,index)=>
index===currentDirectionsIndex? ({...direction,title:{rendered:newTitle}}):direction
})

Why does the handleClick method in my component seem to loop?

I am setting out to learn some react, and I am trying to create a grid of clickable buttons. For now all I want is to change the color of the button once it is clicked.
Starting off from this React Tutorial, I wrote my components to create a variable size grid through loops.
I added a handleClick method to my Grid component to change the color of the button clicked. When I click a button though, the entire column changes colour.
From the debugger it seems to pass the correct values of the indeces (i and j) to the handleClick method, so I am at a loss as to why this is happening.
The code for my components is the following:
var height = 3;
var width = 5;
class Cell extends React.Component {
render() {
return (
<button
className={ this.props.value === 'ON' ? "cell cellOn" : "cell
cellOff" }
onClick={() => this.props.onClick()}>
</button>
);
}
}
export class Grid extends Component {
constructor(props) {
super(props);
this.state = {
cells: Array(height).fill(Array(width).fill(null)),
};
}
handleClick(i, j) {
const cells = this.state.cells.slice();
cells[i][j] = ( cells[i][j] === 'ON' ) ? 'OFF' : 'ON' ;
this.setState({cells: cells});
}
renderCell(i, j) {
return(
<Cell value={this.state.cells[i][j]}
onClick={() => this.handleClick(i, j)} />
);
}
renderCellRow(i, w) {
return(
<div>
{Array.from(Array(w), (_,x) => x).map((j) => this.renderCell(i, j)) }
</div>
);
}
render() {
const status = 'Next player: X';
return (
<div>
<div className="status">{status}</div>
<div>
{Array.from(Array(height), (_,x) => x).map((i) => this.renderCellRow(i, width)) }
</div>
</div>
);
}
}
Could somebody offer some suggestions? Is it because of the map calls that I used to loop on height and width?
Thanks in advance!
The problem is in the way of your state initialization:
Array(height).fill(Array(width).fill(null))
I'll suggest to avoid using fill. It fills the array with objects by reference. So when you change one of them you get updates into the other ones.
Here is a codepen with the working version https://codepen.io/krasimir/pen/YYdLQL?editors=0010

Resources