Clicking a left arrow or a right arrow to switch an element in an array with the element on the left or right React

Viewed 527

I am new to React and am trying to build an app in which a user can create a card, delete a card, and change the order of the cards array by clicking left or right arrow to switch elements with the element on the left or on the right. I am struggling to code this functionaliy. I have the function written to switch the card with that on the left, but this function is not doing anything right now. I also do not get any errors in the console from this function, so I really cannot determine where I am going wrong here. Here is the code so far:

CardList.js will display the form to add a card and display the array of CardItems, passing the functions to switch these items to the left or right ('moveLeft', 'moveRight') as props.

import React from "react";
import CardItem from "./CardItem";
import CardForm from "./CardForm";
import './Card.css';

class CardList extends React.Component {
  state = {
    cards: JSON.parse(localStorage.getItem(`cards`)) || []
    // when the component mounts, read from localStorage and set/initialize the state
  };

  componentDidUpdate(prevProps, prevState) { // persist state changes to longer term storage when it's updated
      localStorage.setItem(
        `cards`,
        JSON.stringify(this.state.cards)
      );
  }

  render() {
    const cards = this.getCards();
    const cardNodes = (
      <div style={{ display: 'flex' }}>{cards}</div>
    );

    return (
     <div>
     <CardForm addCard={this.addCard.bind(this)} /> 
     <div className="container">
     <div className="card-collection">
          {cardNodes} 
          </div>
      </div>
      </div>
    );
  }

  addCard(name) {
    const card = {
      name
    };
    this.setState({
      cards: this.state.cards.concat([card])
    }); // new array references help React stay fast, so concat works better than push here.
  }
  
  
  removeCard(index) {
    this.state.cards.splice(index, 1)
    this.setState({
      cards: this.state.cards.filter(i => i !== index)
    })
  }

  moveLeft(index,card) {
   if (index > 1) {
     this.state.cards.splice(index, 1);
     this.state.cards.splice((index !== 0) ? index - 1 : this.state.cards.length, 0, card)
   }
    return this.state.cards
 }

  moveRight(index, card) {
   // ?
  }

  getCards() {
    return this.state.cards.map((card) => {
      return (
        <CardItem
          card={card}
          index={card.index}
          name={card.name}
          removeCard={this.removeCard.bind(this)}
          moveLeft={this.moveLeft.bind(this)}
          moveRight={this.moveRight.bind(this)}
        />
      );
    });
  }
}
export default CardList;

CardItem is taking in those props and ideally handling moving the card left or right in the array once the left or right icon is clicked.

import React from 'react';

import Card from "react-bootstrap/Card";
import Button from "react-bootstrap/Button";

class CardItem extends React.Component {
 render() {
  return (
  <div>
 <Card style={{ width: '15rem'}}>
  <Card.Header as="h5">{this.props.name}</Card.Header>
  <Card.Body>
    <Button variant="primary" onClick={this.handleClick.bind(this)}>Remove</Button>
  </Card.Body>
  <Card.Footer style={{ display: 'flex' }}>
  <i class="arrow left icon" onClick={this.leftClick.bind(this)} style={{ color: 'blue'}}></i>
  <i class="arrow right icon" onClick={this.rightClick.bind(this)} style={{ color: 'blue'}}></i>
  </Card.Footer>
</Card>
 </div>
    )
  }

handleClick(index) { 
  this.props.removeCard(index)
}

leftClick(index, card) {
this.props.moveLeft(index, card)
}

rightClick(index, card) {
  this.props.moveRight(index, card)
}

}
export default CardItem;

Not sure where I am going wrong here. Any help would be appreciated

Edit #1 Hey guys, so I wrote out a different function to handle moving the card to the left, and I decided to bind "this" to that method in the constructor because I was getting errors saying the program could not read it. However, I am still getting errors basically saying that everything is not defined when I pass the function from CardList to CardItem as props. Does anybody know what the problem is? I suspect its my syntax when I call the methods in CardItem.

CardList.js

import React from "react";
import CardItem from "./CardItem";
import CardForm from "./CardForm";
import './Card.css';

class CardList extends React.Component {
  constructor(props) {
    super();

    this.moveLeft = this.moveLeft.bind(this);
    this.moveRight = this.moveRight.bind(this);
    this.state = {
      cards: JSON.parse(localStorage.getItem(`cards`)) || []
      // when the component mounts, read from localStorage and set/initialize the state
    };
  }
  
  

  componentDidUpdate(prevProps, prevState) { // persist state changes to longer term storage when it's updated
      localStorage.setItem(
        `cards`,
        JSON.stringify(this.state.cards)
      );
  }

  render() {
    const cards = this.getCards();
    const cardNodes = (
      <div style={{ display: 'flex' }}>{cards}</div>
    );

    return (
     <div>
     <CardForm addCard={this.addCard.bind(this)} /> 
     <div className="container">
     <div className="card-collection">
          {cardNodes} 
          </div>
      </div>
      </div>
    );
  }

  addCard(name) {
    const card = {
      name
    };
    this.setState({
      cards: this.state.cards.concat([card])
    }); // new array references help React stay fast, so concat works better than push here.
  }
  
  
  removeCard(index) {
    this.state.cards.splice(index, 1)
    this.setState({
      cards: this.state.cards.filter(i => i !== index)
    })
  }

  moveLeft(index, card) {
    this.setState((prevState, prevProps) => {
        return {cards: prevState.cards.map(( c, i)=> {
            // also handle case when index == 0
            if (i === index) {
                return prevState.cards[index - 1];
            } else if (i === index - 1) {
                return prevState.cards[index];
            }    
        })};
    }); 
 }

  moveRight(index, card) {
   // ?
  }

  getCards() {
    return this.state.cards.map((card) => {
      return (
        <CardItem
          card={card}
          index={card.index}
          name={card.name}
          removeCard={this.removeCard.bind(this)}
          moveLeft={this.moveLeft}
          moveRight={this.moveRight}
        />
      );
    });
  }
}
export default CardList;

CardItem.js

import React from 'react';

import Card from "react-bootstrap/Card";
import Button from "react-bootstrap/Button";

class CardItem extends React.Component {
 render() {
  return (
  <div>
 <Card style={{ width: '15rem'}}>
  <Card.Header as="h5">{this.props.name}</Card.Header>
  <Card.Body>
    <Button variant="primary" onClick={this.handleClick.bind(this)}>Remove</Button>
  </Card.Body>
  <Card.Footer style={{ display: 'flex' }}>
  <i class="arrow left icon" onClick={leftClick(index, card)} style={{ color: 'blue'}}></i>
  <i class="arrow right icon" onClick={rightClick(index, card)} style={{ color: 'blue'}}></i>
  </Card.Footer>
</Card>
 </div>
    )
  }

handleClick(index) { 
  this.props.removeCard(index)
}

leftClick(index, card) {
this.props.moveLeft(index,card)
}

rightClick(index, card) {
  this.props.moveRight(index, card)
}

}
export default CardItem;
1 Answers

To update state arrays in React, you shouldn't use splice, push or the [] operator.

Instead use the methods that return a new array object viz. map, filter, concat,slice.

For a detailed explanation, see this article.

So you can do something like :

moveLeft(index,card) {
    this.setState((prevState, prevProps)=> {
        return {cards: prevState.cards.map((c,i)=> {
            // also handle case when index == 0
            if(i == index) {
                return prevState.cards[index-1];
            } else if(i == index-1) {
                return prevState.cards[index];
            }    
        })};
    }); 
 }

When updating React state using the previous value, always use

setState((prevState,prevProps)=>{ return ...})

as such state updates may be asynchronous. See React docs.

Since you are calling the parent component method from child, it's better to bind these methods in the CardList constructor. Eg:

this.moveLeft = this.moveLeft.bind(this);
this.moveRight ....
Related