Hooks

By convention, all hooks functions will start with the prefix use.

Before you start to try hooks, remember 2 rules:

  • Only call hooks in a component or your own hook.
  • Do not call hooks by conditions, in a loop, or a callback.

We have not yet developed a static code analysis tool like ESint, so there is no way to check it automatically. So if you found some weird state problem, maybe you should check did you violate the hook rules.

For more information, you can refer to Rules of Hooks - React

tip

Resquare has tried the best to make the hooks' behavior the same as React as possible. Therefore, most of the React hooks document content can also apply to Resquare.

Basic Hooks#

useState#

useState hooks allow you to store a value in the component, and the value will be kept until the component being unmounted or updated by using a setter.

Invoke useState<T>(initialState: T) will return a current value and setter pair. If it is the first time to call it, the value will be set as initialValue.

val toggleButtonComponent = declareComponent {
val (isEnabled, setIsEnabled) = useState(false)
div(DivProps(
item = itemStackOf(if (isEnabled) Material.GREEN_WOOL else Material.RED_WOOL),
onClick = { setIsEnabled(!isEnabled) }
))
}

useStateLazy#

useStateLazy<T>(initialValueFactor: () -> T) is basically same as useState. The different part is that instead of providing an initial value, you will need to provide an initialValueFactory, and the factory will only be called once when initializing. Use only if your initial value.

val toggleButtonComponent = declareComponent {
val (itemStack, setItemStack) = useStateLazy { ItemStack(Material.APPLE) }
// ...
}

useMemo#

useMemo<T>(valueFactory: () -> T, deps: Array<Any>) is a memorization function. It will run the valueFactory and memorize the result, and it will update the value once any reference is changed in deps array. You have to put all factory used variables in the deps array to update the result when the variable change. It is useful if you want to avoid recalculate the value in every render. Also, you can use it to avoid memo render.

data class PlayerListComponentProps(
val players: List<Player>,
)
val playerListComponent = declareComponent { props: PlayerListComponentProps ->
val hungryPlayers = useMemo({ props.playes.filter { it.foodLevel < 5 } }, arrayOf(props.players))
// ...
}

useEffect#

useEffect(content: () -> (() -> Unit)?, deps: Array<Any>?) will execute the content after render complete. Inside the content, you can make some side effect, and return a side effect cleaner, so that the component will execute the cleaner function when the component unmounts or effect update. If the side effect does not need to clean, you can return null instead.

Like useMemo, the effect will be updated once any element's reference in deps changed. If you provide an empty deps, which means the effect will keep unchanged until the component unmount. If you do not provide deps, the effect will be updated in each render.

val coinMarketComponent = declareComponent { props ->
val (coinPrice, setCoinPrice) = useState<Int?>(null)
useEffect({
val subscription = CoinMarketService.coinPrice[props.coinType]
.subscribe{ price -> setCoinPrice(it) };
{ subscription.dispose() } // unsubscribe when clean
}, arrayOf(props.coinType)) // resubscribe when coinType changed
// ...
}

useCallback#

useCallback<T>(callback: T, deps: Array<Any?>) is equal to useMemo({ callback }, deps). Useful when you want to avoid a memo component render.

val storeComponent = declareComponent {
val handleBuy = useCallback ({ e: ShopItem -> doSomething(e) }, arrayOf())
buyButtonComponent(BuyButtonComponentProps(
onBuy = handleBuy
))
}

useInterval#

useInterval(interval: Long) will cause rendering each interval Bukkit ticks and return how many intervals passed. Great for make some animation.

val clockComponent = declareComponent {
val passedInterval = useInterval(10L)
// ...
}

useRootContainer#

useRootContainer() will return the root container of current root ui.

useCancelRawEvent#

useCancelRawEvent() will add a click listener and a drag listener in capture phase, and do preventDefault(). i.e., all direct modifications in inventory from players will be canceled.

caution

This is a UI container level hooks, which means it will apply to the whole UI instead of only the current component.

RxJava Hooks#

If you are not intended to use RxJava, you can skip this section.

If you need to add some extra operator or error handling, you should use useMemo to modify your original observable before passing it to the following hooks.

useObservable#

useObservable<T>(observable: Observable<T>) will subscribe the observable with useEffect, and return the last value, null will be returned before first value received.

useSingle#

useSingle<T>(single: Single<T>) Single version of useObservable.

useCompletable#

useCompletable(completable: Completable) will subscribe the completable with useEffect, and return true when completed.

useBehaviourSubject#

useBehaviourSubject<T>(behaviorSubject: BehaviorSubject<T>) will return a pair of current value and setter, which is similar to useState. Since this hook can act like two-way binding, we strongly recommend using BehaviourSubject to bind your data fields.

data class GameRoom(
val readyState: Map<Player, BehaviourSubject<Boolean>>
)
data class ReadyButtonComponentProps(
val gameRoom: GameRoom,
val player: Player,
)
val readyButtonComponent = declareComponent { props: ReadyButtonComponentProps ->
val (playerReady, setPlayerReady) = useBehaviourSubject(props.gameRoom.readyState[props.player]!!)
div(DivProps(
style = styles.readyButton,
item = ItemStack(if(playerReady) Material.GREEN_WOOL else Material.RED_WOOL),
onClick = { setPlayerReady(!playerReady) }
))
}