skip to Main Content

Array.join is useful because it glues together an array of strings by a delimiter taking into account empty arrays and not padding the delimiter at either end of the output string.

I am making a React application and I would like find a similar native function, however, my array of React.ReactNode elements is going to be mapped and rendered, so instead of joining the array with a delimiter and outputting a string, I just want to join the array with a string delimiter, but maintain the Array structure. Is there something already native to javascript/typescript/react that I can use to achieve the below psuedo-code?

let parts: React.ReactNode[] = [];
if (conditionA) {
  parts.push(<div>Component One</div>);
}
if (conditionB) {
  parts.push(<div>Component Two</div>);
}
parts = joinByDelimiterButKeepAsArray(parts, '|');
// I wants parts to now equal
// [<div>Component One</div>, '|', <div>Component Two</div>]
// if conditionA and conditionB are both true
return (
  <>
    {parts.map((part, i) => return (
      <React.ReactFragment key={i}>{part}</React.ReactFragment>
    ))}
  </>
)

3

Answers


  1. Chosen as BEST ANSWER

    Welp, kind of answered my own question. Currently, I use the following function to achieve this:

    function joinByDelimiterButKeepAsArray<T>(a: T[], delimiter: T): T[] {
      return a.flatMap((n, i) => (i + 1 < a.length ? [n, delimiter] : [n]));
    }
    

    Example:

    const parts: React.ReactNode[] = [];
    parts.push(<div>Component One</div>);
    console.log(joinByDelimiterButKeepAsArray(parts, '|'));
    /// [{...}]
    parts.push(<div>Component Two</div>);
    console.log(joinByDelimiterButKeepAsArray(parts, '|'));
    /// [{...}, '|', {...}]
    parts.push(<div>Component Three</div>);
    console.log(joinByDelimiterButKeepAsArray(parts, '|'));
    /// [{...}, '|', {...}, '|', {...}]
    

  2. You could use reduce without the initialValue argument:

    a.reduce((acc, val) => [].concat(acc, delimiter, val));
    

    This requires special care when a is an empty array. If that needs to be supported, then:

    a.length ? a.reduce((acc, val) => [].concat(acc, delimiter, val)) : a;
    

    Addendum

    I just found out that TypeScript only allows the second argument of reduce to be omitted when reduce will return the same type as the array elements. Here we want to return a different type (T[] instead of T), yet start out with an implicit T initial value. Not possible.

    So this restriction kills the beauty of the above JavaScript solution, as now we have to provide the second argument:

    a.reduce<T[]>((acc, val) => acc.concat(delimiter, val), []).slice(1);
    

    And with additional type specifications for the callback parameters:

    a.reduce<T[]>((acc: T[], val: T) => acc.concat(delimiter, val), []).slice(1);
    
    Login or Signup to reply.
  3. You could try this solution:

    const parts = ['<div>Component One</div>', '<div>Component Two</div>'];
    
    /**
     * Add the delimiter between each item in the array.
     *
     * @param {any[]} array
     * @param {string} delimiter
     * @returns {any[]}
     */
    const joinByDelimiterButKeepAsArray = (array, delimiter) => {
      /**
       * To prevent the function from crashing, we need to check if the array is an array.
       */
      if (!Array.isArray(array) || array.length === 0) {
        return [];
      }
    
      const [firstElement, ...rest] = array;
    
      return rest.reduce((acc, curr) =>
        acc.concat(delimiter, curr)
        /**
         * The initialValue is set to an array containing the first element of the array to
         * prevent the edge case of an array of length 1. If we don't do this, it would result
         * in an array of length 2 with the first element being the first element of the array
         * and the second element being the delimiter.
         *
         * e.g. [onlyOneElement] -> [onlyOneElement, delimiter]
         */
      , [firstElement]);
    };
    
    console.log(joinByDelimiterButKeepAsArray(parts, '|'));

    UPDATE

    I’ve never used Typescript before. The typing would probably look like this:

    const joinByDelimiterButKeepAsArray: <T>(array: T[], delimiter: T) => T[] = (array: T[], delimiter: T): T[] => {
      if (!Array.isArray(array) || array.length === 0) {
        return [];
      }
    
      const [firstElement, ...rest] = array;
    
      return rest.reduce((acc: T[], curr: any): T[] =>
        acc.concat(delimiter, curr)
      , [firstElement]);
    };
    

    Please consider marking this as an answer if it helped!

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search