0

I'm relatively new to React and trying to render an array of JSX fragments that all look like this.

                       <>
                        <div className="tourCard" key={address.name}>
                          <h3 className="tourCard__header">{address.name}</h3>
                          <div className="tourCard__capacity">Capacity: {foundUser.capacity}</div>
                          {foundUserAddress}
                          {foundUserAddress2}
                          <section className="tourCard__cityStateZip">
                            <div className="tourCard__city">{foundUser.city}</div>
                            <div className="tourCard__state">{foundUser.state}</div>
                            {foundUserZip}
                          </section>
                          <h5 className="tourCard__blurbHeader">About Us</h5>
                          {foundUserBlurb}
                          {socialMediaButtonClicked ? (
                            <>
                            {foundUserSocialMedia}
                            </>
                          ) : (
                            <>
                            <button onClick={() => {
                              socialMediaButtonClicked = true
                            }}>Social media</button>
                            </>
                          )}
                        </div>
                    </>

I'm pushing them into an array exactly as above, and I'm struggling with the right format in the return statement below to get them to render.

I've tried

return (
      <>
        <section className="tourSection">
          {tourCards}
        </section>
        </>
    )

and

return (
      <>
        <section className="tourSection">
          {tourcards.map(tourCard => {
            return(
              {tourCard}
            )
          }
        </section>
        </>
    )

But neither were successful. Where am I going wrong?

Complete page code below:

import React, {useContext, useState} from "react"
import { withGoogleMap, GoogleMap, Marker, InfoWindow } from 'react-google-maps'
import { AddressContext } from "../addresses/AddressProvider"
import { UserContext } from "../user/UserProvider"
import { render } from '@testing-library/react'

export default (props) => {

  const { addresses } = useContext(AddressContext)
  const { users } = useContext(UserContext)

  let tourCards = []

  const PlanMap = withGoogleMap(props => (
    <GoogleMap google={window.google} defaultCenter = { { lat: 39.5, lng:  -98.35 } }
    defaultZoom = { 4 }>
          {
            addresses.map(address => 
              <>
              <Marker
                key={address.id}
                position={{
                  lat: address.address.lat,
                  lng: address.address.lng
                }}
                label={{
                  text: "Hello World!",
                  fontFamily: "Arial",
                  fontSize: "14px",
                }}
              >
                <InfoWindow
                  key={address.id}>
                    <>
                  <span>{address.name}</span>
                  <div>
                    <button onClick={() => {
                      let foundUser = users.find(user => user.name === address.name)
                      let foundUserAddress = ""
                      if (foundUser.hasOwnProperty("address") && foundUser.hasOwnProperty("addressPublic") && foundUser.addressPublic === true) {
                        foundUserAddress = <>
                        <div className="tourCard__address">{foundUser.address}</div>
                        </>
                      }
                      let foundUserAddress2 = ""
                      if (foundUser.hasOwnProperty("address2") && foundUser.hasOwnProperty("address2Public") && foundUser.address2Public === true) {
                        foundUserAddress2 = <>
                        <div className="tourCard__address2">{foundUser.address2}</div>
                        </>
                      }
                      let foundUserZip = ""
                      if (foundUser.hasOwnProperty("zip") && foundUser.hasOwnProperty("zipPublic") && foundUser.zipPublic === true) {
                        foundUserZip = <>
                        <div className="tourCard__zip">{foundUser.zip}</div>
                        </>
                      }
                      let foundUserBlurb = ""
                      if (foundUser.hasOwnProperty("blurb") && foundUser.hasOwnProperty("blurbPublic") && foundUser.blurbPublic === true) {
                        foundUserBlurb = <>
                        <div className="tourCard__blurb">{foundUser.blurb}</div>
                        </>
                      }
                      let socialMediaButtonClicked = false
                      let foundUserWebsite = ""
                      let foundUserFacebook = ""
                      let foundUserInstagram = ""
                      let foundUserTwitter = ""
                      let foundUserSocialMedia = <>
                      <section className>
                        {foundUserWebsite}
                        {foundUserFacebook}
                        {foundUserInstagram}
                        {foundUserTwitter}
                      </section>
                      </>
                      if (foundUser.webPublic === true) {
                        if (foundUser.hasOwnProperty("website")) {
                          foundUserWebsite = <>
                          <div className="tourCard__website">{foundUser.website}</div>
                          </>
                        }
                        if (foundUser.hasOwnProperty("facebook")) {
                          foundUserFacebook = <>
                          <div className="tourCard__facebook">{foundUser.facebook}</div>
                          </>
                        }
                        if (foundUser.hasOwnProperty("instagram")) {
                          foundUserInstagram = <>
                          <div className="tourCard__instagram">{foundUser.instagram}</div>
                          </>
                        }
                        if (foundUser.hasOwnProperty("twitter")) {
                          foundUserInstagram = <>
                          <div className="tourCard__twitter">{foundUser.twitter}</div>
                          </>
                        }
                      }
                      tourCards.push(
                        <div className="tourCard" key={address.name}>
                          <h3 className="tourCard__header">{address.name}</h3>
                          <div className="tourCard__capacity">Capacity: {foundUser.capacity}</div>
                          {foundUserAddress}
                          {foundUserAddress2}
                          <section className="tourCard__cityStateZip">
                            <div className="tourCard__city">{foundUser.city}</div>
                            <div className="tourCard__state">{foundUser.state}</div>
                            {foundUserZip}
                          </section>
                          <h5 className="tourCard__blurbHeader">About Us</h5>
                          {foundUserBlurb}
                          {socialMediaButtonClicked ? (
                            <>
                            {foundUserSocialMedia}
                            </>
                          ) : (
                            <>
                            <button onClick={() => {
                              socialMediaButtonClicked = true
                            }}>Social media</button>
                            </>
                          )}
                        </div>
                  )
                  console.log(tourCards)
                  debugger
                    }}
                    >
                      Add to tour
                    </button>
                  </div>
                  </>
                </InfoWindow>
              </Marker>
              </>
            )
          }
          </GoogleMap>
  ));

    return (
      <>
        <div>
          <PlanMap
          loadingElement={<div style={{ height: `100%` }} />}
          containerElement={<div style={{ height: `400px`, width: `400px` }} />}
          mapElement={<div style={{ height: `100%` }} />}
          />
        </div>
        <section className="tourSection">
          {tourCards}
        </section>

        </>
    )

}
  • Is anything getting rendered at all? I'm just guessing here but I seem to remember that you can't put onClick functions in JSX elements unless they are directly in the render function. I could be wrong though... – R10t-- Feb 11 '20 at 22:35
  • If your fragment only contains one child, then it probably doesn't need to be a fragment. Can you describe the actual error you're getting? Anything in the console? Also, what is `tourcards` an array of? You might be using an incorrect syntax there. – Khauri Feb 11 '20 at 22:37
  • It doesn't look like you need to wrap any of these components in React Fragments since there's only a single parent for all of them. I don't know why wrapping them in Fragments would cause it to fail, but you might try removing the empty tags wrapping everything. – ajHurliman Feb 11 '20 at 22:39
  • @R10t-- I edited my return statement to exclude the google map from google-maps-react that is successfully rendering. – devintraining Feb 11 '20 at 22:45
  • @Khauri I'm not getting any errors in the console or browser. As I said in a reply to R10t--, I intentionally edited my return statement in my question to exclude some code that is successfully rendering. – devintraining Feb 11 '20 at 22:47
  • I've added my complete component code above. – devintraining Feb 11 '20 at 22:49
  • I think the problem is that your component needs to re-render after you push an element into tourcards. Try initializing it with a basic div like: `tourcards = [
    Hello World
    ]`. And if that works, then try putting tourcards into a state variable and calling the setState function on it to update the array.
    – Khauri Feb 11 '20 at 23:35
  • @Khauri tourcards = [
    Hello World
    ] did render. How do I use setState in a way that doesn't overwrite what's already in there?
    – devintraining Feb 11 '20 at 23:40
  • @Khauri I tried turning tourCards into a state variable, but unfortunately it still didn't render apart from the hello world. – devintraining Feb 11 '20 at 23:46

2 Answers2

0

<> ... </> is not an array. Depending on how and where your tourCards are created you could do something like:

var tourCards = [];

tourCards.push(<div className="tourCard"> ... </div>);
// ... more tourCards.push()

return (
  <>
    <section className="tourSection">
      {tourCards}
    </section>
  </>
);
kavun
  • 3,358
  • 3
  • 25
  • 45
  • I tried pushing it without the fragment tags and it still didn't work. I'm not getting an error it's just not rendering. – devintraining Feb 11 '20 at 22:44
0

As I said in the comments, I think your problem is just that your component doesn't re-render after you update tourCards. And even more, since tourCards is redefined as an empty array each time your component re-renders, it won't contain whatever you try to put into it. So you should use useState to keep track of it.

When working with arrays using react hooks it's a good idea to treat them as immutable and create a new array each time you set the state using array spreading

const [arr, setArr] = useState([])
// somewhere in an effect or callback
setArr(prevArr => [...prevArr, newValue]) // this is the same as arr.push
setArr(prevArr => [newValue, ...prevArr]) // this is the same as arr.unshift
setArr(prevArr => [...newArr, ...prevArr]) // this is arr.concat

Here's a working example you should be able to run right here on SO:

const {useState} = React

function App() {
  const [tourcards, setTourcards] = useState([])
  const addTourCard = function() {
    // Do whatever you need to do here
    const card = <div key={`card-${tourcards.length}`}>Card</div>
    setTourcards(cards => [...cards, card])
  }
  return (
    <div> 
      <button onClick={addTourCard}>Click Me</button>
      <div>{tourcards}</div>
    </div>
  )
}

ReactDOM.render(<App />, document.querySelector("#root"));
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>
Khauri
  • 3,753
  • 1
  • 11
  • 19
  • Thank you so much for your help! Learning how to use the spread operator with arrays and setState will be invaluable. – devintraining Feb 12 '20 at 00:19
  • Looking back on this, I'm confused by the syntax of this line: setTourcards(cards => [...cards, card]) How were you able to declare cards as an array holding the value of tourCards right there in the parentheses without declaring it elsewhere as a const or let or var? – devintraining Feb 13 '20 at 04:50
  • How could I delete a specific item in that array? – devintraining Feb 13 '20 at 04:51
  • That's called an [arrow function expression](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions). The previous value of the state variable is passed as an argument to the callback you pass to the setstate function. [See here](https://reactjs.org/docs/hooks-reference.html#functional-updates). – Khauri Feb 13 '20 at 14:47
  • As for removing, you can use [Array#filter](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter) or [Array#slice](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice) which will both return a new copy of the array instead of modifying it in-place. Depends on how you want to remove the element though. [Some examples](https://stackoverflow.com/questions/47023975/what-is-the-cleanest-way-to-remove-an-element-from-an-immutable-array-in-js) – Khauri Feb 13 '20 at 14:48
  • I have a delete function, but in the scope of the delete function, somehow the state of tourCards is what it was before I pushed the card I'm trying to delete into it. So If I try to delete the 1st element, tourCards is somehow empty. If I try to delete the 2nd element, tourCards somehow only has the 1st element in it. So delete deletes the element I want to delete, and any element after it. Why is tourCards behind what I'm seeing on the screen? – devintraining Feb 13 '20 at 17:17
  • I should also add that tourCards is a state variable. – devintraining Feb 13 '20 at 17:57
  • You should edit your question and add your updated code. – Khauri Feb 13 '20 at 18:04