Draxy
Draxy

Reputation: 565

Referencing a react component from its class method

I have a dynamic todo list I would like to add a "highlight" feature to. Each list item renders with markup for the highlight that should show only for the list item clicked.

export class Todo extends Component {

constructor(props) {
  super(props);
  this.state = {input: '', todos: this.getOldTodo()};
  this.selectItem = this.selectItem.bind(this);
}

  //shortened

  selectItem(i) {
    this.setState({selected: i});
    if (this.state.selected == i) {
    // --- this is the code that needs to change the right list items child's class  
      ???.props.childen[2].className = "active";
    // ---
      console.log("true")
    }
    console.log(i);

  }

  render() {
            //markup also shortened 
            this.state.todos.map((todos, i) => {
              return (
                                                                   //What do I pass to the method here?
                <li key={todos.key} className="todo-li-item" onClick={this.selectItem.bind(this, i)}>
                  <span className="todo-item">{todos.text}</span>
                  <span onClick={this.deleteItem.bind(this, i)} className="delet-todo">&#10005;</span>

                  // --- This is the child that needs its class changed when it's parent is clicked 

                  <div id="todo-select" className={"hidden"}>
                    <span id="todo-select-top"></span>
                    <span id="todo-select-left"></span>
                  </div>  
                </li>
              );
            })
        </ul>
      </div>
    );
  }
}

This is painfully simple and yet so un-obvious as to what I use to do this in react, but hey I'm still learning. Thanks for your time.

Upvotes: 0

Views: 83

Answers (2)

FisNaN
FisNaN

Reputation: 2865

  • The list item can be a stateless component, so the onSelect and onDelete become callback functions.

  • Deleting item with index may get you in trouble, since React will not re-render the entire list every time.

  • I don't know what's inside getOldTodo, but custructor cannot wait. So it will be null initially, if it's an async function.

There is an implementation using ES6 syntax.
Each list item is stateless:

const ListItem = props => {
  const { todo, deleteItem, selectItem } = props;
  return (
    <li key={todo.key} className="todo-li-item" onClick={selectItem}>
      <span className="todo-item">{todos.text}</span>
      <span onClick={deleteItem} className="delet-todo">
        &#10005;
      </span>
      clicked
      <div id="todo-select" className={'hidden'}>
        <span id="todo-select-top" />
        <span id="todo-select-left" />
      </div>
    </li>
  );
};

All events are handled by a stateful component:

export class Todo extends Component {
  state = {
    input: '',
    todos: [],
  };

  async componentDidMount() {
    const todos = await this.getOldTodo();
    this.setState({ todos });
  }

  render() {
    return (
      <div>
        {this.state.todos.map(todo => (
          <ListItem
            todo={todo}
            key={todo.key}
            selectItem={() => {
              this.selectItem(todo);
            }}
            deleteItem={() => {
              this.deleteItem(todo);
            }}
          />
        ))}
      </div>
    );
  }

  selectItem = todo => {
    const idx = this.state.todos.findIndex(i => i.key === todo.key);
    const todos = this.state.todos.slice();
    const todo = { ...this.state.todos[idx] };
    // change
    todos[idx] = todo;
    this.setState({
      todos
    });
  }

  deleteItem = todo => {
    const idx = this.state.todos.findIndex(i => i.key === todo.key);
    const todos = this.state.todos.splice(idx, 1);
    this.setState({
      todos
    });
  }

  getOldTodo = async () => {
    //...
  }
}

Does this make sense to you?

Upvotes: 1

trueter
trueter

Reputation: 126

You've been quite close. Here's my implementation. Key takeaway: Don't mutate the state object.

selectItem(idx) {
  this.setState(state => {
    const todos = [
      state.todos.slice(0, idx),
      { ...state.todos[idx], selected: ! state.todos[idx].selected },
      state.todos.slice(idx + 1, state.todos.length),
    ]
    return {
      ...state,
      todos,
    }
  })
}

deleteItem(idx) {
  this.setState(state => {
    const todos = [...state.todos]
    todos.splice(idx, 1)
    return {
      ...state,
      todos,
    }
  })
}

render() {
  return (
    <div>
      <ul>
        {this.state.todos.map((todo, idx) => (
          <li
            key={todo.key}
            className={'todo-li-item'}
            onClick={this.selectItem.bind(this, idx)}
          >
            <span className="todo-item">{todo.text}</span>
            <span
              onClick={this.deleteItem.bind(this, idx)}
              className="delete-todo"
            >
              &#10005;
            </span>
            <div id="todo-select" className={todo.selected && 'active'}>
              <span id="todo-select-top" />
              <span id="todo-select-left" />
            </div>
          </li>
        ))}
      </ul>
    </div>
  )
}

Upvotes: 1

Related Questions