简体   繁体   中英

Strikethrough a Paragraph in React.js via onClick?

I'm pretty much ready to rip my hair out. So my final project in my Javascript class is an experimental thing with learning React.js, where you do a basic todo list. I got all that done and working, and I can have it add things properly. But my final hurdle is making it so that onclicking the printed paragraph from the button will cause them to give the printed paragraphs the strikethrough property, which can be undone by clicking on it again.

I've looked up everywhere, I've tried other examples from here, and nothing I can think of gets the strikethrough to take place. I tried a basic Javascript function that would do what I wanted if this was an HTML/non-react file, but it breaks the react page when I try to plop it in. So I spent a stupidly long time on a tutorial trying to figure out what to do, and I maybe figured out the step in the right direction? But I still can't get anything to happen and I don't know how to establish an onclick to this.

import React from 'react';
import './App.css';

class App extends React.Component {
    setCurrentToDoItem = (toDoItem) => {
        console.log("toDoItem", toDoItem);

        this.setState({
            currentToDoItem: toDoItem
        });
    };

    saveToDoListItem = (toDoItem) => {
        this.setState({
            toDoList: [...this.state.toDoList,
                toDoItem]


        });

    };

    constructor(props) {
        super(props);

        this.state = {
            currentToDoItem: null,
            toDoList: [],
            strikeThrough: []
        };
    }
    render() {
        return (

            <div>
                <h1>To Do List</h1>
                <label>To Do Item: </label>
                <input
                    onChange={(event) => this.setCurrentToDoItem(event.target.value)}>
                </input>
                <button onClick={() => this.saveToDoListItem(this.state.currentToDoItem)}>
                    <p>Add Item</p>
                </button>
                <p>{this.state.currentToDoItem}</p>


                <div>
                    <p>To Do Items</p>
                    {
                        this.state.toDoList.map((item, index) => <p key={index}>{item} </p>)
                    }
                </div>
            </div>

        );
    }
}

export default App;

This is my App.js code. As you can see, everything else should work fine, but I have no clue how to add a strikethrough effect to what would result from the this.state.toDoList.map((item, index) => <p key={index}>{item} </p>) bit like I would with a function in normal javascript. How do I make the printed lines strikethrough via onclick, and then how do I undo that by clicking on it again? (I assume with a second onclick) I really just need to know how to get a working strikethrough with this, as everything else is pretty much working.

One of the most comfortable ways to do that is as advised in comments. A really quick way to implement this is to toggle class list. In the code bellow, I added a function crossLine which toggles class name "crossed-line" and adds event listener on mapped to-dos (in render function). Then in your App.css add a line

.crossed-line {
    text-decoration: line-through;
}

Here's your edited component code.

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

        this.state = {
            currentToDoItem: null,
            toDoList: [],
            strikeThrough: []
        };
    }
    setCurrentToDoItem = toDoItem => {
        this.setState({
            currentToDoItem: toDoItem
        });
    };

    saveToDoListItem = toDoItem => {
        this.setState({
            toDoList: [...this.state.toDoList, toDoItem]
        });
    };

    crossLine = event => {
        const element = event.target;
        element.classList.toggle("crossed-line");
    };

    render() {
        return (
            <div>
                <h1>To Do List</h1>
                <label>To Do Item: </label>
                <input
                    onChange={event =>
                        this.setCurrentToDoItem(event.target.value)
                    }
                />
                <button
                    onClick={() =>
                        this.saveToDoListItem(this.state.currentToDoItem)
                    }
                >
                    <p>Add Item</p>
                </button>
                <p>{this.state.currentToDoItem}</p>

                <div>
                    <p>To Do Items</p>
                    {this.state.toDoList.map((item, index) => {
                        return (
                            <p onClick={this.crossLine} key={index}>
                                {item}{" "}
                            </p>
                        );
                    })}
                </div>
            </div>
        );
    }
}

As commented, you will have to keep a handle click and add class to add strikethrough using CSS.

For this I have updated your JSX to:

<p onClick={ () => this.handleClick(index) } className={ item.isComplete ? 'completed' : '' } key={index}>{item.value} </p>

and the signature of toDoItem from string to an object:

{
    value: string;
    isComplete: boolean
}

and based on this flag, I'm adding class.

 class App extends React.Component { constructor(props) { super(props); this.state = { currentToDoItem: null, toDoList: [], strikeThrough: [] }; this.setCurrentToDoItem = this.setCurrentToDoItem.bind(this); this.saveToDoListItem = this.saveToDoListItem.bind(this); this.handleClick = this.handleClick.bind(this); } setCurrentToDoItem(toDoItem) { this.setState({ currentToDoItem: toDoItem }); } saveToDoListItem(toDoItem) { this.setState({ toDoList: [...this.state.toDoList, { value: toDoItem, isComplete: false }] }); } handleClick(index) { const { toDoList } = this.state; toDoList[index].isComplete = !toDoList[index].isComplete; this.setState({ toDoList }); } render() { return ( <div> <h1>To Do List</h1> <label>To Do Item: </label> <input onChange={(event) => this.setCurrentToDoItem(event.target.value)}> </input> <button onClick={() => this.saveToDoListItem(this.state.currentToDoItem)}> <p>Add Item</p> </button> <p>{this.state.currentToDoItem}</p> <div> <p>To Do Items</p> { this.state.toDoList.map((item, index) => <p onClick={ () => this.handleClick(index) } className={ item.isComplete ? 'completed' : '' } key={index}>{item.value} </p>) } </div> </div> ); } } ReactDOM.render( < App / > , document.querySelector("#app"))
 body { background: #20262E; padding: 20px; } #app { background: #fff; border-radius: 4px; padding: 20px; transition: all 0.2s; } .completed { text-decoration: line-through; }
 <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script> <div id="app"></div>

Check out this solution https://codesandbox.io/s/crazy-kare-go2vf

I have modified your code to achieve the required functionality. This code does exactly what you want.

Update : Created a TODO fiddler code using React Hooks for modern code approach.

const initialState = {
  items: [
    { text: "Learn JavaScript", done: false },
    { text: "Learn React", done: false },
    { text: "Play around in JSFiddle", done: true },
    { text: "Build something awesome", done: true }
  ]
};

function appReducer(state, action) {
  switch (action.type) {
    case 'ITEM_STATUS_CHANGE':{
       let affected = state.items.slice();
       affected[action.index].done = !affected[action.index].done;
       return Object.assign({}, state,  { items: affected });
    }

    case 'ADD_ITEM_TO_LIST':{
       let affected = state.items.slice();
       affected.push({ text: action.data, done : false})   
       return Object.assign({}, state,  { items: affected });
    }

    default:
      throw new Error();
  }
}

function TodoApp(props){
    const [state, dispatch] = React.useReducer(appReducer, initialState);

    return (
      <div>
        <h2>Todos: 
          <input type="text" id="todoTextItem"/>
          <button 
          onClick={()=>{
                dispatch({
                type: 'ADD_ITEM_TO_LIST',
                data: window.todoTextItem.value
              })
          }}
          >Add Item</button>
        </h2>
        <ol>
        {state.items.map((item, index) => (
          <li key={index}>
            <label>
              <input 
                type="checkbox"
                checked={item.done}
                onChange={()=>{
                    dispatch({
                    type: 'ITEM_STATUS_CHANGE',
                    index: index,
                  })
                }}
              /> 
              <span className={item.done ? "done" : ""}>{item.text}</span>
            </label>
          </li>
        ))}
        </ol>
      </div>
    );
}

ReactDOM.render(<TodoApp />, document.querySelector("#app"))

and in CSS

body {
  background: #20262E;
  padding: 20px;
  font-family: Helvetica;
}

#app {
  background: #fff;
  border-radius: 4px;
  padding: 20px;
  transition: all 0.2s;
}

li {
  margin: 8px 0;
}

h2 {
  font-weight: bold;
  margin-bottom: 15px;
}

.done {
  color: rgba(0, 0, 0, 0.3);
  text-decoration: line-through;
}

input {
  margin-right: 5px;
}

Explanation:

Basically i am creating a list with done boolean flag which is false by default, which helps to identify if the TODO items added to the list is finished or not using reducers . With that logic class .done is toggled. You can change the code according to your need by segregating TODO list from done list items while setting state

This is a Unit testable code by creating Jest snapshot. Never to manipulate DOM directly, which will defeat the purpose of React's snapshot testing.


Old fiddle code using Class Component .

Use this to compare and learn modern hooks concepts from class based.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM