Empresas
Empleos
  • Sobre nosotros
  • Soluciones
    • Publicación de vacantes
      Publica tu vacante y recibe candidatos calificados en 48h.
    • Evaluación de candidatos
      500+ pruebas técnicas y psicológicas, más anti-fraude.
    • Headhunting
      Búsqueda ejecutiva a la medida de principio a fin.
    • Nómina + EOR
      Dispersión de nómina y EOR en más de 15 países de LATAM.
  • Precios
  • Empleos

0

182
Vistas
Why does calling one `setState` function update an entirely separate state value?

I have an issue where one setState function is updating two separate state values.

I am trying to make a small React sortable array. The program should behave like so:

  • fetches data on mount
  • stores that data in both unsortedData & displayedData state hooks
  • when a user clicks "toggle sorting" button the array in displayedData is sorted (using .sort())
  • on a second click of "toggle sorting" it should set displayedData to be the same value as unsortedData - restoring the original order

However, on first click of toggle sorting, it sorts both unsortedData & displayedData, which means I lose the original data order. I understand I could store their order, but I'm wanting to know why these state values appear to be coupled.

Stackblitz working code here.
GIF showing issue here

I cannot find where this is happening. I can't see any object/array referencing going on (I'm spreading for new objects/arrays).

Code here:

const Test = () => {
  const [unsortedData, setUnsortedData] = useState([])
  const [displayedData, setDisplayedData] = useState([])
  const [isSorted, setisSorted] = useState(false)

  const handleSorting = () => setisSorted(!isSorted)

  useEffect(() => {
    if (isSorted === true) setDisplayedData([...unsortedData.sort()]) // sort data
    if (isSorted === false) setDisplayedData([...unsortedData]) // restore original data order
  }, [isSorted, unsortedData])

  useEffect(() => {
    const mockData = [3, 9, 6]
    setUnsortedData([...mockData]) // store original data order in "unsortedData"
    setDisplayedData([...mockData])
  }, [])

  return (
    <div>
      {displayedData.map(item => item)}
      <br />
      <button onClick={() => handleSorting()}>Toggle sorting</button>
    </div>
  )
}
about 4 years ago · Juan Pablo Isaza
2 Respuestas
Responde la pregunta

0

The .sort function mutates the array, so when you do this:

setDisplayedData([...unsortedData.sort()])

You are mutating unsortedData, then making a copy of it afterwards. Since you mutated the original array, that change can display on the screen when the component rerenders.

So a minimum fix would be to copy first, and sort afterwards:

setDisplayedData([...unsortedData].sort())

Also, why do I need the useEffect? Why can't I just move the contents of the useEffect (that has the if statements in) into the handleSorting function?

Moving it into handleSorting should be possible, but actually i'd like to propose another option: have only two states, the unsorted data, and the boolean of whether to sort it. The displayed data is then a calculated value based on those two.

const [unsortedData, setUnsortedData] = useState([3, 9, 6]) // initialize with mock data
const [isSorted, setisSorted] = useState(false)

const displayedData = useMemo(() => {
  if (isSorted) {
    return [...unsortedData].sort();
  } else {
    return unsortedData
  }
}, [unsortedData, isSorted]);

const handleSorting = () => setisSorted(!isSorted)

// No use useEffect to sort the data
// Also no useEffect for the mock data, since i did that when initializing the state

The benefits of this approach are that

  1. You don't need to do a double render. The original version sets isSorted, renders, then sets displayed data, and renders again
  2. It's impossible to have mismatched states. For example, in between that first and second render, isSorted is true, and yet the displayed data isn't actually sorted yet. But even without the double render case, having multiple states requires you to be vigilant that every time you update unsortedData or isSorted, you also remember to update displayedData. That just happens automatically if it's a calculated value instead of an independent state.
about 4 years ago · Juan Pablo Isaza Denunciar

0

If you want to display sorted array to user only when the user presses the button, you could use this code statement (orignaly coppied from vue3 todo list)

const filters = {
  none: (data) => [...data],
  sorted: (data) => [...data].sort((a, b) => a - b),
}

const Test = () => {
  const [unsortedData, setUnsortedData] = useState([])
  const [currentFilter, setCurrentFilter] = useState('none')

  useEffect(() => {
    const mockData = [3, 9, 6]
    setUnsortedData([...mockData])
  }, [])

  return (
    <div>
      <ul>
         {/* All magic is going here! */}
         {filters[currentFilter](unsortedData).map(item => <li key={item}>{item}</li>)}
      </ul>
      <br />
      <button
        onClick={() =>
            setCurrentFilter(currentFilter === 'none' ? 'sorted' : 'none')}>
          Toggle sorting
       </button>
    </div>
  )
}
about 4 years ago · Juan Pablo Isaza Denunciar
Responde la pregunta
Encuentra empleos remotos

¡Descubre la nueva forma de encontrar empleo!

Top de empleos
Top categorías de empleo
Empresas
Publicar vacante Precios Comercial
Legal
Términos y condiciones Política de privacidad
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Recomiéndame algunas ofertas
Necesito ayuda