Jonovono
Jonovono

Reputation: 2085

How could I structure this React Native Section List implementation

So I want to use RN Section list in a sort of unorthodox way.

I want the section list to pass off rendering to a component as the renderings won't be very uniform.

I want to use section list so as you scroll you still get to see the headers.

I made a component that takes in children and renders them in a section list like so:

class SomeSectionList extends Component {

    render() {
        let sections = React.Children.map(this.props.children, (Child, index) => {
            return {title: Child.type.title, data: [''], renderItem: () => Child, index }
    });

        return (
            <SectionList

                renderSectionHeader={({section}) => {
                    return <Text style={{ fontWeight: "bold" }}>{section.title}</Text>
        }}
                sections={sections}
                keyExtractor={(item, index) => item + index}
            />
        );
    }
}

And the usage would be something like:

                <SomeSectionList>
                    <Comp1 />
                    <Comp2 />
                </SomeSectionList>

However, my issue is. Say in this case Comp1 does not render anything from it's component, I want to be able to hide it's section from the section list.

How could the SomeSectionList component know that it didn't render anything or didn't have the data to render anything so it can hide it's section and it's header?

Any suggestions would be great. I feel like using SectionList for this is overkill (but it makes showing the headers nicer) so open to alternatives as well.

Upvotes: 5

Views: 2829

Answers (2)

Jonovono
Jonovono

Reputation: 2085

Here is an alternate that I am thinking after the helpful advice of @JaydeepGalani!!

class SomeSectionList extends Component {
  constructor(props) {
    super(props)
    this.state = {
            hiddenChildren: {}
    }
  }


  onLayout = (event, index) => {
    if (event.nativeEvent.layout.height <= 0) {
            const hiddenChildren = this.state.hiddenChildren
            hiddenChildren[index] = true
            this.setState({
                hiddenChildren
            })
    } else {

            const hiddenChildren = this.state.hiddenChildren
            delete hiddenChildren[index]
            this.setState({
                hiddenChildren
            })
    }
  }

    render() {
        let sections = React.Children.map(this.props.children, (Child, index) => {
            return {
                title: Child.type.title, 
                index,
        data: [''], 
        renderItem: () => (
          <View onLayout={event => this.onLayout(event, index)}>
            {this.state.children[index]}
          </View>
      )}
    });

        return (
            <SectionList

                renderSectionHeader={({section}) => {
                    const index = section.index
                    if (this.state.hiddenChildren[index]) return 

                    return <Text style={{ fontWeight: "bold" }}>{section.title}</Text>
        }}
                sections={sections}
                keyExtractor={(item, index) => item + index}
            />
        );
    }
}

Since in the first implementation once the section got removed it's really hard to bring it back as the onLayouts don't get triggered. In this we still technically 'render' the section, but hide the header and since the section is of height 0 it won't show up, but it's still rendered and say at a later point in time that section changes and suddenly renders something it will now show up in the section list.

Curious on any feedback around this?

Upvotes: 0

Jaydeep Galani
Jaydeep Galani

Reputation: 4961

You can accomplish this using onLayout method that comes with View.

By which we can get the height of the component rendered. if it is 0 that means nothing is rendered inside it or else it contains some data.

See this Working example on snack

export default class App extends React.Component {
  render() {
    return (
      <SomeSectionList>
        <Comp1 />
        <Comp2 />
        <Comp1 />
        <Comp2 />
        <Comp1 />
      </SomeSectionList>
    );
  }
}

class Comp1 extends React.Component {
  render() {
    return (
      <View>
        <Text>Comp11</Text>
      </View>
    );
  }
}

class Comp2 extends React.Component {
  render() {
    return null;
  }
}

class SomeSectionList extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      children: this.props.children,
    };
  }
  onLayout = (event, index) => {
    if (event.nativeEvent.layout.height <= 0) {
      let oldProps = this.state.children;
      oldProps.splice(index, 1);
      this.setState({ children: oldProps });
    }
  };
  render() {
    let sections = React.Children.map(this.state.children, (Child, index) => {
      return {
        title: Child.type.title,
        data: [''],
        renderItem: () => (
          <View onLayout={event => this.onLayout(event, index)}>
            {this.state.children[index]}
          </View>
        ),
        index,
      };
    });

    return (
      <SectionList
        renderSectionHeader={({ section }) => {
          return <Text style={{ fontWeight: 'bold' }}>{section.title}</Text>;
        }}
        sections={sections}
        keyExtractor={(item, index) => item + index}
      />
    );
  }
}

Here, first of all, we have assigned this.props.children into state. Then in onLayout method, we are checking if the current indexed child has 0 height or not. if yes then remove it from the array of children.

You'll see clearly that some views are deleting. for this thing what we have done in one scenario is put one loader that covers the whole SectionList area with position absolute and you can hide it when all things are rendered correctly.

Upvotes: 3

Related Questions