40

I'd like to inject a separator between each element of an array using an array.join-like syntax.

Something like the following:

render() {
 let myArray = [1,2,3];
 return (<div>
   {myArray.map(item => <div>{item}</div>).join(<div>|</div>)}
</div>);
}

I've got it working using a lodash.transform approach, but it feels ugly, I'd like to just do .join(<some-jsx>) and have it insert a separator in between each item.

0

11 Answers 11

31

You can also use reduce to insert the separator between every element of the array:

render() {
  let myArray = [1,2,3];
  return (
    <div>
      {
        myArray
          .map(item => <div>{item}</div>)
          .reduce((acc, x) => acc === null ? [x] : [acc, ' | ', x], null)
      }
    </div>
  );
}

or using fragments:

render() {
  let myArray = [1,2,3];
  return (
    <div>
      {
        myArray
          .map(item => <div>{item}</div>)
          .reduce((acc, x) => acc === null ? x : <>{acc} | {x}</>, null)
      }
    </div>
  );
}
Sign up to request clarification or add additional context in comments.

1 Comment

This does not work in TypeScript because acc is not a string. A cleaner and TS-proof way to solve this is to actually use a Fragment as shown in stackoverflow.com/a/56136781/2733244.
13

You can also do it by combining .reduce and React fragments.

function jsxJoin (array, str) {
  return array.length > 0
    ? array.reduce((result, item) => <>{result}{str}{item}</>)
    : null;
}

function jsxJoin (array, str) {
  return array.length > 0
    ? array.reduce((result, item) => <React.Fragment>{result}{str}{item}</React.Fragment>)
    : null;
}

const element = jsxJoin([
  <strong>hello</strong>,
  <em>world</em>
], <span> </span>);

ReactDOM.render(element, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.4.1/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.4.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>

2 Comments

What is <> </>?
It's a shorthand notation for fragments. See reactjs.org/docs/fragments.html#short-syntax
8

Trying to .join with React Elements is probably not going out pan out for you. This would produce the result you describe needing.

render() {
    let myArray = [1,2,3];
    return (
      <div>
        {myArray.map((item, i, arr) => {
          let divider = i<arr.length-1 && <div>|</div>;
          return (
            <span key={i}>
                <div>{item}</div>
                {divider}
            </span>
          )
        })}
      </div>
    );
  }

2 Comments

btw, generally you don't want a <div> inside a <span>
this is really ugly hack. what's wrong with join..?
4

Using Array.map and React.Fragment you can join each array item with any JSX element you want. In the example below I'm using a <br /> tag but this can be substituted for whatever element you want.

const lines = ['line 1', 'line 2', 'line 3'];
lines.map((l, i) => (
    <React.Fragment key={i}>{l}{i < (lines.length - 1) ? <br /> : ''}</React.Fragment>
));

The output would be

line 1 <br />
line 2 <br />
line 3

Comments

2

Btw. you can also supply functions to react. My approach would be .forEach pairs of push(value); push(glue);, and afterwards pop() the trailing glue...

function() {
    joinLike = [];
    originData.forEach(function(v,i) {
        joinLike.push(v);
        joinLike.push(<br>);
    })
    joinLike.pop();
    return joinLike;
}

Comments

2

If you are using TypeScript, you can simply copy this file and use jsxJoin:

import { Fragment } from "react";

/**
 * Join together a set of JSX elements with a separator.
 *
 * @see https://stackoverflow.com/q/33577448/5506547
 */
function jsxJoin(components: JSX.Element[], separator: any) {
    // Just to be sure, remove falsy values so we can add conditionals to the components array
    const filtered = components.filter(Boolean);
    return filtered.length === 0
        ? null
        : (filtered.reduce(
              (acc, curr, i) =>
                  acc.length
                      ? [
                            ...acc,
                            // Wrap the separator in a fragment to avoid `missing key` issues
                            <Fragment key={i}>{separator}</Fragment>,
                            curr
                        ]
                      : [curr],
              []
          ) as JSX.Element[]);
}

export { jsxJoin };

Comments

1

To produce the requested solution, in React join doesn't seem to work so with map and reduce you can do it like this:

render() {
  let myArray = [1, 2, 3];
  return (
    <div>
        {myArray
            .map(item => <div>{item}</div>)
            .reduce((result, item) => [result, <div>|</div>, item])}
    </div>
  );
}

Comments

1

You could also try flatMap(). Browser support is coming, but until then you could use a polyfill. Only downside is you'd have to lose the last element.

Eg.

{myArray.flatMap(item => [<div>{item}</div>, <div>|</div>]).slice(0, -1)}

Or

{myArray.flatMap((item, i) => [
  <div>{item}</div>,
  i < myArray.length - 1 ? <div>|</div> : null
])

Comments

1

You can use a function like this one

function componentsJoin(components, separator) {
  return components.reduce(
    (acc, curr) => (acc.length ? [...acc, separator, curr] : [curr]),
    []
  );
}

Or can use a package, found this one https://www.npmjs.com/package/react-join

Comments

1

This will cover all cases

const items = []
    if(x1) {
       items.push(<span>text1</span>)
    }
    if(x2) {
       items.push(<span>text3</span>)
    }
    if(x3) {
       items.push(<span>text3</span>)
    }
    
    
    return <div>
        <>{items.reduce((result, item) => result.length > 0 ? [...result, ', ', item] : [item], [])}</>
    </div>

Comments

0
// typescript
export function joinArray<T, S>(array: Array<T>, separator: S): Array<T | S> {
  return array.reduce<(T | S)[]>((p, c, idx) => {
    if (idx === 0) return [c];
    else return [...p, separator, c];
  }, []);
}
// javascript
export function joinArray(array, separator) {
    return array.reduce((p, c, idx) => {
        if (idx === 0)
            return [c];
        else
            return [...p, separator, c];
    }, []);
}
// example
console.log(joinArray(["1", "2", "3"], 2));
// -> ["1", 2, "2", 2, "3"]
// example
// privacyViews -> JSX.Element[]
const privacyViews = joinArray(
  privacys.value.map(({ key, name }) => {
    return (
      <button onClick={() => clickPrivacy(key!)} class={Style.privacyBtn}>
        {name}
      </button>
    );
  }),
  <span class={Style.privacyBtn}>、</span>
);

1 Comment

While this code may provide a solution to the question, it's better to add context as to why/how it works. This can help future users learn, and apply that knowledge to their own code. You are also likely to have positive feedback from users in the form of upvotes, when the code is explained.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.