5ecured
5ecured

Reputation: 201

(React) Selecing different dropdown values does not change anything

I have a dropdown menu, and selecting different values does not change anything. Previously, it did change just fine. Here is my code:

context.js:

import React, { useState, useEffect } from 'react'
import items from './data'

const RoomContext = React.createContext()

const RoomProvider = ({ children }) => {
    const [state, setState] = useState({
        rooms: [],
        sortedRooms: [],
        featuredRooms: [],
        loading: true,
        type: 'all',
        capacity: 1,
        price: 0,
        minPrice: 0,
        maxPrice: 0,
        minSize: 0,
        maxSize: 0,
        breakfast: false,
        pets: false
    })

    const formatData = items => {
        let tempItems = items.map(item => {
            let id = item.sys.id
            let images = item.fields.images.map(image => image.fields.file.url)
            let room = { ...item.fields, images, id }
            return room
        })
        return tempItems
    }

    const getRoom = slug => {
        let tempRooms = [...state.rooms]
        const room = tempRooms.find(room => room.slug === slug)
        return room
    }

    useEffect(() => {
        let rooms = formatData(items)
        let featuredRoomsTemp = state.rooms.filter(room => room.featured)
        let maxPrice = Math.max(...state.rooms.map(item => item.price))
        let maxSize = Math.max(...state.rooms.map(item => item.size))

        setState({
            ...state,
            rooms,
            featuredRooms: featuredRoomsTemp,
            sortedRooms: rooms,
            loading: false,
            price: maxPrice,
            maxPrice,
            maxSize
        })
    }, [])

      const handleChange = e => {
        const target = e.target
        const value = e.type === 'checkbox' ? target.checked : target.value
        const name = e.target.name

        setState({
            ...state,
            [name]: value 
        })
        filterRooms()
    }

    const filterRooms = () => {
        console.log('reached')
        let { rooms, type, capacity, price, minSize, maxSize, breakfast, pets } = state

        let tempRooms = [...rooms]
        if (type !== 'all') {
            // console.log('NOT ALL')
            tempRooms = tempRooms.filter(room => room.type === type)
        }   
        console.log('temprooms:' ,tempRooms)

        //ONLY AFTER ADDING THE BELOW SETSTATE,  AM I UNABLE TO CHANGE THE DROPDOWN VALUES. IF I REMOVE THE BELOW, I CAN CHANGE IT JUST FINE. Why would a setState make me unable to change the values?
        setState({
            ...state,
            sortedRooms: tempRooms
        })
    }

    return (
        <RoomContext.Provider value={{
            ...state,
            getRoom,
            handleChange
        }}>
            {children}
        </RoomContext.Provider>
    )
}

const RoomConsumer = RoomContext.Consumer

export function withRoomConsumer(Component) {
    return function ConsumerWrapper(props) {
        return <RoomConsumer>
            {value => <Component {...props} context={value} />}
        </RoomConsumer>
    }
}

export { RoomProvider, RoomConsumer, RoomContext }


RoomsFilter.js

import React, { useContext } from 'react'
import { RoomContext } from '../context'
import Title from './Title'

const getUnique = (items, value) => {
    return [...new Set(items.map(item => item[value]))]
}

const RoomsFilter = ({ rooms }) => {
    const context = useContext(RoomContext)

    const { handleChange, type, capacity, price, minPrice, maxPrice, minSize, maxSize, breakfast, pets } = context

    let types = getUnique(rooms, 'type')
    types = ['all', ...types]

    types = types.map((item, i) => {
        return (
            <option value={item} key={i}>{item}</option>
        )
    })

    return (
        <section className='filter-container'>
            <Title title='Search rooms' />
            <form className='filter-form'>
                {/* select type */}
                <div className='form-group'>
                    <label htmlFor='type'>Room type</label>
                    <select name='type' id='type' value={type} className='form-control' onChange={handleChange}>
                        {types}
                    </select>
                </div>
                {/* end select type */}
            </form>
        </section>
    )
}

export default RoomsFilter

==================================================================

As you can see, it is only when I added

setState({
            ...state,
            sortedRooms: tempRooms
        })

in filterRooms (in context.js) that I am no longer able to change the dropdown value. Why would adding setState do this?

Please help, and thank you for reading!

===============UPDATE=====================

  1. RoomsContainer is the one that contains the Dropdown menu(RoomsFilter) and the list of rooms below that dropdown menu(RoomsList).
  2. RoomsFilter is the dropdown menu component
  3. RoomsList is the component whose job is to render the rooms

Rooms Container:

import React from 'react'
import RoomsFilter from './RoomsFilter'
import RoomsList from './RoomsList'
import { withRoomConsumer } from '../context'
import Loading from './Loading'

function RoomContainer({ context }) {
const { loading, sortedRooms, rooms } = context
console.log(context)
if (loading) {
return <Loading />
}

return (
<div>
<RoomsFilter rooms={rooms} />
<RoomsList rooms={sortedRooms} />
</div>
)
}

export default withRoomConsumer(RoomContainer)

RoomsFilter:

import React, { useContext } from 'react'
import { RoomContext } from '../context'
import Title from './Title'

const getUnique = (items, value) => {
return [...new Set(items.map(item => item[value]))]
}

const RoomsFilter = ({ rooms }) => {
const context = useContext(RoomContext)

const { handleChange, type, capacity, price, minPrice, maxPrice, minSize, maxSize, breakfast, pets } = context

let types = getUnique(rooms, 'type')
types = ['all', ...types]

types = types.map((item, i) => {
return (
<option value={item} key={i}>{item}</option>
)
})

return (
<section className='filter-container'>
<Title title='Search rooms' />
<form className='filter-form'>
{/* select type */}
<div className='form-group'>
<label htmlFor='type'>Room type</label>
<select name='type' id='type' value={type} className='form-control' onChange={handleChange}>
{types}
</select>
</div>
{/* end select type */}
</form>
</section>
)
}

export default RoomsFilter

RoomsList

import React from 'react'
import Room from './Room'

const RoomsList = ({ rooms }) => {
console.log(rooms)
if (rooms.length === 0) {
return (
<div className='empty-search'>
<h3>Unfortunately no rooms match your search criteria</h3>
</div>
)
}

return (
<section className='roomslist'>
<div className='roomslist-center'>
{rooms.map(item => {
return <Room key={item.id} room={item} />
})}
</div>
</section>
)
}

export default RoomsList

Upvotes: 0

Views: 649

Answers (2)

Rami GB
Rami GB

Reputation: 819

You are not using the setState correctly .. it should be used like this instead

setState(prevState => ({...prevState , sortedRooms: tempRooms}))

Upvotes: 0

Jacob
Jacob

Reputation: 78890

The problem is that you have two different setState calls; one is in your handleChange function, and the other is inside of filterRooms. Both of those functions on a given change are using the previous state, but after the first:

setState({
  ...state,
  [name]: value 
})

...state is still going to have that same original value when you call:

setState({
  ...state,
  sortedRooms: tempRooms
})

...so the second update is going to overwrite what the first setState did. As was already pointed out, you can use the callback version of setState for the second call so you're using the new previous state on that second call:

setState(state => ({
  ...state,
  sortedRooms: tempRooms
}));

...but a better approach is to only call setState once on change instead of confusingly making two state updates. Instead of filterRooms calling setState, have it return the filtered Array, and do your update all in one call. Here's what filterRooms could do instead:

const getFilteredRooms = (type) => {
  let { rooms, capacity, price, minSize, maxSize, breakfast, pets } = state
  return type === 'all' ? rooms : rooms.filter(room => room.type === type)
}

...and change your handleChange to:

const handleChange = e => {
  const target = e.target
  const value = e.type === 'checkbox' ? target.checked : target.value
  const name = e.target.name

  setState({
    ...state,
    [name]: value,
    sortedRooms: getFilteredRooms(value)
  })
}

Upvotes: 1

Related Questions