Lesson 06-React Native Styling and Layout

React Native Styling Basics

Core Concepts of the Styling System

React Native’s styling system shares similarities with web CSS but has distinct differences:

  • JavaScript Objects: Styles are defined as JavaScript objects, not CSS files.
  • Flexbox by Default: Layouts are based on Flexbox, with no support for floating or grid layouts.
  • Unit Limitations: Supports pixels (default unit) and percentages, but not em or rem.

Styles are typically applied to components via the style prop, for example:

<Text style={{ fontSize: 18, color: 'blue' }}>Hello</Text>

Introduction to Flexbox Layout

Flexbox is the primary layout mechanism in React Native. Core properties include:

  • flex: Defines the component’s flexibility.
  • flexDirection: Specifies the arrangement direction (row or column).
  • justifyContent: Controls alignment along the main axis.
  • alignItems: Controls alignment along the cross axis.

Example Code

import React from 'react';
import { View, StyleSheet } from 'react-native';

const App = () => {
  return (
    <View style={styles.container}>
      <View style={styles.box1} />
      <View style={styles.box2} />
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
  },
  box1: { width: 50, height: 50, backgroundColor: 'red' },
  box2: { width: 50, height: 50, backgroundColor: 'blue' },
});

export default App;

Analysis

  • flex: 1: The container fills its parent space.
  • flexDirection: 'row': Child elements are arranged horizontally.
  • justifyContent: 'space-between': Child elements are aligned to the container’s edges.

Style Merging and Inheritance

Basic Style Merging

React Native supports merging multiple styles using arrays, with later styles overriding earlier ones for conflicting properties.

Example Code

import React from 'react';
import { View, Text, StyleSheet } from 'react-native';

const App = () => {
  const baseStyle = { fontSize: 16 };
  const customStyle = { color: 'red', fontWeight: 'bold' };

  return (
    <View style={styles.container}>
      <Text style={[baseStyle, customStyle]}>Merged Styles</Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
});

export default App;

Analysis

  • [baseStyle, customStyle]: Merges styles, with customStyle overriding baseStyle for shared properties.
  • Result: The text is displayed with a 16-point font, red, and bold.

Text Style Inheritance

The Text component supports style inheritance, where child Text components inherit parent styles unless explicitly overridden.

Example Code

import React from 'react';
import { View, Text, StyleSheet } from 'react-native';

const App = () => {
  return (
    <View style={styles.container}>
      <Text style={styles.parentText}>
        Parent Text <Text style={styles.childText}>Child Text</Text>
      </Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
  parentText: { fontSize: 20, color: 'blue' },
  childText: { color: 'red' },
});

export default App;

Analysis

  • Inheritance: childText inherits fontSize: 20 from parentText.
  • Override: childText’s color: 'red' overrides the parent’s color: 'blue'.
  • Result: The child text is displayed with a 20-point font in red.

Common Style Merging Scenarios

  • Conditional Merging: Apply different styles based on conditions.
  • Style Reuse: Extract common styles as base styles.

Example Code

import React, { useState } from 'react';
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';

const App = () => {
  const [isActive, setIsActive] = useState(false);

  const baseButtonStyle = { padding: 10, borderRadius: 5 };
  const activeStyle = { backgroundColor: 'green' };
  const inactiveStyle = { backgroundColor: 'gray' };

  return (
    <View style={styles.container}>
      <TouchableOpacity
        style={[baseButtonStyle, isActive ? activeStyle : inactiveStyle]}
        onPress={() => setIsActive(!isActive)}
      >
        <Text style={styles.buttonText}>Toggle</Text>
      </TouchableOpacity>
    </View>
  );
};

const styles = StyleSheet.create({
  container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
  buttonText: { color: 'white', textAlign: 'center' },
});

export default App;

Analysis

  • Conditional Merging: Selects activeStyle or inactiveStyle based on isActive.
  • Result: The button displays green when active and gray when inactive.

Dynamic Styling

State-Based Dynamic Styles

Dynamically adjust styles based on state changes.

Example Code

import React, { useState } from 'react';
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';

const App = () => {
  const [isPressed, setIsPressed] = useState(false);

  return (
    <View style={styles.container}>
      <TouchableOpacity
        style={[styles.button, { backgroundColor: isPressed ? 'blue' : 'gray' }]}
        onPress={() => setIsPressed(!isPressed)}
      >
        <Text style={styles.buttonText}>Press Me</Text>
      </TouchableOpacity>
    </View>
  );
};

const styles = StyleSheet.create({
  container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
  button: { padding: 15, borderRadius: 5 },
  buttonText: { color: 'white' },
});

export default App;

Analysis

  • Dynamic Property: backgroundColor changes based on isPressed.
  • Real-Time Updates: State changes trigger UI re-rendering.

Generating Styles with Conditional Logic

Generate complex style objects using conditional logic.

Example Code

import React, { useState } from 'react';
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';

const App = () => {
  const [size, setSize] = useState('small');

  const getButtonStyle = () => ({
    padding: size === 'small' ? 10 : 20,
    backgroundColor: size === 'small' ? 'orange' : 'purple',
    borderRadius: 5,
  });

  return (
    <View style={styles.container}>
      <TouchableOpacity
        style={getButtonStyle()}
        onPress={() => setSize(size === 'small' ? 'large' : 'small')}
      >
        <Text style={styles.buttonText}>Change Size</Text>
      </TouchableOpacity>
    </View>
  );
};

const styles = StyleSheet.create({
  container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
  buttonText: { color: 'white', textAlign: 'center' },
});

export default App;

Analysis

  • getButtonStyle: Returns a dynamic style object based on size.
  • Flexibility: Functional style generation suits complex logic.

Dynamically Computed Style Properties

Generate style values through calculations, such as based on screen dimensions.

Example Code

import React from 'react';
import { View, Text, Dimensions, StyleSheet } from 'react-native';

const { width } = Dimensions.get('window');

const App = () => {
  return (
    <View style={styles.container}>
      <View style={{ width: width * 0.8, height: 100, backgroundColor: 'teal' }}>
        <Text style={styles.text}>Dynamic Width</Text>
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
  text: { color: 'white', textAlign: 'center', marginTop: 40 },
});

export default App;

Analysis

  • Dimensions.get('window'): Retrieves screen dimensions.
  • Computed Style: Width is set to 80% of the screen width.

Properties and Style Precedence

Inline vs. External Style Precedence

Inline styles (defined directly in the style prop) take precedence over externally defined styles.

Example Code

import React from 'react';
import { View, Text, StyleSheet } from 'react-native';

const App = () => {
  return (
    <View style={styles.container}>
      <Text style={[styles.text, { color: 'green' }]}>Inline Priority</Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
  text: { fontSize: 20, color: 'red' },
});

export default App;

Analysis

  • styles.text: Defines red color.
  • Inline Style: Overrides with green color.
  • Result: Text is displayed with a 20-point font in green.

Style Array Precedence Rules

In a style array, later styles override earlier ones for conflicting properties.

Example Code

import React from 'react';
import { View, Text, StyleSheet } from 'react-native';

const App = () => {
  const style1 = { fontSize: 16, color: 'blue' };
  const style2 = { color: 'orange' };

  return (
    <View style={styles.container}>
      <Text style={[style1, style2]}>Array Priority</Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
});

export default App;

Analysis

  • style1: Blue color.
  • style2: Orange color, overriding color.
  • Result: Text is displayed with a 16-point font in orange.

Component Property Precedence

Certain component properties may override styles, such as Text’s numberOfLines.

Example Code

import React from 'react';
import { View, Text, StyleSheet } from 'react-native';

const App = () => {
  return (
    <View style={styles.container}>
      <Text
        numberOfLines={1}
        style={{ width: 100, color: 'purple' }}
      >
        This is a very long text that should be truncated
      </Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
});

export default App;

Analysis

  • numberOfLines: Restricts text to one line, taking precedence over style width constraints.
  • Result: Text is truncated to a single line.

Comprehensive Case Study

Requirement Analysis

We aim to create a dynamic card component that:

  • Toggles size and color on tap.
  • Combines base and dynamic styles using style merging.
  • Ensures correct style precedence.

Code Implementation and Analysis

import React, { useState } from 'react';
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';

const App = () => {
  const [isLarge, setIsLarge] = useState(false);

  const baseCardStyle = {
    padding: 20,
    borderRadius: 10,
    alignItems: 'center',
  };

  const sizeStyle = {
    width: isLarge ? 200 : 100,
    height: isLarge ? 150 : 75,
  };

  const colorStyle = {
    backgroundColor: isLarge ? 'coral' : 'lightblue',
  };

  return (
    <View style={styles.container}>
      <TouchableOpacity
        style={[baseCardStyle, sizeStyle, colorStyle]}
        onPress={() => setIsLarge(!isLarge)}
      >
        <Text style={styles.cardText}>Tap to Toggle</Text>
      </TouchableOpacity>
    </View>
  );
};

const styles = StyleSheet.create({
  container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
  cardText: { color: 'white', fontSize: 16 },
});

export default App;

Analysis

  • Style Merging: baseCardStyle provides foundational styles, while sizeStyle and colorStyle handle dynamic adjustments.
  • Dynamic Styling: isLarge controls size and color.
  • Precedence: Later styles in the array override earlier ones for conflicting properties.

Advanced Techniques and Considerations

Performance Optimization Tips

  • Use StyleSheet.create: Caches style objects to avoid repeated computations.
const styles = StyleSheet.create({ key: { color: 'red' } });
  • Avoid Inline Styles: Inline styles create new objects on each render.
  • Memoize Dynamic Styles: Use useMemo to optimize complex dynamic styles.
const dynamicStyle = useMemo(() => ({
width: isLarge ? 200 : 100,
}), [isLarge]);

Debugging Style Techniques

  • Log Output: Use console.log(JSON.stringify(style)) to inspect styles.
  • React Native Debugger: Enable style inspection tools.
  • Visualize Boundaries: Temporarily add borderWidth: 1 to debug layouts.
Share your love