JustAnotherDev
JustAnotherDev

Reputation: 415

React - appendChild() a React Link tag to existing li appends [object, object]

Have a loop that qualifies if an objects date key/value matches a date on a calendar. If it does I want it to append a React Link tag to the corresponding li element. The condition loop works fine, but when it appends the Link tag it is appending [object, object] since I am using createTextNode().

Is there another way to append a Link tag to an existing li without using createTextNode()?

I am done the usual google search with no luck.

for (let i = 0; i < events.length; i++) {
     const link = (<Link to={`/events/${events[i].eventId}`}> . 
                  {events[i].title}</Link>);
     const eventLink = document.createTextNode(link);

     if (events[i].date.slice(15) === day.toString()) {
     cell.appendChild(eventLink);
    }
}

Need the li element to contain the React Link tag and not a text node.

UPDATE Code with ReactDOM render and BrowserRouter but when I click the Link tag React doesn't render the route component.

for (let i = 0; i < events.length; i++) {
    const link = (
                <>
        {day.toString()}
        <Link to={`/events/${events[i].eventId}`}>{events[i].title}</Link>
        </>);

        const eventDate = events[i].date.split('-');
        const eventYear = eventDate[0];
        const eventMonth = eventDate[1];
        const eventDay = eventDate[2];
        if (eventDay === day.toString() &&
        eventMonth === (month + 1).toString() &&
        eventYear === year.toString()) {
        ReactDOM.render(<BrowserRouter>{link}</BrowserRouter>, cell);
            }
        }

Upvotes: 1

Views: 7600

Answers (2)

Yurui Zhang
Yurui Zhang

Reputation: 2232

Not sure why you need to do it this way - I'm pretty sure there are other ways to render Links based on an array into your DOM.

However if you insist, you should use ReactDOM

In your loop:

// create an empty div to hold our links:
const tempDiv = document.createElement('div');

// render links into the empty div
ReactDOM.render(eventLinks, tempDiv);

// append the link, since you don't want the div in cell
cell.appendChild(tempDiv.firstChild); // there is only 1 child

This is very "hacky" - I'd highly recommend looking for a real "React" way of doing this.


EDIT:

your updated code won't work. react-router relies on an internal context to navigate, and the *Router components provide that context - your code renders a BrowserRouter for each link meaning each link will has its own context. Multiple routers probably won't work well in one page as they will fight control over the browser.

First of all you probably shouldn't call ReactDOM.render in each iteration of your loop. ReactDOM.render takes control of all the content inside your cell - rendering it again and again meaning only the last call works.

Secondly, your BrowserRouter should be placed at the top level of your App (e.g. in your root component).

Again, manipulating dom like that is not how you should use React. I'm curious what kind of app you are building and how it is structured?


So here's what I think you should do - since you are already using react, I imagine there is a component tree (e.g. you have a top level component that renders other components, etc.)

So at the very top of your app, you should wrap everything in the BrowserRouter.

// index.js, maybe.

// assuming App is your "top level component"
import App from './App.jsx';

ReactDOM.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>
, document.getElementById('root'))

And, now, create a EventLinks component that renders all the links:

// EventLinks.jsx

const EventLinks = ({ events }) => (
  events.map((event) => (
    <li key={event.eventId}>
      <Link to={`/events/${event.eventId}`}>
        {event.title}
      </Link>
    </li>
  ))
);

In your component where cell lives, render the links directly.

// Cell component

const Cell = ({ events }) => {
  // filter events if you need to here, since you have that `if` logic originally

  const visibleEvents = events.filter(filterFunction);

  return (
    <ul>
      <EventLinks events={visibleEvents} />
    </ul>
  );
}

This is probably not exactly match your app's structure but I hope you get the idea.

so your App renders a layout and Cell component, and then your Cell renders EventLinks.

Honestly you shouldn't be using ReactDOM at all except for the only entry point of your app.

Upvotes: 3

Itay Wolfish
Itay Wolfish

Reputation: 566

if i am getting your questions correctly you are trying to add to a li element a link(of React link) as the text inside the li element(and if im correct as clickable link ..... anchor tag element)

cell must be the correct li element in which i assume you iterate parallel to the event, then here is a possible solution:

for (let i = 0; i < events.length; i++) {
     const link = (<Link to={`/events/${events[i].eventId}`}> . 
                  {events[i].title}</Link>);

     if (events[i].date.slice(15) === day.toString()) {
     let tempComponent = ReactDOM.createElement("li",{...propertiesIfNeeded},link)
     ReactDOM.render(tempComponent,cell) //you could also pass here refrece to cell/li items where i wrote cell(eg.document.querySelect....)
    }
}

or solution 2 creating the li as part of the link component you create in the iteration:

for (let i = 0; i < events.length; i++) {
         const link = (<li><Link to={`/events/${events[i].eventId}`}> . 
                      {events[i].title}</Link></li>);

         if (events[i].date.slice(15) === day.toString()) {
         ReactDOM.render(link,cell) //you could also pass here refrece to cell/li items where i wrote cell(eg.document.querySelect....)
        }
    }

Upvotes: 0

Related Questions