Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How ro re-render everything in React Native

My app has it's theme (colors and styles) delivered via a REST API. Whenever a theme is changed on the server the app gets notified via socket communication so it can get the new theme via REST.

The theme is fetched in a Redux Saga and the colors are saved to both the Redux store (to enable persisting to disk) as well as a "global" singleton object which I can reach from everywhere, i.e. even non connected components.

When a new theme arrives I want to re-render every element in the app. I have managed to re-render all store-connected components in a hacky way - by injecting a dummy property which changes when new a theme is changed.

Is there a way to forceUpdate the whole app?

Problem areas:

  • dumb components (not aware of theme changes)

  • really smart components (aware of the store, but knows not to re-render in vain)

Here is an example of a hacky force-re-render of react-navigation:

myAppContainer.render() {

  <AppRouter
    screenProps={this.props.styling}
  />
}

I.e. my AppContainer gets this.props.styling via mapStateToProps. The wrapped StackNavigator gets this via screenProps which forces the navigator to re-render when the store gets new styling data.

I don't want to continue on this hacky path. Instead I'd like a forceUpdateEverything() function which I could call from my AppContainer. Is there such a thing?


Edit to comment the answer from @bennygenel:

I think what Benny describes is essentially what I did. A change in this.props.styling triggers a re-render.

But I'd have to implement this into all components and "hack" react-navigation via its screenProps as I described. This is what I'd hoped to avoid, but I guess I'll have to take the long way..

I'm using immutable.js, so sending in the complete style instead of just the theme name is no big deal as it's only a pointer which is speedy to compare with it's former value to look for changes.

I didn't get forceUpdate() to work other than on the component it's called on. I assume it doesn't get propagated recursively through all children.

like image 460
Michael Avatar asked Oct 10 '17 06:10

Michael


1 Answers

I think the simplest and most performance way of doing this rather then holding all styling object in your state and redux you can just hold some sort of theme identifier. This way when any changes happen for the theme that your components use can be applied.

In react there is a method called forceUpdate. Although it sounds like the thing you are looking for, its not a really good practice to use it.

From the react docs;

By default, when your component’s state or props change, your component will re-render. If your render() method depends on some other data, you can tell React that the component needs re-rendering by calling forceUpdate().

Calling forceUpdate() will cause render() to be called on the component, skipping shouldComponentUpdate(). This will trigger the normal lifecycle methods for child components, including the shouldComponentUpdate() method of each child. React will still only update the DOM if the markup changes.

Normally you should try to avoid all uses of forceUpdate() and only read from this.props and this.state in render().

I think what you should do is to create a theme logic in your app and change the theme accordingly.

Example

// styles.js
const styles = {
  button: {
    blue: {
      backgroundColor: 'blue'
    },
    red: {
      backgroundColor: 'red'
    },
    green: {
      backgroundColor: 'green'
    }    
  }
};

export default styles;

// Dumb component example
import styles from './styles';

const Button = (props) => {
  return <Button {...props} style={[props.style, styles.button[props.theme]]} />
};

// Complex component example
import styles from './styles';

export default Button extends React.Component {
  constructor(props) {
    super(props);
  }
  render() {
    const {style, theme} = this.props;
    return <Button {...this.props} style={[style, styles.button[theme]]} />
  }
}

// usage in another component
import React, { Component } from 'react';
import { Text, View, StyleSheet } from 'react-native';
import Button from './components/Button'; // Custom button component
import styles from './styles';

export default App extends React.Component {
  constructor(props) {
    super(props);
  }
  render() {
    return(
      <View style={styles.container}>
        <Text style={styles.paragraph}>
          Change code in the editor and watch it change on your phone!
          Save to get a shareable url. You get a new url each time you save.
        </Text>
        <Button theme="blue" {/* other Button component props */} />
      </View>
    );
  }
}

In my example I use styles defined as hard-coded object. You can add your API call and socket communication logic to it.

In example rather than passing the complete style object from one component/screen to another I can just simply pass the theme name and any change on theme prop will force the component to fetch new style object from styles constant.

Theme doesn't need to be set for every single object. You can use a higher property in your object.

Example

const styles = {
  id5248698745: {
    button: {
      backgroundColor: 'green'
    },
    label: {
      color: 'yellow',
      fontSize: 18
    },
    paragraph: {
      color: 'red',
      fontSize: 19
    },
    headings: {
      color: 'wihte',
      fontSize: 24
    }
  }
};

// ...
render() {
    const {style, theme} = this.props;
    return <Button {...this.props} style={[style, styles[theme].button]} />
}

// ...
// <Button theme={this.props.themeId} {/* other Button component props */} />
like image 50
bennygenel Avatar answered Oct 14 '22 12:10

bennygenel