MSadura
MSadura

Reputation: 1042

Update component's instance props / style

I know this kind of manipulations might be unrecommended but in my situation I cannot find any other solution. I have a reference to component instance and I want to reach its parent, update style and re-render. If you think this is crazy I'll describe the problem.

I am using section list with sticky header and it generates tree like this (simplified):

<VirtualizedList>
    <CellRenderer> //internal component rendered by Virtualized list with flex: 1
        <MyComponent ref=(instance) => changeStylesFunc(instance) />
    </CellRenderer>
</VirtualizedList>

I am passing MyComponent but event if i set its style to width: 80 wrapper (CellRenderer) will have full width because of its own styles. T cannot change CellRendererstyle because that would involve RN source code changes.

My idea: since I have ref to MyComponent instance I can also reach parent instance (CellRenderer) by:

const cellRenderer = ref._reactInternalInstance._currentElement._owner

and I am stuck here because I had no luck with updating styles for cellRenderer.

What I've tried:

cellRenderer._instance.props.styles = { width: 80 };
cellRenderer._currentElement.props.styles = { width: 80 };

Upvotes: 1

Views: 727

Answers (2)

Val
Val

Reputation: 22797

This is another way to change visual width from <SectionList /> items, without touching <VisualizedList /> at all.

Result:

enter image description here

Code:

export class Home extends Component {
  constructor(props) {
    super(props);

    this.state = {};
  }

  getUidFromSectionAndRow(section, row) {
    return `${section}_${row}`;
  }
  getStyleWithUid(uid) {
    return this.state[uid] || {flex: 1};
  }
  setStyleWithUid(uid, style) {
    this.setState({
      [uid]: style
    });
  }

  render() {
    let sectionData = [
      [1,2,3],
      [1,2,3]
    ]

    return (
      <SectionList
        renderItem={({item}) => {
          return (
            <View style={[this.getStyleWithUid(item.key), {flexDirection: 'row', marginVertical: 10}]}>
              <Text style={{flex: 1, textAlign: 'center', textAlignVertical: 'center', backgroundColor: 'white'}}>This is number {item.value} row.</Text>
              <TouchableOpacity onPress={() => {
                this.setStyleWithUid(item.key, {width: 200});
              }}>
                <Text style={{width: 80, height: 50, backgroundColor: 'purple', textAlign: 'center', textAlignVertical: 'center', color: 'white'}}>Change Width</Text>
              </TouchableOpacity>
            </View>
          )
        }}
        renderSectionHeader={ ({section}) => <Text style={{fontSize: 20, fontWeight: '600'}}>{section.title}</Text> }
          sections={
            [
              { data: [
                {
                  key: this.getUidFromSectionAndRow(1, 1),
                  value: 1
                },
                {
                  key: this.getUidFromSectionAndRow(1, 2),
                  value: 2
                },
                {
                  key: this.getUidFromSectionAndRow(1, 3),
                  value: 3
                }
              ], title: 'Section 1' },
              { data: [
                {
                  key: this.getUidFromSectionAndRow(2, 1),
                  value: 1
                },
                {
                  key: this.getUidFromSectionAndRow(2, 2),
                  value: 2
                },
                {
                  key: this.getUidFromSectionAndRow(2, 3),
                  value: 3
                }
              ], title: 'Section 2' },
            ]
          }
      />
    );
  }
}

This 70 lines of code should be simple enough, the key is to make every section / row reflect to an unique key in state.

So every time you set style with setStyleWithUid(), it only effects to that specific row.

Upvotes: 0

Val
Val

Reputation: 22797

I think what you need is Lifting State Up:

Often, several components need to reflect the same changing data. We recommend lifting the shared state up to their closest common ancestor. Let’s see how this works in action.

In your case, <CellRenderer /> and <MyComponent /> need to reflect the same changing data.

class MyCell extends Component {
  constructor(props) {
    super(props);
    this.state = {
      cellStyle: { flex: 1 }
    }
  }

  changeStyleFunc(newStyle) {
    this.setState({ cellStyle: newStyle });
  }

  render() {
    return (
      <CellRenderer style={this.state.cellStyle}>
        <MyComponent onChangeStyle={(newStyle) => changeStylesFunc(newStyle)} />
      </CellRenderer>
    );
  }
}

So you can use:

<VirtualizedList>
  ...
  <MyCell />
  ...
</VirtualizedList>

Note:

Don't mess up by modifying props from ref like that. It won't work, since An update can be caused by changes to props or state. Not by changing their inner, immutable value.

Upvotes: 2

Related Questions