Search code examples
javascriptreactjstypescriptreact-pdf

Compose components to variable in reactjs


Is it possible to compose more different parts of component to variable? documentation

const App = () => {
  let element;

  element = <View>
      <Text>text</Text>
    </View>  // --> OK

  element = element + <View>
      <Text>text2</Text>
    </View>  // --> TypeError: Cannot read properties of undefined (reading 'height')

  element.push(<View>
      <Text>text3</Text>
    </View>);  // --> TypeError: element.push is not a function

  return <>
    {element}
  </>
}

export default App;

I use reactjs 17.0.2, typescript and "@react-pdf/renderer": "2.3.0".


Solution

  • Update

    Based on your question here, this should work:

    <Document>
      <Page size="A4" orientation="landscape">
        {/* -- Table LEFT: -- */}
        <View>
          {/* -- Table Head: -- */}
          <View>
            <Text>Index</Text>
            <Text>Brand</Text>
            <Text>Type</Text>
          </View>
          {/* -- Table Body: -- */}
          {data?.cars?.length &&
            data.cars.map(({ id, brand, type }, index) => (
              <View key={`${id}-left`}>
                <Text>{index + 1}</Text>
                <Text>{brand || ''}</Text>
                <Text>{type || ''}</Text>
              </View>
            ))}
        </View>
      </Page>
    
      <Page size="A4" orientation="landscape">
        {/* -- Table RIGHT: -- */}
        <View>
          {/* -- Table Head: -- */}
          <View>
            <Text>Color</Text>
            <Text>Fuel</Text>
          </View>
          {/* -- Table Body: -- */}
          {data?.cars?.length &&
            data.cars.map(({ id, color, fuel }) => (
              <View key={`${id}-right`}>
                <Text>{color || ''}</Text>
                <Text>{fuel || ''}</Text>
              </View>
            ))}
        </View>
      </Page>
    </Document>
    

    The issue seems to be with how you're handling arrays, not with rending React elements.

    If you want to access the properties of the object in an array element, you can destructure the element, so instead of

    data.cars.map((car, index) => (<Text>{car.color}</Text>))
    

    you can do

    data.cars.map(({id, brand, type, color, fuel}, index) => (<Text>{color}</Text>));
    

    If you're not performing any operations on the array elements, you can use an implicit return instead of an explicit return:

    // explicit return
    data.cars.map(({id, brand, type, color, fuel}, index) => { 
      // do something else here
      return (
        <Text>{color}</Text>
      )
    });
    
    // implicit return
    data.cars.map(({id, brand, type, color, fuel}, index) => (<Text>{color}</Text>));
    

    Also, when you're rending known text values in React, you don't need to wrap it in curly braces ({}), you can just render the text directly.

    Instead of

    <Text>{'color'}</Text>
    

    you can just put

    <Text>color</Text>
    

    unless it's required by whatever library you're using. I'm not familiar with @react-pdf/renderer.

    One more thing to keep in mind is that the key for list items in React should be something stable. Using array indices as keys is discouraged (see React docs).


    Original answer

    If you want to render an element this way, you could do something like this:

    const App = () => {
      let element = [];
    
      // Each child in a list needs a unique "key" prop
      element.push(<View key={someUniqueKey}>
          <Text>text</Text>
        </View>)
    
      element.push(<View key={someOtherUniqueKey}>
          <Text>text2</Text>
        </View>)
    
      element.push(<View key={oneMoreUniqueKey}>
          <Text>text3</Text>
        </View>);
    
      return <>
        {element}
      </>
    }
    
    export default App;
    

    Personally, I haven't seen anyone render components like this.

    The strategy you are looking for is called conditional rendering, and there are different ways to do this depending on the situation.

    For example, if you're trying to dynamically render data from an API response, you could do something like this:

    const App = () => {
      const { data } = fetchDataFromAPI();
    
      return (
        <>
          <View>
            <Text>text</Text>
          </View>
    
          {data?.text2 && (
            <View>
              <Text>{data.text2}</Text>
            </View>
          )}
    
          {data?.text3 && (
            <View>
              <Text>{data.text3}</Text>
            </View>
          )}
        </>
      );
    };
    
    export default App;
    

    You can check out the React docs for conditional rendering and rendering lists.

    (Note: The above links are for the beta docs. If you prefer the classic(?) docs: conditional rendering and lists)