skip to Main Content

The code below is a small, very simplified snippet from a React messaging app

const { useState } = React;

const Chat = () => {
    const [myMessages, setMyMessages] = useState([]);
    const [message, setMessage] = useState("");
    const [myButtons, setMyButtons] = useState([{}]);

    const addNewMessage = () => {

        let buttonIndex = myMessages.length;
        setMyButtons(myButtons => [...myButtons, { value: "Copy message" }]);

        let newMessage =
            (<div>
                <div>{message}</div>
                <button className="copy-to-clipboard-button" onClick={() => { copyToClipboard(buttonIndex, message); }}>
                    {myButtons[buttonIndex].value}
                </button>
            </div>);

        setMyMessages([...myMessages, newMessage]);

    };


    const copyToClipboard = (i, text) => {
        alert("copied");
        navigator.clipboard.writeText(text);
        let allButtons = myButtons.slice();
        allButtons[i].value = "Copied!";
        setMyButtons([...allButtons]);
    };


    const handleChange = (event) => {
        setMessage(event.target.value);
    };


    return (

        <div>
            <div style={{ maxHeight: "80vh", minHeight: "90vh", maxWidth: "96vw" }}>
                <div>{myMessages.map(message => <div>{message}</div>)}
                    <input
                        type="text"
                        id="message"
                        name="message"
                        onChange={handleChange}
                        value={message}
                    />

                    <button onClick={addNewMessage}>Go!</button>
                </div>
            </div>

        </div>

    )
};

ReactDOM.createRoot(document.body).render(<Chat />);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>

This code is for demonstration purposes only in order to spotlight the problem I’m trying to solve. Basically, when a new message is added, a copy to clipboard button is added with it. The value of each these dynamically added buttons is set to {myButtons[buttonIndex].value} with the intention that I’m tying the button’s value to a state variable that can be updated and rendered. This value is set correctly when the component is first added.

When a ‘copy-to-clipboard-button’ is clicked, the copyToClipboard method is called and the copy functionality works correctly. The next step is to change the button value to "Copied!". Although the array is updated correctly, the value of the button is not updated. I suspect that the problem is that when the button is created, it sets the value as a static value and is not tied to that state variable going forward. My thinking is that there is a way to indicate that the button value should be tied to state ongoing, but I’m not able to find the right syntax. Perhaps there’s a better way to approach this altogether. Looking for some guidance. Thanks!

2

Answers


  1. const { useState } = React;
    
    const Message = ({ message, buttonIndex }) => {
      const [buttonValue, setButtonValue] = useState("Copy message");
    
      const copyToClipboard = (text) => {
        alert("copied");
        navigator.clipboard.writeText(text);
        setButtonValue("Copied!");
      };
    
      return (
        <div>
          <div>{message}</div>
          <button
            className="copy-to-clipboard-button"
            onClick={() => {
              copyToClipboard(message);
            }}
          >
            {buttonValue}
          </button>
        </div>
      );
    };
    
    const Chat = () => {
      const [myMessages, setMyMessages] = useState([]);
      const [message, setMessage] = useState("");
    
      const addNewMessage = () => {
        setMyMessages([...myMessages, message]);
      };
    
      const handleChange = (event) => {
        setMessage(event.target.value);
      };
    
      return (
        <div>
          <div style={{ maxHeight: "80vh", minHeight: "90vh", maxWidth: "96vw" }}>
            {myMessages.map((message, index) => (
              <Message key={index} message={message} buttonIndex={index} />
            ))}
            <input type="text" id="message" name="message" onChange={handleChange} value={message} />
            <button onClick={addNewMessage}>Go!</button>
          </div>
        </div>
      );
    };
    
    ReactDOM.createRoot(document.body).render(<Chat />);
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>

    if i got your mean correct , create a separate component for each message and its associated button. This way, each button’s value would be tied to its own state variable and can be updated independently

    Login or Signup to reply.
  2. You need a function to trigger references:

    If you use

      let newMessage =
                (<div>
                    <div>{message}</div>
                    <button className="copy-to-clipboard-button" onClick={() => { copyToClipboard(buttonIndex, message); }}>
                        {myButtons[buttonIndex].value}
                    </button>
                </div>);
    

    after compile:

      let newMessage = createElement('div', [
        createElement('div', message),
        createElement('button', {...}, [myButtons[buttonIndex].value])
      ])
    

    this leads to static compilation at creation time instead use function

     let newMessage =
                () => {
                // scope value
                let msg = message
                return (<div>
                    <div>{msg}</div>
                    <button className="copy-to-clipboard-button" onClick={() => { copyToClipboard(buttonIndex, msg); }}>
                        {myButtons[buttonIndex].value}
                    </button>
                </div>);
            }
    
    const { useState } = React;
    
    const Chat = () => {
        const [myMessages, setMyMessages] = useState([]);
        const [message, setMessage] = useState("");
        const [myButtons, setMyButtons] = useState([{}]);
    
        const addNewMessage = () => {
    
            let buttonIndex = myButtons.length;
            const currentButton = { value: "Copy message" }
            myButtons.push(currentButton)
            setMyButtons([...myButtons])
    
            // you need a function to trigger references
            let newMessage =
                (myButtons) => {
                // scope value
                let msg = message
                return (<div>
                    <div>{msg}</div>
                    <button className="copy-to-clipboard-button" onClick={() => { copyToClipboard(currentButton, msg); }}>
                        {currentButton.value}
                    </button>
                </div>);
            }
    
            setMyMessages([...myMessages, newMessage]);
    
        };
    
    
        const copyToClipboard = (currentButton, text) => {
            alert("copied "+ text);
            navigator.clipboard.writeText(text)
           currentButton.value = "Copied!";
            setMyButtons([...myButtons]);
        };
    
    
        const handleChange = (event) => {
            setMessage(event.target.value);
        };
    
    
        return (
    
            <div>
                <div style={{ maxHeight: "80vh", minHeight: "90vh", maxWidth: "96vw" }}>
                    <div>{myMessages.map(message => <div>{message(myButtons)}</div>)}
                        <input
                            type="text"
                            id="message"
                            name="message"
                            onChange={handleChange}
                            value={message}
                        />
    
                        <button onClick={addNewMessage}>Go!</button>
                    </div>
                </div>
    
            </div>
    
        )
    };
    
    ReactDOM.createRoot(document.body).render(<Chat />);
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>

    I fixed some bugs in your code or use diff to see details

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search