1

I have been stuck for a few hours now.. I am trying to send a component new props... but It wont get the new ones.

Here is what happens.

First the user clicks a button on the post component... It fires the function, 'add favorites' and 'addFavs' on the parent component below. These two functions determine if it should add the user to favorites or delete the user from favorites.

When the user is added as a favorite, the button they clicked, inside the searchResults component, appears a different color. The problem is they have to click the button twice for the new props to be sent back to the searchResults component and change the color - and I cant figure out how to get useEffect to send the component the new props.

Inside of the function 'addFavs', I call the origial search functions, 'getBands' and 'getTourBands' to fetch the updated data. This gets added to the states called bands, all bands, and tourBands. My theory was once this updated data gets added to the state, it would send the new props to my searchResultsComponent.

Thanks for your help - let me know if its too complicated.

    import React, { useState, useEffect } from 'react'
import { Button, Card, CardTitle } from 'reactstrap'
import LocationInput from './LocationInput'
import TypeInput from './TypeInput'
import VideosInput from './VideosInput'
import ShowsInput from './ShowsInput'
import VideoPosts from '../../SetupCards/Posts/Views/VideoPosts'
import ShowsPosts from '../../SetupCards/Posts/Views/ShowsPosts'
import FeedPosts from '../../SetupCards/Posts/Views/FeedPosts'
import { useAuth0 } from "../../../react-auth0-spa";
import BandCard from '../../BookABand/BandCard'
import SearchResults from './SearchResults'

let shouldUpdate = 0

export default function BandSearchBar(props) {
    const [ isSelected, setIsSelected ] = useState('location')
    const [ bands, setBands ] = useState([])
    const [ tourBands, setTourBands ] = useState([])
    const [ allBands, setAllBands ] = useState([])
    const [ locationText, setLocationText ] = useState('')
    const [ location, setLocation ] = useState([])
    const [ genre, setGenre ] = useState('Genre/Style')
    const [ bandTypes, setBandTypes ] = useState('all')
    const [ videosFilter, setVideosFilter ] = useState('all')
    const [ showsFiltered, setShowsFiltered ] = useState('all')
    const { getTokenSilently } = useAuth0();
    const { loading, user } = useAuth0();

    const getHomeBands = async () => {
        const token = await getTokenSilently();
        try {
            const response = await fetch(`/api/autoquotegenerators/homeBands/${location[0]}/${location[1]}`, {
                headers: {
                    Authorization: `Bearer ${token}`,
                }
            })
            const responseData = await response.json();
            if(responseData !== []){
                setBands(responseData)
            }
        } catch (error) {
            console.log(error)
        }
    }
    const getTourBands = async () => {
        const token = await getTokenSilently();
        try {
            const response = await fetch(`/api/autoquotegenerators/tourBands/${location[0]}/${location[1]}`, {
                headers: {
                    Authorization: `Bearer ${token}`,
                }
            })
            const responseData = await response.json();
            if(responseData !== []){
                setTourBands(responseData)
            }
        } catch (error) {
            console.log(error)
        }
        let allBandsArray = Array.from(bands).concat(tourBands)
            setAllBands(allBandsArray)
    }

    useEffect(() => {
        setTimeout(() => {
            if(shouldUpdate >= 1){
                getHomeBands()
                getTourBands()
            }else {
                shouldUpdate += 1
            }

        },250)
    }, [location])

    const searchLocation = (location, text) => {
        setLocation(location)
        setLocationText(text)
    }

    const showCard = (set) => {
        switch(set){
            case 'location':
                return <div><LocationInput savedLocationText={locationText} searchLocation={searchLocation} savedGenre={genre} filterByGenre={filterByGenre} /></div>
            case 'bands':
                return <div><TypeInput savedType={bandTypes} filterBandTypes={filterBandTypes} /> </div>
            case 'videos':
                return <div><VideosInput filterByVideos={filterByVideos} videosFilter={videosFilter} /> </div>
            case 'shows':
                return <div><ShowsInput filterShows={filterShows} showsFiltered={showsFiltered}/> </div>
        }
    }

    if (loading || !user) {
        return <div>Loading...</div>;
    }

    const addRockOn = async (postId, rocks, _id) => {
        const token = await getTokenSilently();
        try {
            await fetch(`/api/autoquotegenerators/posts/${_id}/${postId}`,{
                method: 'PUT',
                headers: {
                    Authorization: `Bearer ${token}`,
                    "Content-Type": "application/json; charset=UTF-8",
                },
                body: JSON.stringify({
                    rockOn: rocks
                })
            })
        } catch (error) {
            console.log(error)
        }
    }

    const addFavorites = (userId, band) => {

        if(band.favorites.includes(userId)){
            addFavs(band.favorites.filter(fav => {
                return fav !== userId
            }), band._id)

        }else {
            addFavs(band.favorites.concat(userId), band._id)
        }

    }

    const addFavs = async (favs, id) => {
        const token = await getTokenSilently();
            try{
                await fetch(`/api/autoquotegenerators/${id}`, {
                    method: 'PUT',
                    headers: {
                        Authorization: `Bearer ${token}`,
                        "Content-Type": "application/json; charset=UTF-8",
                    },
                    body: JSON.stringify({
                        favorites: favs
                    })
                })
                getHomeBands()
                getTourBands()
            } catch(error){
                console.log(error)
            }
    }

    const convertPost = (post, band) => {
        if(genre === 'Genre/Style'){
            switch (post.type) {
                case "video":
                    return (
                        <VideoPosts addFavorites={addFavorites} favorites={band.favorites} addRockOn={addRockOn} link={post.data} band={band} post={post} _id={band._id} />
                    )
                case "text":
                    return (
                        <FeedPosts addFavorites={addFavorites} favorites={band.favorites} addRockOn={addRockOn} band={band}  post={post} _id={band._id}/>
                    )   
                case "show":
                    return (
                        <ShowsPosts addFavorites={addFavorites} favorites={band.favorites} addRockOn={addRockOn} band={band} post={post} _id={band._id}/>
                    )
                default: 
                    return null;
            }
        }else {
            if(band.bandGenre === genre ){
                switch (post.type) {
                    case "video":
                        return (
                            <VideoPosts addFavorites={addFavorites} favorites={band.favorites} addRockOn={addRockOn} link={post.data} band={band} post={post} />
                        )
                    case "text":
                        return (
                            <FeedPosts addFavorites={addFavorites} favorites={band.favorites} addRockOn={addRockOn} band={band} post={post} _id={band._id} />
                        )   
                    case "show":
                        return (
                            <ShowsPosts addFavorites={addFavorites} favorites={band.favorites} addRockOn={addRockOn} band={band} post={post} _id={band._id}/>
                        )
                    default: 
                        return null;
                }
            }
        }

    }

    const convertBand = (band) => {
        if(genre === 'Genre/Style'){
            return <Button color="light" className="w-100 mb-1">
            <BandCard id={band._id} key={band.bandName} youtube={band.youtube} bandName={band.bandName} bandBio={band.bandBio} />
            </Button>
        }else {
            if(band.bandGenre === genre){
                return <Button color="light" className="w-100 mb-1">
                <BandCard id={band._id} key={band.bandName} youtube={band.youtube} bandName={band.bandName} bandBio={band.bandBio} />
                </Button> 
            }
        }
    }

    const createPromoVideo = (link, band) => {
        let post = {
            date: null
        }
        return <VideoPosts post={post} link={link} band={band} _id={band._id} />
    }

    const filterBandTypes = (type) => {
        setBandTypes(type)
    }

    const filterByGenre = (genre) => {
        setGenre(genre)
    }

    const filterByVideos = (videos) => {
        setVideosFilter(videos)
    }

    const filterShows = (shows) => {
        setShowsFiltered(shows)
    }

    return (
        <div className="d-flex flex-column">
            <div className="d-flex flex-row">
                <Button id="location" onClick={() => {setIsSelected('location')}} 
                color={isSelected === 'location' ? "dark active" : "dark"} className="w-100 h5" style={{borderTopLeftRadius: '3px', borderBottomLeftRadius: '3px', borderTopRightRadius: '0px', borderBottomRightRadius: '0px'}} >Feed</Button>
                <Button id="bands" onClick={() => {setIsSelected('bands')}} color={isSelected === 'bands' ? "dark active" : "dark"}  className="w-100 h5 rounded-0">Bands</Button>
                <Button id="videos" onClick={() => {setIsSelected('videos')}} 
                color={isSelected === 'videos' ? "dark active" : "dark"} className="w-100 h5 rounded-0">Videos</Button>
                <Button id="shows" onClick={() => {setIsSelected('shows')}}  color={isSelected === 'shows' ? "dark active" : "dark"} className="w-100 h5" style={{borderTopRightRadius: '3px', borderBottomRightRadius: '3px', borderTopLeftRadius: '0px', borderBottomLeftRadius: '0px'}}>Shows</Button>
            </div>
            <div>
                {isSelected ? showCard(isSelected) : null}
            </div>
            <SearchResults isSelected={isSelected} bandTypes={bandTypes} allBands={allBands} bands={bands} tourBands={tourBands} convertPost={convertPost} convertBand={convertBand} videosFilter={videosFilter} showsFiltered={showsFiltered} createPromoVideo={createPromoVideo}/>
        </div>
    )
}

I have also tried to use the useEffect() hook to call a function to display the component with new props. Still nothing. ** When I tried the use effect, I had it listen to 'bands', 'allBands', and 'tourBands'. If they changed it would pass the component to a function that would display it in the render - This didnt work so I didnt include it in my code above.

Here is the file/component SearchRestuls.js

        import React from 'react'

    export default function SearchResults(props) {
    const {isSelected, bandTypes, allBands, bands, tourBands, convertPost, convertBand, videosFilter, showsFiltered, createPromoVideo} = props

    return (
    <div>
    <div style={{
        display: isSelected === 'location' ? 'block' : 'none'
    }}>

        {bandTypes === 'all' ? allBands.map(band => {
            return band.posts.map(post => {
                let currPost = convertPost(post, band)
                return currPost
            })
        }).reverse() : null}

        {bandTypes === 'local' ? bands.map(band => {
            return band.posts.map(post => {
                let currPost = convertPost(post, band)
                return currPost
            })
        }).reverse() : null}

        {bandTypes === 'touring' ? tourBands.map(band => {
            return band.posts.map(post => {
                let currPost = convertPost(post, band)
                return currPost
            })
        }).reverse() : null}

    </div>
    <div style={{
        display: isSelected === 'bands' ? 'block' : 'none'
    }}>
        {bandTypes === 'all' ? allBands.map(band => {
            let currBand = convertBand(band)
            return currBand
        }) : null}
        {bandTypes === 'local' ? bands.map(band => {
            let currBand = convertBand(band)
            return currBand
        }) : null}
        {bandTypes === 'touring' ? tourBands.map(band => {
            let currBand = convertBand(band)
            return currBand
        }) : null}
    </div>
    <div style={{
        display: isSelected === 'videos' ? 'block' : 'none'
    }}>
        {bandTypes === 'all' && videosFilter === 'all' ? allBands.map((band) => {
            return band.posts.map(post => {
                if(post.type === 'video'){
                    let currBand = convertPost(post, band)
                    return currBand
                }
            })
        }) : null}
        {bandTypes === 'local' && videosFilter === 'all' ? bands.map((band) => {
            return band.posts.map(post => {
                if(post.type === 'video'){
                    let currBand = convertPost(post, band)
                    return currBand
                }
            })
        }) : null}
        {bandTypes === 'touring' && videosFilter === 'all' ? tourBands.map((band) => {
            return band.posts.map(post => {
                if(post.type === 'video'){
                    let currBand = convertPost(post, band)
                    return currBand
                }
            })
        }) : null}

        {bandTypes === 'all' && videosFilter === 'promo' ? allBands.map((band) => {
            return band.youtube.map(link => {
                let currVid = createPromoVideo(link, band)
                return currVid
            })
        }) : null}
    </div>
    <div style={{
        display: isSelected === 'shows' ? 'block' : 'none'
    }}>
        {bandTypes === 'all' && showsFiltered === 'all' ? allBands.map((band) => {
            return band.posts.map(post => {
                if(post.type === 'show'){
                    let currBand = convertPost(post, band)
                    return currBand
                }
            })
        }) : null}
        {bandTypes === 'local' && showsFiltered === 'all' ? bands.map((band) => {
            return band.posts.map(post => {
                if(post.type === 'show'){
                    let currBand = convertPost(post, band)
                    return currBand
                }
            })
        }) : null}
        {bandTypes === 'touring' && showsFiltered === 'all' ? tourBands.map((band) => {
            return band.posts.map(post => {
                if(post.type === 'show'){
                    let currBand = convertPost(post, band)
                    return currBand
                }
            })
        }) : null}

        {bandTypes === 'all' && showsFiltered === 'upcoming' ? allBands.map((band) => {
            return band.posts.map(post => {
                if(post.type === 'show'){
                    let performanceDateUnformatted;
                    performanceDateUnformatted = post.details.filter(detail => {
                        if(detail.title === 'Performance Date'){
                            return detail.detail
                        }
                    })[0].detail

                    var months = {
                        'Jan' : '01',
                        'Feb' : '02',
                        'Mar' : '03',
                        'Apr' : '04',
                        'May' : '05',
                        'Jun' : '06',
                        'Jul' : '07',
                        'Aug' : '08',
                        'Sep' : '09',
                        'Oct' : '10',
                        'Nov' : '11',
                        'Dec' : '12'
                    }

                    let year = performanceDateUnformatted.slice(11)
                    let day = performanceDateUnformatted.slice(8,10)
                    let month = months[performanceDateUnformatted.slice(4,7)]

                    let showDateFormatted = new Date(year, month - 1, day)

                    let today = new Date()

                    let todayPlusOneWeek = new Date(today.getUTCFullYear(), today.getUTCMonth(), today.getDate() + 7)

                    if(showDateFormatted > today && showDateFormatted < todayPlusOneWeek ){
                        let currBand = convertPost(post, band)
                        return currBand
                    }else {
                        return null
                    }
                }
            })
        }) : null}
    </div>
    </div>
    )
    }
Nick McLean
  • 601
  • 1
  • 9
  • 23
  • are the arguments of `convertPost` based on anything inside the `SearchResults` component? Passing the entire function generating the posts sounds like a likely cause to this problem. (it would be ideal if you posted the `SearchResults` code) – Adam Jeliński Apr 28 '20 at 20:59
  • I just added the SearchResults code above - SearchResults was originally all inside the render of BandSearchBar. I thought it might work, for some reason, if it was moved inside its own component but alas here I am haha – Nick McLean Apr 28 '20 at 21:02
  • Just found out - the first click updates the database, but doesnt pass the props. The second click updates the props but doesnt update the database. – Nick McLean Apr 28 '20 at 21:12
  • Did it work fine before you separated the component? – Adam Jeliński Apr 28 '20 at 21:13
  • It worked the same way before I seperated it. – Nick McLean Apr 28 '20 at 21:17
  • The three states that SearchResults use, band - allBands - and tourBands - they all get updated on the first Click as well. Just SearchResults doesnt re-render. – Nick McLean Apr 28 '20 at 21:20

2 Answers2

0

I'm not really familiar with async/await in react, but maybe there a problem with it ? Your code seems to be correct, but, for exemple, is this conditions really valid sometimes ?

const responseData = await response.json();
if (responseData !== []) {
  setTourBands(responseData);
}
Quentin Grisel
  • 4,794
  • 1
  • 10
  • 15
  • Hey! Thanks for the response - I should have been more specific. I tried useEffect listening to the states where I store the data - band, allBands, and tourBand. – Nick McLean Apr 28 '20 at 21:11
  • Yeah sorry i read your question again and edited my answer haha – Quentin Grisel Apr 28 '20 at 21:12
  • Hmm - I just removed that to try it and it doesnt affect the results. The reason I have that in there is that when the user types in their location the geocoder converts the city into an array of coordiantes to send to 'getHomeBands' and 'getTourBands' - Sometimes it would send back an empty array causing an error when the SearchResults component would try to map through the empty results. Either way - it does the same thing with or without now the if statement now. – Nick McLean Apr 28 '20 at 21:16
  • geocoder ? what is it haha I was talking about getHomeBands() method where you fetch data and then transform the response into json, but you are using it asynchronously so i was wondering if the .json() method wasn't too long and so the condition below resulted in false, and so skipping the setBands() call – Quentin Grisel Apr 28 '20 at 21:31
0

Your code base is super complex so it's hard to tell what exactly is going on, but as far as I can tell, your state is getting somewhat desynced, thus the values update later than you expect them.

I believe the issue is caused by the getHomeBands and getTourBands being separate functions and setAllBands in getTourBands relying on bands that are changed in getHomeBands. React doesn't promise the state will be up to date when you call multiple changes very quickly and here they might happen almost simultaneously. Furthermore you are doing network fetches, which might finish in a different order than you called them in, meaning that the new bands might not have even arrived yet.

To fix this, just merge the 2 functions and call all 3 setStates with data that is independent of the current state.

    const getBands = async () => {
        const token = await getTokenSilently();
        try {
            let response = await fetch(`/api/autoquotegenerators/homeBands/${location[0]}/${location[1]}`, {
                headers: {
                    Authorization: `Bearer ${token}`,
                }
            })
            const newBands = await response.json();
            if(newBands !== []){
                setBands(newBands)
            }

            response = await fetch(`/api/autoquotegenerators/tourBands/${location[0]}/${location[1]}`, {
                headers: {
                    Authorization: `Bearer ${token}`,
                }
            })
            const newTourBands = await response.json();
            if(newTourBands !== []){
                setTourBands(newTourBands)
            }

            if((newBands !== []) && (newTourBands !== [])){
                setAllBands([ ...newBands, ...tourBands])
            }
        } catch (error) {
            console.log(error)
        }
    }
Adam Jeliński
  • 1,708
  • 1
  • 10
  • 21
  • And today you taught me about Async functions! It works! I really thought it was an issue with updating the props - agh! Thanks again for your help! – Nick McLean Apr 28 '20 at 22:48
  • No problem! I'm happy you learned something. The asynchronous nature of state makes it quite easy to make mistakes. And once you add other async things to it, like networking, the order might get completely messed up, leading to weird behaviour like you got here. – Adam Jeliński Apr 28 '20 at 23:01
  • Hey man! Do you know how to preload background images in react? Is it the same as in HTML? https://stackoverflow.com/questions/61576115/background-image-flickering-when-change-react – Nick McLean May 03 '20 at 15:08
  • Hey again adam! Do you know about about regEx? https://stackoverflow.com/questions/61732102/reg-ex-works-differently-on-two-different-files-of-code?noredirect=1#comment109194107_61732102 – Nick McLean May 11 '20 at 14:44