Skip to content

Instantly share code, notes, and snippets.

@mrousavy
Last active August 1, 2023 13:16
Show Gist options
  • Save mrousavy/bc3b748c56330bec09f128ecb4be0acb to your computer and use it in GitHub Desktop.
Save mrousavy/bc3b748c56330bec09f128ecb4be0acb to your computer and use it in GitHub Desktop.
`useStyle` - a typed `useMemo` for styles which also includes style flattening and searching
import { DependencyList, useMemo } from "react";
import {
ImageStyle,
RegisteredStyle,
StyleProp,
StyleSheet,
TextStyle,
ViewStyle,
} from "react-native";
/**
* A hook to memoize a style. Uses `ViewStyle` per default, but can be used with other styles deriving from `FlexStyle` as well, such as `TextStyle`.
* @param styleFactory The function that returns a style
* @param deps The dependencies to trigger memoization re-evaluation
* @example
*
* const style = useStyle(() => ({ height: someDynamicValue }), [someDynamicValue])
*/
export const useStyle = <
TStyle extends ViewStyle | TextStyle | ImageStyle,
TOutput extends StyleProp<TStyle>
>(
styleFactory: () => TOutput,
deps?: DependencyList
): TOutput =>
// eslint-disable-next-line react-hooks/exhaustive-deps
useMemo(styleFactory, deps);
/**
* A hook to memoize a style and flatten it into a single object. Uses `ViewStyle` per default, but can be used with other styles deriving from `FlexStyle` as well, such as `TextStyle`.
* @param styleFactory The function that returns a style
* @param deps The dependencies to trigger memoization re-evaluation
* @example
*
* const style = useStyle(() => ({ height: someDynamicValue }), [someDynamicValue])
*/
export const useFlatStyle = <
TStyle extends ViewStyle | TextStyle | ImageStyle,
TOutput extends StyleProp<TStyle>
>(
styleFactory: () => TOutput,
deps?: DependencyList
): TStyle extends (infer U)[] ? U : TStyle =>
// eslint-disable-next-line react-hooks/exhaustive-deps
useMemo(() => StyleSheet.flatten(styleFactory()), deps);
const isRegisteredStyle = <T>(
style: T | unknown
): style is RegisteredStyle<T> => {
if (typeof style === "object" && style != null)
return "__registeredStyleBrand" in style;
else return false;
};
/**
* Find a specific value in the given style
* @param style The style to search the given key in
* @param stylePropertyKey The style property to search for
* @returns The value of the found style property, or `undefined` if not found
*/
export const findStyle = <
TStyle extends ViewStyle | TextStyle | ImageStyle,
TResult extends TStyle extends (infer U)[] ? U : TStyle,
TName extends keyof TResult
>(
style: StyleProp<TStyle>,
stylePropertyKey: TName
): TResult[TName] | undefined => {
if (Array.isArray(style)) {
// we're doing a reverse loop because values in elements at the end override values at the beginning
for (let i = style.length - 1; i >= 0; i--) {
const result = findStyle<TStyle, TResult, TName>(
// @ts-expect-error it's complaining because it is `readonly`, but we're not modifying it anyways. StyleProp<T>::RecursiveArray<T> needs to be readonly.
style[i],
stylePropertyKey
);
if (result != null) return result;
}
// style not found in array
return undefined;
} else {
if (style == null) {
// null, undefined
return undefined;
} else if (typeof style === "boolean") {
// false
return undefined;
} else if (isRegisteredStyle(style)) {
// RegisteredStyle<T> (number) - does not actually exist.
// @ts-expect-error typings for StyleProp<> are really hard
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return style.__registeredStyleBrand[stylePropertyKey];
} else if (typeof style === "object") {
// { ... }
// @ts-expect-error typings for StyleProp<> are really hard
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return style[stylePropertyKey];
} else {
// it's not a known style type.
return undefined;
}
}
};
@a-eid
Copy link

a-eid commented Apr 14, 2021

what's the difference btw useStyle & useFlatStyle, is it okay to use Arrays as styles ?

@mrousavy
Copy link
Author

what's the difference btw useStyle & useFlatStyle

@a-eid useStyle is the same as a useMemo, useFlatStyle uses StyleSheet.flatten, so if you pass an array of styles it flattens all those styles into a single style object.

@a-eid
Copy link

a-eid commented Apr 14, 2021

would these 2 examples be valid use cases for useFlatStyle & useStyle

function Cmp({ insets }){

  const viewStyle = useFlatStyle(() => [styles.view, { marginBottom: insets.bottom }], [insets.bottom])
  
  return <View style={viewStyle}/>
}
function Cmp({ insets }){

  const viewStyle = useStyle(() => ({ marginBottom: insets.bottom }), [insets.bottom])
  
  return <View style={viewStyle}/>
}

@mrousavy
Copy link
Author

Yes, but if you also use an array in the useStyle example you wouldn't be able to do viewStyle.marginBottom to find out the bottom margin. With useFlatStyle you would, since it merges the two styles into one.

@a-eid
Copy link

a-eid commented Apr 14, 2021

@mrousavy I'm not sure I understand, could u please give an example,

Edit:

aha I get it now

  const viewStyle = useStyle(() => [styles.view, { marginBottom: insets.bottom }], [insets.bottom])
  console.log(viewStyle.marginBottom) // undefined 
  // you need to use 
  console.log(viewStyle[1].marginBottom) // correctly logs insets.bottom 

  
  const viewStyle = useFlatStyle(() => [styles.view, { marginBottom: insets.bottom }], [insets.bottom])
  console.log(viewStyle.marginBottom) // correctly logs insets.bottom  

that's what you meant right ^^ ( sorry it took me sometimes to understand 😑 ).

I'm not sure when would I need to access a property on the style object thou.

@mrousavy
Copy link
Author

yes that's what I meant. I needed this for a custom view that did some special processing when the user passes a style with borderRadius, that's why I originally created it. I also thought that it might be faster (performance wise), but that has to be benchmarked. (not sure if Yoga natively supports arrays for style)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment