Disable react-router Link in react - javascript

I'm new to React and got a issue to disable a link covert by a button element.
I tried the following:
class ShoppingCartLink extends React.Component {
constructor(){
super();
ShoppingCartStore.register(this.refresh.bind(this));
this.state = {count:0};
this.item = "items";
this.linkDisabled = new Boolean(true);
}
refresh(){
if (ShoppingCartStore.items.length === 0 || ShoppingCartStore.items.length > 1 )
{
this.item = "items";
}
else
{
this.item = "item";
}
if (ShoppingCartStore.items.length !== 0)
{
this.linkDisabled = false;
}
this.setState({count: ShoppingCartStore.items.length});
}
render() {
return (
<div>
<button type="button" disabled = {this.linkDisabled}>
<Link to="shoppingCart">Shopping Cart: {this.state.count} {this.item}</Link>
</button>
</div>
)
}
}
By default the Link should be disbaled as long no item is added to the cart.
I debugged through it and when the constructor is called "linkDisabled" is set to true as well in render(). The problem is that the link is still enabled.
Thanks for your help!

There is no "disabled" attribute for anchor tags, and Links are just router aware anchor tags. You can use one of the two things
1. Diasble pointer events with css
REACT Component
constructor(){
super();
ShoppingCartStore.register(this.refresh.bind(this));
this.state = {
count:0,
linkDisabled: 'disable-link';
};
}
refresh(){
const length = ShoppingCartStore.items.length;
const classValue = (length === 0)? 'disable-link': '';
this.setState({
count: length,
linkDisabled: classValue
});
}
render() {
const length = ShoppingCartStore.items.length;
return (<div>
<button type="button" disabled={this.state.linkDisabled}>
<Link to="shoppingCart" className={this.state.linkDisabled}>Shopping Cart: {this.state.count} {((length > 0)?"items":"item")}</Link>
</button>
</div>);
}
CSS
. disable-link {
pointer-events: none;
}
2. Make use of event.preventDefault() on click of link if there are no items in the cart
constructor(){
super();
ShoppingCartStore.register(this.refresh.bind(this));
this.state = {
count:0,
linkDisabled: true
};
}
refresh(){
const length = ShoppingCartStore.items.length;
this.setState({
count: length,
linkDisabled: (length !== 0)
});
}
handleClick = (e) => {
if(this.state.linkDisabled == true) {
e.preventDefault();
}
}
render() {
const length = ShoppingCartStore.items.length;
return (<div>
<button type="button" disabled={this.state.linkDisabled}>
<Link to="shoppingCart" onClick={this.handleClick}>Shopping Cart: {this.state.count} {((length > 0)?"items":"item")}</Link>
</button>
</div>);
}
However in both these cases you can access the route with commandline as only pointer events are disabled.

linkDisabled does not need to be in state. This opens up possibilities of changing state and forgetting to update linkDisabled. It is better to compute it in render.
class ShoppingCartLink extends React.Component {
constructor(){
super();
ShoppingCartStore.register(this.refresh.bind(this));
this.state = {count: 0};
}
refresh() {
this.setState({count: ShoppingCartStore.items.length});
}
render() {
const linkDisabled = this.state.count === 0;
const item = this.state.count === 1 ? "item" : "items";
return (
<div>
<button type="button" disabled={linkDisabled}>
<Link to="shoppingCart">Shopping Cart: {this.state.count} {item}</Link>
</button>
</div>
)
}
}
This way, if you ever add more to the component and have to setState in a different place, you will not need to worry about duplicating the logic for linkDisabled and item.

constructor(){
super();
ShoppingCartStore.register(this.refresh.bind(this));
this.state = {
count:0,
linkDisabled: true
};
}
handleClick(event) {
if(this.state.linkDisabled) event.preventDefault();
}
refresh(){
const length = ShoppingCartStore.items.length;
this.setState({
count: length,
linkDisabled: (length === 0)
});
}
render() {
const length = ShoppingCartStore.items.length;
return (<div>
<Link to="shoppingCart"
onClick={this.handleClick.bind(this)}>Shopping Cart: {this.state.count} {((length > 0)?"items":"item")}</Link>
</div>);
}
After some refactoring ... Try this ?

Related

Why setState() doesn't update render on React?

I am trying to make a 2D game-board that consists of "Cells" which has a binary state value called isAlive. The user can change this value by clicking on them. This value is indicated to the user using different colors. The Board.js constructor creates every cell with a false value.
The board also has a "Reset" button to reset all Cell's back to false.
The values of the Cells changes correctly when I click on them. When "Reset" button is clicked, I want every Cell's isAlive value to be false. However, when "Reset" button is pressed visually there is no change on the board (The Cell's colors doesn't change). The console.log(this.state.cells); line on handleClick() method on Board.js prints props: Object { isAlive: false } for all Cells. So why the Cell's are not updating visually? What am I doing wrong here?
Board.js
export default class Board extends React.Component {
constructor(props) {
super(props);
this.state = {
cells: []
};
this.rowNum = 10;
this.colNum = 10;
for (let i = 0; i < (this.rowNum * this.colNum); i++) {
this.state.cells.push(<Cell isAlive={false}/>);
}
this.handleClick = this.handleClick.bind(this);
this.getCols = this.getCols.bind(this);
}
getCols() {
let cols = [];
for (let i = this.rowNum; i <= (this.rowNum * this.colNum); i += this.rowNum) {
cols.push(this.state.cells.slice(i - this.rowNum, i).map(function (item, i) {
return <div key={i}>{item}</div>
}));
}
return cols;
}
handleClick() {
const newBoard = this.state.cells;
newBoard.forEach((item, index, arr) => {
arr[index] = <Cell isAlive={false}/>
});
this.setState({
cells: newBoard
}, function () {
console.log(this.state.cells);
}
);
}
render() {
return (<div className="background">
<table className="gameBoard">
<tbody>
<tr>
{this.getCols().map(function (item, index) {
return <td className="col" key={index}>{item}</td>
})}
</tr>
</tbody>
</table>
<button onClick={this.handleClick}> Reset</button>
</div>
);
}
}
The significant parts of the Cell.js:
export default class Cell extends React.Component {
constructor(props) {
super(props);
this.colors = {
dead: '#041b40',
alive: '#b2e8f7',
hover_dead: '#495d76',
hover_alive: '#e6fffd'
};
this.state = {
isAlive: this.props.isAlive,
isMouseDown: false,
isHovering: false
};
this.handleClick = this.handleClick.bind(this)
}
handleClick() {
this.setState({
isAlive: !this.state.isAlive,
isMouseDown: this.state.isMouseDown,
isHovering: this.state.isHovering
})
}
...
determineColor() {
if (this.state.isAlive && this.state.isHovering) {
return this.colors.hover_alive;
} else if (!this.state.isAlive && this.state.isHovering) {
return this.colors.hover_dead;
} else if (this.state.isAlive && !this.state.isHovering) {
return this.colors.alive;
}else {
return this.colors.dead;
}
}
render() {
return (
<div>
<button className='square'
onClick={this.handleClick}
type='button'
style={{
backgroundColor: this.determineColor()
}}>
</button>
</div>
)
};
}
You need to update the inner state on props change:
export default class Cell extends React.Component {
constructor(props) {
super(props);
this.colors = {
dead: '#041b40',
alive: '#b2e8f7',
hover_dead: '#495d76',
hover_alive: '#e6fffd'
};
this.state = {
isAlive: this.props.isAlive,
isMouseDown: false,
isHovering: false
};
this.handleClick = this.handleClick.bind(this)
}
componentDidUpdate(prevProps, prevState) { //<--HERE
this.setState({isAlive: this.props.isAlive})
}
handleClick() {
this.setState({
isAlive: !this.state.isAlive,
isMouseDown: this.state.isMouseDown,
isHovering: this.state.isHovering
})
}
...
determineColor() {
if (this.state.isAlive && this.state.isHovering) {
return this.colors.hover_alive;
} else if (!this.state.isAlive && this.state.isHovering) {
return this.colors.hover_dead;
} else if (this.state.isAlive && !this.state.isHovering) {
return this.colors.alive;
}else {
return this.colors.dead;
}
}
render() {
return (
<div>
<button className='square'
onClick={this.handleClick}
type='button'
style={{
backgroundColor: this.determineColor()
}}>
</button>
</div>
)
};
You must use the componentDidUpdate
componentDidUpdate(prevProps) {
if (this.props.isAlive!== prevProps.isAlive) {
this.setState({isAlive: this.props.isAlive})
}
}

How to update the value from todo list : when click on update button value should get edited on text field and should get updated to same index

React Todo list : I want to update the text when click on update button.when update button is clicked the value should get appeared on text field from where the text is added and again after editing the value,the value should get updated on same index after clicking on add button.
import React from 'react';
class Todo extends React.Component{
constructor(props){
super(props);
this.onChange = this.onChange.bind(this);
this.onSubmit = this.onSubmit.bind(this);
this.handleClick= this.handleClick.bind(this);
this.state={
todoList:[],
todoString:"",
a:''
}
};
onChange(event)
{
this.setState({todoString:event.target.value});
}
onSubmit()
{
let todoList = this.state.todoList;
let todoObject = {"todoText":this.state.todoString, "isDone": false, "isUpdated":false};
todoList.push(todoObject); // todo-object is push in todolist
this.setState({todoList:todoList,todoString:''});
}
handleClick(todoItem) {
todoItem.isDone = true;
this.setState({todoList:this.state.todoList});
}
updatedClick(value) {
this.setState({todoString: value.todoText});
}
render()
{
return(
<div>
<div>{
}
<input value={this.state.todoString} onChange={(e) => this.onChange(e)}/> <button onClick={(e) =>this.onSubmit(e)}> Add </button>
{
this.state.todoList.map((value, index) => {
return(
<div>
{value.isDone === true ? <span style={{'text-decoration':'line-through','padding':'10px'}}>{value.todoText}</span>: <span>{value.todoText}</span>}
<button onClick={()=>this.handleClick(value)}> Mark Done</button>
<button onClick={(e)=>this.updatedClick(value)}> update</button>
</div>
)
})
}
</div>
</div>
)
}
}
export default Todo;
we have to use one extra property in state for insert/update mode. on update click we will set index for element that is going to be updated and if index is set and not equal to -1 then we are in edit mode else we use insert mode.
class Todo extends React.Component{
constructor(props){
super(props);
this.onChange = this.onChange.bind(this);
this.onSubmit = this.onSubmit.bind(this);
this.handleClick= this.handleClick.bind(this);
this.state={
todoList:[],
todoString:"",
editIndex : -1 // init with insert mode.
}
};
onChange(event) { this.setState({todoString:event.target.value}); }
onSubmit() {
let todoList = this.state.todoList;
if(this.state.editIndex != -1) { // update mode
todoList[this.state.editIndex].todoText = this.state.todoString;
} else { // insert mode
let todoObject = {"todoText" : this.state.todoString, "isDone": false, "isUpdated":false};
todoList = [...todoList , todoObject];
}
this.setState({todoList:[...todoList] , todoString:'' , editIndex : -1 }); // reset insert mode
}
handleClick(todoItem) { // improved for toggling insted of only mark as done.
todoItem.isDone = !todoItem.isDone;
this.setState({todoList:[...this.state.todoList]});
}
updatedClick(value) { // set update mode
let i = this.state.todoList.findIndex(l => {return l.todoText == value.todoText});
this.setState({editIndex : i, todoString : value.todoText});
}
render()
{
return(
<div>
<div>
<input value={this.state.todoString} onChange={(e) => this.onChange(e)}/> <button onClick={(e) =>this.onSubmit(e)}> Add </button>
{
this.state.todoList.map((value, index) => {
return(
<div>
{
value.isDone === true ?
<span style={{'text-decoration':'line-through','padding':'10px'}}>{value.todoText}</span> :
<span>{value.todoText}</span> }
<button onClick={()=>this.handleClick(value)}> Toggle</button>
<button onClick={(e)=>this.updatedClick(value)}> update</button>
</div>
)
})
}
</div>
</div>
)
}
}
export default Todo;

React calling methods in different conditions

I'm beginner on react and i've written the code below:
class Note extends React.Component {
constructor(props) {
super(props);
this.state = {editing: false};
this.edit = this.edit.bind(this);
this.save = this.save.bind(this);
}
edit() {
// alert('edit');
this.setState({editing: !this.state.editing});
}
save() {
this.props.onChange(this.refs.newVal.value, this.props.id);
this.setState({editing: !this.state.editing});
// console.log('save is over');
}
renderForm() {
return (
<div className="note">
<textarea ref="newVal"></textarea>
<button onClick={this.save}>SAVE</button>
</div>
);
}
renderDisplay() {
return (
<div className="note">
<p>{this.props.children}</p>
<span>
<button onClick={this.edit}>EDIT</button>
<button onClick={this.remove}>X</button>
</span>
</div>
);
}
render() {
console.log(this.state.editing);
return (this.state.editing) ? this.renderForm()
: this.renderDisplay()
}
}
class Board extends React.Component {
constructor(props){
super(props);
this.state = {
notes: []
};
this.update = this.update.bind(this);
this.eachNote = this.eachNote.bind(this);
this.add = this.add.bind(this);
}
nextId() {
this.uniqeId = this.uniqeId || 0;
return this.uniqeId++;
}
add(text) {
let notes = [
...this.state.notes,
{
id: this.nextId(),
note: text
}
];
this.setState({notes});
}
update(newText, id) {
let notes = this.state.notes.map(
note => (note.id !== id) ?
note :
{
id: id,
note: newText
}
);
this.setState({notes})
}
eachNote(note) {
return (<Note key={note.id}
id={note.id}
onChange={this.update}>
{note.note}
</Note>)
}
render() {
return (<div className='board'>
{this.state.notes.map(this.eachNote)}
<button onClick={() => this.add()}>+</button>
</div>)
}
}
ReactDOM.render(<Board />,
document.getElementById('root'));
In render(), onClick event has a function, that is, if used in this way: {this.add} the following error is created:
Uncaught Error: Objects are not valid as a React child (found: object with keys {dispatchConfig, _targetInst, nativeEvent, type, target, currentTarget, eventPhase, bubbles, cancelable, timeStamp, defaultPrevented, isTrusted, view, detail, ...})
Why? while in the eachNote() method this command is used:
onChange={this.update}
And there was no error.
Someone can tell me the reason? thanks.
The problem is that in the add function you are taking an argument text and setting it in the state so when you call onClick={() => this.add()}, you are not passing any argument to add function and hence in its definition text is undefned and hence state note is set as undefined.
However if you directly call it like onClick={this.add} , the add function receives the event object as a parameter and hence it sets state note to be an event object which you are using to render
onClick={this.add} will pass the click event to this.add.
So what you need to do is either:
onClick={e => this.add('some text')} or similar.
If you want to onClick={this.add} you have to ensure that your add method is: add(event) { ... } instead.
The <Note /> component does not contain a render() method to return anything. Add a render() method and return something.
class Note extends React.Component {
constructor(props) {
super(props);
this.state = {editing: false};
this.edit = this.edit.bind(this);
}
edit() {
// alert('edit');
this.setState({editing: !this.state.editing});
}
render() {
return (
<div>Render something</div>
)
}
}
class Board extends React.Component {
constructor(props){
super(props);
this.state = {
notes: []
};
this.update = this.update.bind(this);
this.eachNote = this.eachNote.bind(this);
this.add = this.add.bind(this);
}
nextId() {
this.uniqeId = this.uniqeId || 0;
return this.uniqeId++;
}
add(text) {
let notes = [
...this.state.notes,
{
id: this.nextId(),
note: text
}
];
this.setState({notes});
}
update(newText, id) {
let notes = this.state.notes.map(
note => (note.id !== id) ?
note :
{
id: id,
note: newText
}
);
this.setState({notes})
}
eachNote(note) {
return (<Note key={note.id}
id={note.id}
onChange={this.update}>
{note.note}
</Note>)
}
render() {
return (<div className='board'>
{this.state.notes.map(this.eachNote)}
<button onClick={() => this.add()}>+</button>
</div>)
}
}
ReactDOM.render(<Board />,
document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.js"></script>
<div id="root"></div>

React remove element from onclick

When a user deletes an item from their cart, I have the item displayed with a button to add it back to the cart. This works. Once the user adds the item back to their cart, I want the item in the display component to be removed. Here is my code for reference.
CART:
class Cart extends Component {
constructor(props) {
super(props);
this.state = {differences: [],};
}
componentWillReceiveProps(nextProps){
let thisProps = this.props.cart.items;
let theNextProps = nextProps.cart.items;
if (thisProps.map(i => i.sku).some(item => !theNextProps.map(i => i.sku).includes(item))) {
let diff = [thisProps.filter(item => !theNextProps.includes(item))];
this.setState({differences: this.state.differences.concat(diff)});
}
}
...
render = () => {
<CartAddBack data={this.state.differences} onAddToCart={this.props.addToCart} />
<CheckoutSection className='Checkout-cart-items' titleKey='checkout.items.title'>
{this.props.cart.items.map((item) => {
return (
<CheckoutItem item={item} key={item.sku} onRemoveProduct={this.props.removeFromCart} onUpdateQuantity={this.props.updateCartItem}/>
);
})}
</CheckoutSection>
}
}
CartAddBack:
class CartAddBack extends Component {
constructor() {
super();
this.state = {deleted: null};
this.onDelete = this.onDelete.bind(this);
}
onDelete(id){
console.log("THE SKU SHOULD BE HERE", id);
this.setState(id);
}
render() {
let {data} = this.props;
let theData = data.map(i => parseInt(i[0].sku));
let theStated = this.state.deleted;
return (
<div>
{data &&
<div className="CartAddBack">
<div className="CartAddBack-Wrapper">
<ul className="CartAddBack-Item-ul">
{theStated != null
? theData.filter(i => !theStated.includes(i)) &&
<CartAddBackItem data={item[0]} onAddToCart={this.props.onAddToCart} onDelete={this.onDelete}/>
: data.map((item) => {
return <CartAddBackItem data={item[0]} onAddToCart={this.props.onAddToCart} onDelete={this.onDelete}/>
})
}
</ul>
</div>
</div>
}
</div>
)
}
}
CartAddBackItem:
class CartAddBackItem extends Component {
constructor() {
super();
this.onClick = this.onClick.bind(this);
}
onDelete(){
this.props.onDelete({deleted: this.props.data.sku})
}
allowSubmit() {
this.setState({
allowSubmit: true,
});
}
onClick() {
if (this.props.data) {
if (this.props.data.quantity <= this.props.data.inventory_quantity) {
const success = () => {
this.allowSubmit();
},
failure = (err) => {...};
this.props.onAddToCart({
...{sku: this.props.data.sku, quantity: this.props.data.quantity}, quantity: this.props.data.quantity}).then(success, failure);
}
else {
this.setState=({display: false});
const success = () => {
this.allowSubmit();
},
failure = (err) => {...};
this.props.onAddToCart({
...{sku: this.props.data.sku, quantity: this.props.data.quantity}, quantity: 1}).then(success, failure);
}
}
}
render() {
let {data} = this.props;
return (
<li className="CartAddBackItem">
{data &&
<div className="CartAddBackItem-Wrapper">
<Button className="CartAddBackItem-button" onClick={this.onClick}><FormattedMessage id="cart.cartAddBack"/></Button>
<Link to={`product/${data.sku}`} className="CartAddBackItem-Link">
<p className="CartAddBackItem-title">{data.title}</p>
</Link>
</div>
}
</li>
)
}
}
I want CartAddBack to remove CartAddBackItem if the item was clicked in CartAddBackItem. Only thing I havent tried that I just thought about was to make a componentWillReceiveProps inside CartAddBack. But there has to be a better way. Issue I'm running into is my mapping items into CartAddBackItem. The gross looking {theStated != Null ? theData.filter(i =>... allows me to add items to the cart. It works if it was only data.map((item)=>... but I want to show my thinking. Any advice?

Click handler for each button incorrectly returns the last button of a set

I am trying to add a click handler to each button that is generated in a loop and inserted into an array.
However, clicking a button always outputs the last button of each row of buttons and not the specific button itself.
My code is rather verbose, but we only need to be looking at the time.push() part and the click handler setup. Everything else is just setup.
import React from 'react';
import { friendlyTimeSlot, scopedTimeslots } from '../../utilities/helpers';
class TimeSlotStack extends React.Component {
constructor() {
super();
this.clickHandler = this.clickHandler.bind(this);
this.state = {
times: undefined
};
}
componentWillMount() {
this.updatePropsAndState(this.props);
}
componentWillReceiveProps(nextProps) {
this.updatePropsAndState(nextProps);
this.forceUpdate();
}
updatePropsAndState(props) {
const time = [];
let matchedTimeSlots;
if (props.promotionId) {
matchedTimeSlots = props.timeSlots.filter(timeSlot => {
const timeSlotsIds = timeSlot.AvailablePromotions.map(p => p.Id);
if (timeSlotsIds.includes(props.promotionId)) {
return timeSlot;
}
return false;
});
} else {
matchedTimeSlots = props.timeSlots.filter(timeSlot => timeSlot.HasStandardAvailability);
}
const scopedTimes = scopedTimeslots(matchedTimeSlots, props.preferredTimeSlot);
scopedTimes.forEach((item, i) => {
const friendlyTime = friendlyTimeSlot(item.TimeSlot, true);
const leaveTimeRequired = item.IsLeaveTimeRequired;
let itemPromo;
let leaveTime;
let itemPrice;
if (props.promotionId) {
itemPromo = item.AvailablePromotions.find(ourItem => ourItem.Id === props.promotionId);
leaveTime = itemPromo.LeaveTime || item.LeaveTime;
itemPrice = (itemPromo.BasePrice > 0) ? `£${itemPromo.BasePrice}` : '';
} else {
leaveTime = item.LeaveTime;
}
time.push(
<button
className="btn btn-default"
type="button"
onClick={(e) => this.clickHandler(e)}
ref={input => {
this.button = input;
}}
key={i}
data-time={friendlyTime}
data-leave-time-required={leaveTimeRequired}
data-leave-time={leaveTime.slice(0, -3)}
data-promotion-id={props.promotionId}
>
{friendlyTimeSlot(item.TimeSlot)}<br />{itemPrice}
</button>
);
});
this.setState({
times: time
});
}
clickHandler(e) {
e.preventDefault();
console.log(this.button.dataset);
}
render() {
if (this.state.times && this.props.name && this.props.description) {
return (
<div className="panel panel-default">
<div className="panel-heading">
<h3 className="panel-title">{this.props.name}</h3>
</div>
<div className="panel-body">
<p>{this.props.description}</p>
{this.state.times}
</div>
</div>
);
}
return (
<p>No times available.</p>
);
}
}
TimeSlotStack.propTypes = {
name: React.PropTypes.string.isRequired,
description: React.PropTypes.string.isRequired,
timeSlots: React.PropTypes.array.isRequired,
preferredTimeSlot: React.PropTypes.string.isRequired,
promotionId: React.PropTypes.number
};
export default TimeSlotStack;
When I then click a button, I always get the last button from each list. Hopefully the screenshot below will help make this clearer:
The log above comes from:
clickHandler(e) {
e.preventDefault();
console.log(this.button.dataset);
}
...but was generated by clicking the first buttons of each row. You can see that it always outputs the last only.
Is there something I'm doing wrong? This is my first React project and it's gotten me all flustered. Please let me know if I'm doing something that's not the React way that could be causing this.
Thanks!
You are overwriting the button variable, this in this context is a reference to a TimeSlotStack instance. To do what you want you need to maintain a list of buttons, for instance.
constructor() {
super();
this.clickHandler = this.clickHandler.bind(this);
this.buttons = [];
this.state = {
times: undefined
};
}
....
// using a IFE so `clickHandler` is called with the correct index
((idx) => {
time.push(
<button
className="btn btn-default"
type="button"
onClick={(e) => this.clickHandler(e, idx)}
ref={button => {
this.buttons.push(button);
}}
key={idx}
data-time={friendlyTime}
data-leave-time-required={leaveTimeRequired}
data-leave-time={leaveTime.slice(0, -3)}
data-promotion-id={props.promotionId}
>
{friendlyTimeSlot(item.TimeSlot)}<br />{itemPrice}
</button>
);
})(i);
....
clickHandler(e, i) {
e.preventDefault();
console.log(this.buttons[i].dataset);
}

Categories

Resources