Search code examples
javascriptreactjscruduse-effectuse-state

React: [Label]-[input]-[edit btn]-[save btn-(hidden)]


As the title states, Ive got a simple crud operation I could use some help with the useEffect/useState hook in how to implement.

form input/userdetails

I've got the input set to disabled by default, displaying the previous value via the placeholder. The state is then updated via a useState/useEffect hooks. Also, the second button (save) is set to hidden by default.

Goal:

  • essentially, just trying to setup an event listener for each edit-input button: hide the edit button, enable the input, unhide the save button.

  • with a separate (2nd) event listener on the save button: to hide the save button, unhide the edit button, return the input to disabled, turn the placeholder to the new value, and submit the value(I've got a good idea of the last part)

JSX:

<label className="col">
  <div className="row">
    <p className="bold nowrap inline-value-title" >Test Name:&nbsp; </p>
    <input 
      id="display-name-change"
      type="text"
      className="form-reset inline-input"
      onChange={(e) => setDisplayName(e.target.value)}
      value={displayName}
      placeholder={user.displayName}
      disabled
    />
    <button type="button" className="inline-button edit-val-btn">
      <FontAwesomeIcon icon={faPenToSquare}  />
    </button>
    <button type="button" className="hidden inline-button save-val-btn">.  
      <FontAwesomeIcon icon={faCircleCheck}  />
    </button>
  </div>
</label>

My Javascript: (as you can probably tell it's still very Vanilla and I think that's the problem)...

const editValueBtns = document.querySelectorAll('.edit-val-btn');
const saveValueBtns = document.querySelectorAll('.save-val-btn');

useEffect(() => {
    editValueBtns.forEach((button) => {
        button.addEventListener('click', (e) => {
            button.classList.add('hidden')
            button.nextSibling.classList.remove('hidden')
            button.parentElement.children[1].removeAttr("disabled") ;
        })
    })

    saveValueBtns.forEach((button) => {
        button.addEventListener('click', (e) => {
            button.classList.add('hidden')
            button.previousSibling.classList.remove('hidden')
            button.parentElement.children[1].addAttr("disabled") ;
        })
    })
}, []);

EDIT: Showing the inputs being submitted to Firebase/Firestore

const handleSubmit = async (e) => {
    e.preventDefault();
    let selectedFile = document.querySelector('#thumbnailInput')

    // displayname
    if(displayName.length == 0){console.log('No change of name')} 
    else { 
        console.log('change to displayname')
        updateProfile(user, { displayName })
        setDoc(doc(db, 'users', user.uid), { displayName }, { merge: true })

    }

    // phone Number
    if (phoneNo.length == 0){console.log('No change of phone no')}
    else {
        console.log('change to phone')
        updateProfile(user, { phoneNo })
        setDoc(doc(db, 'users', user.uid), { phoneNo }, { merge: true })
    }

    // title
    if (title.length == 0){console.log('No change of title')}
    else {
        console.log('change to title')
        updateProfile(user, { title })
        setDoc(doc(db, 'users', user.uid), { title }, { merge: true })
    }

    // avatar thumbnail
    if(selectedFile.files[0] == undefined){
        console.log('no change to thumbnail')
    } else {
        console.log('change to thumbnail')
        // pass the path in ref to create a StorageReference
        const storageRef = ref(storage,`thumbnails/${user.uid}/${displayName}`) //thumbnail.name

        // upload image, file is a blob here
        await uploadBytes(storageRef, thumbnail);

        const downloadUrl = await getDownloadURL(storageRef);

        // this function returns promise too, add await 
        await updateProfile(user, { photoURL: downloadUrl })

        updateProfile(user, { photoURL: downloadUrl})

        setDoc(doc(db, 'users', user.uid), {
            photoURL: downloadUrl,
        }, { merge: true })
    }

    // clear all form inputs
    const inputs = e.target.querySelectorAll('.form-reset')
    inputs.forEach((input) => {
        input.value=""
    })
}

Solution

  • I see your vanilla js way, and raise you the react way. In react, you shouldn't have to use document.querySelector, previousSibling, parentElement, classList.add, classList.remove, addAttr or button.addEventListener. See solution in CodeSandbox or below:

    App.jsx

    import { Row } from "./components/Row";
    import "./styles.css";
    
    export default function App() {
      return (
        <div className="App">
          <Row placeholder="input 1" />
          <Row placeholder="input 2" />
          <Row placeholder="input 3" />
        </div>
      );
    }
    

    Row.jsx

    import { useState } from "react";
    
    export const Row = ({ defaultValue, placeholder }) => {
      const [value, setValue] = useState(defaultValue);
      const [disabled, setDisabled] = useState(true);
    
      const handleEditClick = () => {
        setDisabled(false);
      };
    
      const handleSaveClick = () => {
        setDisabled(true);
        // save logic goes here
      };
    
      return (
        <label className="col">
          <div className="row">
            <p className="bold nowrap inline-value-title">Test Name:</p>
            <input
              type="text"
              className="form-reset inline-input"
              onChange={(e) => {
                setValue(e.target.value);
              }}
              value={value}
              placeholder={placeholder}
              disabled={disabled}
            />
            {disabled && (
              <button
                type="button"
                onClick={handleEditClick}
                className="inline-button edit-val-btn"
              >
                edit
              </button>
            )}
            {!disabled && (
              <button
                type="button"
                onClick={handleSaveClick}
                className="hidden inline-button save-val-btn"
              >
                save
              </button>
            )}
          </div>
        </label>
      );
    };