Last updated on

React to Jetpack Compose Dictionary

I’ve been trying out Jetpack Compose on a personal project and liking the API. Compose is a pretty big API shift, and I’ve found my React knowledge much more helpful than my Android knowledge. Perhaps this is how React Native developers come to replace native Android developers.

Many concepts and functions in the two libraries work the same but have different names. Here’s a compilation of terms I’ve seen along with explanations. Jetpack Compose is still changing, and this list is based on version 1.0.0-alpha02.

Children Prop > Children Composable

Both React and Compose refer to elements to be displayed inside another UI component as children.

React passes children by value, under a special prop named children.

function Container(props) {
  return <div>{props.children}</div>;
}

<Container>
  <span>Hello world!</span>
</Container>

Jetpack Compose passes composable functions as the functions themselves don’t return anything.

@Composable
fun Container(children: @Composable () -> Unit) {
  Box {
    children()
  }
}

Container {
  Text("Hello world"!)
}

Context > Ambient

While most data is passed through the component tree as props/parameters, sometimes this model can be cumbersome. React includes the Context API to share this data. Compose uses Ambient to accomplish the same thing.

createContext > ambientOf

A React Context is created with React.createContext, while a Jetpack Compose ambient is created with ambientOf.

Provider > Provider

The value can be controlled using a “Provider” in both React and Compose.

<MyContext.Provider value={myValue}>
  <SomeChild />
</MyContext.Provider>
Providers(MyAmbient provides myValue) {
  SomeChild()
}

useContext > Ambient.current

Accessing the React context value is accomplished using the useContext hook.

const myValue = useContext(MyContext);

Accessing the value of an Ambient is done by using the .current getter.

val myValue = MyAmbient.current

useEffect > onCommit

Mutations, subscriptions, timers, logging, and other side effects are not allowed inside the main body of a UI component. They must be placed inside a callback function that both React and Jetpack Compose will call at the correct point.

React has the useEffect hook to run side effects every render.

useEffect(() => {
  sideEffectRunEveryRender();
});

Compose has the onCommit effect to run side effects every composition.

onCommit {
  sideEffectRunEveryComposition()
}

Clean-up function > onDispose

Side effects often create resources that need to be cleaned up once the UI component is unused. React allows the useEffect function to return a second function, known as the clean-up function.

useEffect(() => {
  const subscription = source.subscribe();
  return () => {
    subscription.unsubscribe();
  };
});

Jetpack Compose exposes an onDispose function inside onCommit, which runs when the effect leaves the composition.

onCommit {
  val subscription = source.subscribe()
  onDispose {
    subscription.unsubscribe()
  }
}

useEffect(callback, deps) > onCommit(inputs, callback)

Both useEffect and onCommit default to running after every render/composition. However, side effects can also be run only when their dependencies change.

React allows a second parameter to be passed to useEffect with a list of dependencies:

useEffect(() => {
  sideEffect();
}, [dep1, dep2]);

Jetpack Compose allows inputs to be passed as parameters before the callback:

onCommit(input1, input2) {
  sideEffect()
}

useEffect(callback, []) > onActive(callback)

Side effects can also be run only when the UI component is first displayed.

When an empty dependency list is passed to useEffect, the side effect is only run once.

useEffect(() => {
  sideEffectOnMount();
}, []);

Jetpack Compose has a separate onActive effect for this.

onActive {
  sideEffectOnActive()
}

Hook > Effect

React lets you build your own hooks to extract component logic into reusable functions. They can use other hooks like useState and useEffect to encapsulate logic that relates to the component lifecycle.

function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }

    ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
    };
  });

  return isOnline;
}

In Jetpack Compose, @Composable functions are used as the equivalent of hooks (along with acting as the equivalent of Components). These composable functions, sometimes referred to as “effect” functions, usually start with a lowercase letter instead of an uppercase letter.

@Composable
fun friendStatus(friendID: String): State<Boolean?> {
  val isOnline = remember { mutableStateOf<Boolean?>(null) }

  onCommit {
    val handleStatusChange = { status: FriendStatus ->
      isOnline.value = status.isOnline
    }

    ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange)
    onDispose {
      ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange)
    }
  }

  return isOnline
}

Key Prop > Key Composable

Keys are used to help React and Jetpack Compose identify which items have changed, are added, or are removed. They must be unique among the list of items you display at that point in a UI component.

React has a special string prop named key.

<ul>
  {todos.map((todo) => (
    <li key={todo.id}>{todo.text}</li>
  ))}
</ul>

Jetpack Compose has a special utility composable called key that can take any input.

Column {
  for (todo in todos) {
    key(todo.id) { Text(todo.text) }
  }
}

Multiple inputs can be passed in to key in Compose, while React requires a single string (although multiple strings could be concatenated).

.map > For Loop

Since React passes elements by value, elements corresponding to an array are usually created using array.map(). The returned elements in the map callback can be embedded in JSX.

function NumberList(props) {
  return (
    <ul>
      {props.numbers.map((number) => (
        <ListItem value={number} />
      ))}
    </ul>
  );
}

Composable UI functions in Jetpack Compose emit other UI composables and don’t return anything. As a result, a simple for loop can be used instead of .map().

@Composable
fun NumberList(numbers: List<Int>) {
  Column {
    for (number in numbers) {
      ListItem(value = number)
    }
  }
}

In fact, any iteration method can be used, such as .forEach().

useMemo > remember

React allows values to be re-computed only if certain dependencies change inside a component through the useMemo hook.

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

Jetpack Compose has a similar function named remember that only re-computes a value if the inputs change.

val memoizedValue = remember(a, b) { computeExpensiveValue(a, b) }

React Component > Composable

In React, Components are used to split the UI into independent and reusable pieces. They can come in the form of a function that takes a props parameter and returns a React node.

function Greeting(props) {
  return <span>Hello {props.name}!</span>;
}

In Jetpack Compose, Composable functions are building blocks used to split the UI into independent and reusable pieces. They are functions with a @Composable annotation that can take any number of parameters.

@Composable
fun Greeting(name: String) {
  Text(text = "Hello $name!")
}

Both libraries also refer to these concepts as UI components. However, Jetpack Compose also uses composable functions for other functionality, see hook > effect and key for examples.

Render > Composition

Once data has changed inside a UI component, the library must adjust what is presented on screen. React refers to this as rendering, while Jetpack Compose refers to this as composition.

Reconciler > Composer

Internally React needs to figure out what changes when a component is rendered. This diffing algorithm is called the “Reconciler”. React Fiber referred to the release of the new Fiber Reconciler which replaced the old algorithm.

Jetpack Compose’s diffing is done using the Composer. It determines how nodes change every time a composable completes composition.

State > State

Both React and Compose refer to local variables you want to mutate as “state”.

useState > state

Creating a new state variable in React is done with the useState hook. It returns a tuple with the value and a setter.

const [count, setCount] = useState(0);

<button onClick={() => setCount(count + 1)}>
  You clicked {count} times
</button>

Compose uses the mutableStateOf function to return a MutableState object, which contains a variable with a getter and setter.

val count = remember { mutableStateOf(0) }

Button(onClick = { count.value++ }) {
  Text("You clicked ${count.value} times")
}

MutableState contains componentN() functions, allowing you to destructure the getter and setter just like React.

val (count, setCount) = remember { mutableStateOf(0) }

Button(onClick = { setCount(count + 1) }) {
  Text("You clicked ${count} times")
}

To avoid recomputing the initial state, mutableStateOf is usually wrapped with the remember function.

setState updater function > Snapshot

When updating state in a React component based on a previous value, you can pass an “updater” function that receives the current state as a function argument.

class Button extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }

  render() {
    <button onClick={() => this.setState(state => ({ count: state.count + 1 }))}>
      You clicked {this.state.count} times
    </button>
  }
}

In Jetpack Compose, this behaviour is encapsulated in a concept called snapshots. A snapshot represents state values at a certain time. The Snapshot class exposes an enter function to run an updater callback. Inside the callback, all state objects return the value they had when the snapshot was taken.

Storybook > Preview

The Storybook tool helps you preview React components on the web independently by creating “stories”. The @Preview annotation in Jetpack Compose lets you build example composables that can be previewed in Android Studio.

Ternary Operator > If Statement

React components often use the ternary operator (cond ? a : b) to conditionally render components, as the successful branch is returned (unlike if statements in JavaScript).

function Greeting(props) {
  return (
    <span>
      {props.name != null
        ? `Hello ${props.name}!`
        : 'Goodbye.'}
    </span>
  );
}

Kotlin doesn’t have ternary operators as if statements do return the result of the successful branch. Since if statements in Kotlin act like ternary operators in JavaScript, there is no need for a second variation.

@Composable
fun Greeting(name: String?) {
  Text(text = if (name != null) {
    "Hello $name!"
  } else {
    "Goodbye."
  })
}