skip to Main Content

In the following code, when I click on the buttons, the buttonsColumns state updates as expected (as evidence by the the console.log in the function moveButton), and therefore, the buttons should move instantly to the right or left depending on whether I click them with the right or left mouse button. However, the displayed position on the page only updates when I click in the input field and enter something. I don’t understand why it behaves this way. Any idea to help me?

Also, I have planned two different behaviors depending on whether I click with the right or left mouse button. I manage to capture the normal (left) click (as evidenced by the console.log), but I can’t capture the right click. Could you tell me why and how to solve this?


const AssemblyLine = props => {
  
  let columns = {}
  for (let stage of props.stages) {
    columns[stage] = [] 
  }
  const [buttonsColumns, setButtonsColumns] = useState (columns)
  const [newInput, setNewInput] = useState("")
  
  const buttons = (stage) => {
    const allButtons = buttonsColumns[stage].map ((input) => { 
      return (
        <button
              data-testid="assembly-item"
              key = {input}
              onClick = {(e) => moveButton(e, stage, input)}
           >
              {input}
        </button>
      )
    })
    return allButtons
  }
  
  const stages = props.stages.map ((stage, index) => {
    return (
      <div
        data-testid="stage"
        key = {index} >
        <span> {stage} </span>
       {buttons(stage)}
      </div>
    )
  })

  function addNewButton () {
    let updatedObject = buttonsColumns
    updatedObject[props.stages[0]].unshift(newInput)
    setButtonsColumns (updatedObject)
    setNewInput("")
  }

  function moveButton (e, stage, input) {
    let updatedObject = buttonsColumns
    updatedObject[stage]= updatedObject[stage].filter( x => x !== input)
    if (e.button===0) {
      console.log("clic gauche")
      let positionForward = props.stages.indexOf(stage)+1
      if (positionForward<props.stages.length) {
        updatedObject[props.stages[positionForward]].unshift(input)
      }
    }
    if (e.button===2) {
      console.log("clic droit")
      let positionBackward = props.stages.indexOf(stage)-1
      if (positionBackward>0){
        updatedObject[props.stages[positionBackward]].push(input)
      }
    }
    setButtonsColumns (updatedObject)
    console.log("resultat", buttonsColumns)
  }


  
  return (
    <>
      <input
        name = "newButton"
        data-testid = "add-item"
        onKeyDown = {e => e.key === "Enter" ? addNewButton() : null}
        onChange = {e => setNewInput(e.target.value)}
        value = {newInput}
      />
      <div id="stagesContainer">
        {stages}
      </div>
    </>
  );
};

export default AssemblyLine;

And here is an exemple of the props.stages passed in app.js :

    "Idea", 
    "Development", 
    "Testing", 
    "Deployment"
  ]

I deleted the if condition but it didn’t work. I also tried to edit the columnsButtons structure but it didn’t help.

2

Answers


  1. Chosen as BEST ANSWER

    Thanks to BetterReact Developer who helped me with correcting a small mistake in the updatedObject definition and adding the onContextMenu property to the button component, here is the corrected code that works just perfectly fine :

        import React, {useState} from "react";
    
    const AssemblyLine = props => {
      
      let columns = {}
      for (let stage of props.stages) {
        columns[stage] = [] 
      }
      const [buttonsColumns, setButtonsColumns] = useState (columns)
      const [newInput, setNewInput] = useState("")
      
      const buttons = (stage) => {
        const allButtons = buttonsColumns[stage].map ((input, i) => { 
          return (
            <button
                  data-testid="assembly-item"
                  key = {i}
                  onClick = {() => moveButtonToLeft (stage, input, i)}
                  onContextMenu={(e)=> moveButtonToRight (e, stage, input, i)}
               >
                  {input}
            </button>
          )
        })
        return allButtons
      }
      
      const stages = props.stages.map ((stage, index) => {
        return (
          <div
            data-testid="stage"
            key = {index} >
            <span> {stage} </span>
           {buttons(stage)}
          </div>
        )
      })
    
      function addNewButton () {
        const updatedObject = {...buttonsColumns}
        updatedObject[props.stages[0]].unshift(newInput)
        setButtonsColumns (updatedObject)
        setNewInput("")
      }
    
      function moveButtonToLeft (stage, input, i) {
        const updatedObject = {...buttonsColumns}
        updatedObject[stage]= updatedObject[stage].filter((x,indexOfx) => (x !== input) || (indexOfx!==i)) // gestion des doublons
        let positionForward = props.stages.indexOf(stage)+1
        if (positionForward<props.stages.length) {
          updatedObject[props.stages[positionForward]].unshift(input)
        }
        setButtonsColumns (updatedObject)
      }
        
      function moveButtonToRight (e, stage, input, i) {
        e.preventDefault()
        const updatedObject = {...buttonsColumns}
        updatedObject[stage]= updatedObject[stage].filter((x,indexOfx) => (x !== input) || (indexOfx!==i)) // gestion des doublons
        let positionBackward = props.stages.indexOf(stage)-1
        if (positionBackward>=0) {
          updatedObject[props.stages[positionBackward]].push(input)
        }
        setButtonsColumns (updatedObject)
      }
    
    
      return (
        <>
          <input
            name = "newButton"
            data-testid = "add-item"
            onKeyDown = {e => e.key === "Enter" ? addNewButton() : null}
            onChange = {e => setNewInput(e.target.value)}
            value = {newInput}
          />
          <div id="stagesContainer">
            {stages}
          </div>
        </>
      );
    };
    
    export default AssemblyLine;
    

  2. It seems like the issue might be related to the way you are updating the state. In React, when you use the useState hook to manage state, you should avoid directly modifying the state variable. Instead, create a copy of the state and then update the copy. This ensures that React detects the state change properly.

    I have updated the moveButton and addNewButton functions to use the correct approach:

    import React, { useState } from 'react';
    
    const AssemblyLine = (props) => {
      let columns = {};
      for (let stage of props.stages) {
        columns[stage] = [];
      }
      const [buttonsColumns, setButtonsColumns] = useState(columns);
      const [newInput, setNewInput] = useState('');
    
      const buttons = (stage) => {
        const allButtons = buttonsColumns[stage].map((input) => {
          return (
            <button
              data-testid="assembly-item"
              key={input}
              onClick={(e) => moveButton(e, stage, input)}
              onContextMenu={(e) => handleContextMenu(e, stage, input)}
            >
              {input}
            </button>
          );
        });
        return allButtons;
      };
    
      const stages = props.stages.map((stage, index) => {
        return (
          <div data-testid="stage" key={index}>
            <span> {stage} </span>
            {buttons(stage)}
          </div>
        );
      });
    
      function addNewButton() {
        const updatedObject = { ...buttonsColumns };
        updatedObject[props.stages[0]].unshift(newInput);
        setButtonsColumns(updatedObject);
        setNewInput('');
      }
    
      function moveButton(e, stage, input) {
        e.preventDefault(); // Prevent the default behavior for left-click
        const updatedObject = { ...buttonsColumns };
        updatedObject[stage] = updatedObject[stage].filter((x) => x !== input);
    
        if (e.button === 0) {
          console.log('Left click');
          const positionForward = props.stages.indexOf(stage) + 1;
          if (positionForward < props.stages.length) {
            updatedObject[props.stages[positionForward]].unshift(input);
          }
        }
    
        setButtonsColumns(updatedObject);
        console.log('Result:', buttonsColumns);
      }
    
      function handleContextMenu(e, stage, input) {
        e.preventDefault(); // Prevent the default context menu for right-click
        console.log('Right click');
        const updatedObject = { ...buttonsColumns };
        const positionBackward = props.stages.indexOf(stage) - 1;
        if (positionBackward >= 0) {
          updatedObject[props.stages[positionBackward]].push(input);
        }
        setButtonsColumns(updatedObject);
        console.log('Result:', buttonsColumns);
      }
    
      return (
        <>
          <input
            name="newButton"
            data-testid="add-item"
            onKeyDown={(e) => (e.key === 'Enter' ? addNewButton() : null)}
            onChange={(e) => setNewInput(e.target.value)}
            value={newInput}
          />
          <div id="stagesContainer">{stages}</div>
        </>
      );
    };
    
    export default AssemblyLine;
    

    And also, I’ve added the onContextMenu event for the buttons to capture the right-click and prevent the default context menu. This allows you to handle the right-click behavior in the moveButton function.

    Hope this helps you out.

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