skip to Main Content

State is not updating from within the child function after the first click in React Typescript.

App.tsx

const list: { name: string, age: number, username: string }[] = [
  {
    name: "Robert",
    age: 20,
    username: "rtucker"
  },
  {
    name: 'John',
    age: 30,
    username: 'jtest'
  },
  {
    name: "Alice",
    age: 24,
    username: 'acooper'
  },
]

function App() {
const [activeList, setActiveList] = useState(list);

 const didMount = useRef(false)

  useEffect(() => {
    setActiveList(list)
  }, [])

  useEffect(() => {
    // Return early, if this is the first render:
     if (!didMount.current) {
       didMount.current = true;
       return
     }
    // Paste code to be executed on subsequent renders:
    console.log(activeList)
  }, [activeList])

const SortList = (item: string): void => {
let sortedList = new Array<typeof list[0]>()

    //console.log(options)
    let index = options.findIndex(o => o === item)
    console.log(index, item)

    switch (item) {
      case "age":
        if (index === -1) {
          sortedList = list.sort((a, b) => {
            //console.log(a.name, b.name)
            if (a.age > b.age) return 1
            if (a.age < b.age) return -1
            return 0
          })
          options.push(item)
          console.log('ascending', item)
        } else {
          sortedList = list.sort((a, b) => {
            if (a.age < b.age) return 1
            if (a.age > b.age) return -1
            return 0
         })


          console.log('descending')
          console.log(index)
          options.splice(index, 1)
        }

        console.log(sortedList)
        break
     default:
       break
    }

  setActiveList(sortedList)
}
const element = document.getElementById('root')

  return (
    <><div id='menu-container'>
      <ul style={{ listStyle: 'none' }}>
        {activeList.map((i, index) => (
          <li key={index}><p>name: {i.name} <br />age: {i.age} <br /> username: {i.username}</p></li>))}
      </ul><ContextMenu title={'Sort'} items={items} onClick={SortList} element={element} />
    </div></>
  )
}

ContextMenu.tsx

interface IContextMenu {
    title: string
    items: Array<string>
    onClick: (item: string) => void
    element: HTMLElement | null
}

export default function ContextMenu({ title, items, onClick, element }: IContextMenu) {
    if (!element) return null
    const { anchorPoint, isOpen, setIsOpen } = useContextMenu(element);

    if (!isOpen) {
        return null;
    }

    return (
        <><ul
            className={styles.ContextMenu}
            style={{ top: anchorPoint.y, left: anchorPoint.x }}
        ><p className={styles.title}>{title}</p>
            {items.map((item) => (
                <li key={item} onClick={() => {

                    onClick(item)
                    setIsOpen(false)
                }}>{item}</li>
            ))}
        </ul></>
    );
}


useContextMenu.ts file

function useContextMenu (element: HTMLElement) {
  const [isOpen, setIsOpen] = useState(false)
  const [anchorPoint, setAnchorPoint] = useState<AnchorPoint>({ x: 0, y: 0 })
  //   const width = window.innerWidth
  const handleContextMenu = useCallback(
    (event: MouseEvent) => {
      event.preventDefault()

      setAnchorPoint({
        x: event.pageX,
        y: event.pageY
      })
      setIsOpen(true)
    },
    [setIsOpen, setAnchorPoint]
  )

  const handleClick = useCallback(() => {
    if (isOpen) {
      setIsOpen(false)
    }
  }, [isOpen])

  useEffect(() => {
    element.addEventListener('click', handleClick)
    element.addEventListener('contextmenu', handleContextMenu)

    return () => {
      element.removeEventListener('click', handleClick)
      element.removeEventListener('contextmenu', handleContextMenu)
    }
  })

  return {
    anchorPoint,
    isOpen,
    setIsOpen
  }
}

contextMenu.scss

.ContextMenu {
    position: fixed;
    background: #eee;
    border: 1px solid #ccc;
    border-radius: 0.3rem;
    top: 0;
    left: 0;
    max-width: 15rem;
    list-style: none;
    margin: 0;

    .title {
        border-bottom: 1px solid #ccc;
        text-align: center;
    }

    & li {
        margin: 0;
        padding: 0;
        padding-right: 0.25rem;
        font-size: 0.9em;
        cursor: pointer;
    }

    & li:hover,
    & li:focus {
        background-color: grey;
        color: white;
    }

    // & li:not(:last-child) {
    //     margin: 0 0 1rem;
    // }
}

What is supposed to happen is when the SortList function receives a string ‘age’ it creates a sorted array from the list variable on the first click in ascending order and descending array on the second click.

For some reason the first time it’s clicked to sort all is good and everything updates however that’s the last time it updates. It will not sort in reverse order as the ActiveList never updates after the first time.

3

Answers


  1. Chosen as BEST ANSWER

    I was ABLE TO FIND A SOLUTION!!!


  2. You are taking the initial state (list), mutate it by sorting it, and then set it back to the state.

    Since the current and previous state are the same array (just sorted), React doesn’t detect the change, and doesn’t re-render the component.

    Whenever you sort the list, clone the original array using spread, and then sort it:

    const SortList = (item: string): void => {
      const sortedList = [...list] // clone original array
    
      const index = options.findIndex(o => o === item)
    
      switch (item) {
        case "age":
          if (index === -1) {
            sortedList.sort((a, b) => { // sort the clone
              if (a.age > b.age) return 1
              if (a.age < b.age) return -1
              return 0
            })
    
            options.push(item)
          } else {
            sortedList.sort((a, b) => {  // sort the clone
              if (a.age < b.age) return 1
              if (a.age > b.age) return -1
              return 0
            })
    
            options.splice(index, 1)
          }
          break
        default:
          break
      }
    
      setActiveList(sortedList)
    }
    

    Right now the sorted list is always based on the initial value, so any change to list (adding / removing items for example), would reset when sorting. I would clone the previous state, and then sort it:

    const SortList = (item: string): void => {
      setActiveList(prevList => {
        const sortedList = [...prevList] // clone original array
    
        const index = options.findIndex(o => o === item)
    
        switch (item) {
          case "age":
            if (index === -1) {
              sortedList.sort((a, b) => { // sort the clone
                if (a.age > b.age) return 1
                if (a.age < b.age) return -1
                return 0
              })
              options.push(item)
            } else {
              sortedList.sort((a, b) => { // sort the clone
                if (a.age < b.age) return 1
                if (a.age > b.age) return -1
                return 0
              })
    
              options.splice(index, 1)
            }
            break
          default:
            break
        }
        
        return sortedList
      })  
    }
    

    Notes:

    You already use list as the initial state, so setting it in useEffect is redundant:

    useEffect(() => {
      setActiveList(list)
    }, [])
    

    You are also mutation the options array, and this might also affect rendering if options is a state. Theses statements mutate options:

    options.push(item)
    
    options.splice(index, 1)
    
    Login or Signup to reply.
  3. It looks like you’re setting the list items’ key prop as the array index, but the indices won’t change when the list is sorted. You should choose another attribute for each key (such as name) so that React can "see" the change in order:

    Keys tell React which array item each component corresponds to, so that it can match them up later. This becomes important if your array items can move (e.g. due to sorting), get inserted, or get deleted. A well-chosen key helps React infer what exactly has happened, and make the correct updates to the DOM tree.

    As the docs say: "Index as a key often leads to subtle and confusing bugs."

    https://react.dev/learn/rendering-lists#keeping-list-items-in-order-with-key

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