How ro re-render everything in React Native

12,573

Solution 1

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 */} />

Solution 2

Most dirty way: add the key property to the root component and change it anytime you need it. It causes rerender everything. But you will lose state.

Share:
12,573
Michael
Author by

Michael

Updated on July 13, 2022

Comments

  • Michael
    Michael almost 2 years

    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.

  • Michael
    Michael over 6 years
    Thank you for a very elaborate answer. I extended my question with some thoughts.
  • Michael
    Michael over 6 years
    Thank you. I need to keep the local state in the components as it could hold non saved data. I keep form changes in local state and throttles the updates to the Redux store, which in turn persists to disk. I.e. if I'd loose state I could discard a couple of seconds of data input for my users.
  • Michael
    Michael over 6 years
    Also.. I want to keep all the styling in my Redux store in order to piggy back on the stores hydration logic. I.e. when the app is restarted the styling gets hydrated from disk and copied to my global style object. The store-version of the styles are only used as an original source.
  • Michael
    Michael over 6 years
    Another thought.. By passing theme name via props to all of my dumb components I have essentially made them connected to the store, haven't I? Wouldn't it be almost the same to actually connect them so they could listen to the store for them selves? This way I'd not have to pass the theme around to every component.
  • bennygenel
    bennygenel over 6 years
    Yes in a way you did but not exactly. Because they are "dumb" components they are not aware of the state or the store. They are just using the prop you pass on them. Essentially you are removing and adding a new component every time you re-render. Its not exactly like that but you can think it like this. Regard to your addition to your question, I think using screenProps is not a hacky way since its the purpose of the prop itself.
  • Michael
    Michael over 6 years
    Is it better to propagate props all the way down the component hierarchy or make to make dumb components connected (i.e. smarter)? Either way all components will be re-rendered upon a prop change. To me it seems easier to connect everything to the store. Or is there a lot of overhead in connected vs dumb components?
  • bennygenel
    bennygenel over 6 years
    I think there should be balance. Dumb components are better for performance since they are "dumb". So I would suggest you to use them as much as you can, but I can't be sure of your projects requirements completely since I don't know all of it. You can try to combine your dumb components with a smart one, like a Theming component that themes the children components. Maybe similar to MuiThemeProvider
  • Michael
    Michael over 6 years
    Thanks Benny. I have now implemented it (almost) the way you suggest. I give this.props.styling to my smart components, which in turn passes it down to the dumb components. It works great. I had a little struggle with some very dynamic components (forms that build themselves based on a specification), but it turned out great. Can't stop playing with altering theme-colors in my database to see the colors change in the app a fraction of a second later :)