From 39a075cdf83adc00a31b305ac2463ceaa944f044 Mon Sep 17 00:00:00 2001 From: Trond Bergquist Date: Thu, 25 Apr 2019 10:05:04 +0200 Subject: [PATCH] Remove withContext HOC use HotelsFormContext as hook --- app/core/src/screens/hotelsStack/Homepage.js | 183 +++++++----------- .../src/screens/hotelsStack/HotelsForm.js | 155 +++++++-------- .../screens/hotelsStack/HotelsFormContext.js | 10 +- .../hotelsStack/placepicker/HotelCityItem.js | 80 ++++---- .../hotelsStack/placepicker/HotelCityList.js | 2 +- packages/shared/index.js | 1 - .../withContext/__tests__/withContext.test.js | 86 -------- .../shared/src/withContext/withContext.js | 52 ----- 8 files changed, 177 insertions(+), 392 deletions(-) delete mode 100644 packages/shared/src/withContext/__tests__/withContext.test.js delete mode 100644 packages/shared/src/withContext/withContext.js diff --git a/app/core/src/screens/hotelsStack/Homepage.js b/app/core/src/screens/hotelsStack/Homepage.js index 754c0c29..2339fe1b 100644 --- a/app/core/src/screens/hotelsStack/Homepage.js +++ b/app/core/src/screens/hotelsStack/Homepage.js @@ -13,26 +13,12 @@ import { DateFormatter } from '@kiwicom/mobile-localization'; import PlacepickerModal from './placepicker/PlacepickerModal'; import HotelsForm from './HotelsForm'; import { - withHotelsFormContext, + HotelsFormContext, type HotelsFormContextType, } from './HotelsFormContext'; type Props = {| +navigation: NavigationType, - +cityName: string, - +cityId: string, - +checkin: Date, - +checkout: Date, - +coordinates: {| - +lat: number, - +lng: number, - |}, - +adultsCount: number, - +childrenCount: Array<{| +age: number |}>, -|}; - -type State = {| - showPlacepicker: boolean, |}; function Section({ children }: { children: React.Node }) { @@ -43,29 +29,20 @@ function Section({ children }: { children: React.Node }) { return {children}; } -class Homepage extends React.Component { - static navigationOptions = { - headerTitle: ( - - - - ), - }; - - state = { - showPlacepicker: false, - }; - - goToNewHotelsPage = () => { - const { - cityId, - cityName, - checkin, - checkout, - adultsCount, - childrenCount: children, - } = this.props; - this.props.navigation.navigate('NewHotelsPackage', { +const Homepage = (props: Props) => { + const [showPlacepicker, setShowPlacepicker] = React.useState(false); + const { + cityName, + cityId, + checkin, + checkout, + coordinates: { lat, lng }, + adultsCount, + children, + }: HotelsFormContextType = React.useContext(HotelsFormContext); + + function goToNewHotelsPage() { + props.navigation.navigate('NewHotelsPackage', { cityId, cityName, currency: 'EUR', @@ -73,18 +50,10 @@ class Homepage extends React.Component { checkout: DateFormatter(checkout).formatForMachine(), roomsConfiguration: [{ adultsCount, children }], }); - }; + } - goToStay22HotelsPage = () => { - const { - coordinates: { lat, lng }, - cityName, - checkin, - checkout, - adultsCount, - childrenCount: children, - } = this.props; - this.props.navigation.navigate('NewHotelsPackage', { + function goToStay22HotelsPage() { + props.navigation.navigate('NewHotelsPackage', { cityName, currency: 'EUR', checkin: DateFormatter(checkin).formatForMachine(), @@ -92,74 +61,60 @@ class Homepage extends React.Component { roomsConfiguration: [{ adultsCount, children }], coordinates: { latitude: lat, longitude: lng }, }); - }; - - goToSingleHotel = () => { - this.props.navigation.navigate('SingleHotelPackage'); - }; - - togglePlacepicker = () => { - this.setState(state => ({ - showPlacepicker: !state.showPlacepicker, - })); - }; + } - render() { - return ( - - -
- -
-
- } - testID="homePage__Hotels-button" - onPress={this.goToNewHotelsPage} - /> -
+ function goToSingleHotel() { + props.navigation.navigate('SingleHotelPackage'); + } -
- } - onPress={this.goToStay22HotelsPage} - /> -
+ function togglePlacepicker() { + setShowPlacepicker(show => !show); + } -
- } - onPress={this.goToSingleHotel} - /> -
- + +
+ +
+
+ } + testID="homePage__Hotels-button" + onPress={goToNewHotelsPage} /> - - - ); - } -} +
-const select = ({ - cityName, - cityId, - checkin, - checkout, - coordinates, - adultsCount, - children, -}: HotelsFormContextType) => ({ - cityName, - cityId, - checkin, - checkout, - coordinates, - adultsCount, - childrenCount: children, -}); +
+ } + onPress={goToStay22HotelsPage} + /> +
-export default withHotelsFormContext(select)(Homepage); +
+ } + onPress={goToSingleHotel} + /> +
+ +
+
+ ); +}; + +Homepage.navigationOptions = { + headerTitle: ( + + + + ), +}; + +export default Homepage; diff --git a/app/core/src/screens/hotelsStack/HotelsForm.js b/app/core/src/screens/hotelsStack/HotelsForm.js index e6b131ae..a9f97f60 100644 --- a/app/core/src/screens/hotelsStack/HotelsForm.js +++ b/app/core/src/screens/hotelsStack/HotelsForm.js @@ -12,88 +12,86 @@ import { import { DateUtils } from '@kiwicom/mobile-localization'; import { - withHotelsFormContext, + HotelsFormContext, type HotelsFormContextType, } from './HotelsFormContext'; type Props = {| - +cityName: string, +togglePlacepicker: () => void, - +checkin: Date, - +checkout: Date, - +onCheckinChange: Date => void, - +onCheckoutChange: Date => void, - +onAdultsChange: number => void, - +adultsCount: number, - +childrenCount: number, - +onChildrenChange: boolean => void, |}; -class HotelsForm extends React.Component { - incrementAdults = () => { - this.props.onAdultsChange(1); - }; +export default function HotelsForm(props: Props) { + const { + cityName, + checkin, + checkout, + adultsCount, + children: childrenCount, + actions: { onCheckinChange, onCheckoutChange, setAdults, setChildren }, + }: HotelsFormContextType = React.useContext(HotelsFormContext); - decrementAdults = () => { - this.props.onAdultsChange(-1); - }; + function incrementAdults() { + setAdults(1); + } - incrementChildren = () => { - this.props.onChildrenChange(true); - }; + function decrementAdults() { + setAdults(-1); + } - decrementChildren = () => { - this.props.onChildrenChange(false); - }; + function incrementChildren() { + setChildren(true); + } - render() { - return ( - <> - - } - onPress={this.props.togglePlacepicker} - type="secondary" - /> - - - - - - - - - - - - - + + } + onPress={props.togglePlacepicker} + type="secondary" + /> + + + + - - + + + - - ); - } + + + + + + + + + ); } const styles = StyleSheet.create({ @@ -106,24 +104,3 @@ const styles = StyleSheet.create({ marginEnd: 5, }, }); - -const select = ({ - cityName, - checkin, - checkout, - adultsCount, - children, - actions: { onCheckinChange, onCheckoutChange, setAdults, setChildren }, -}: HotelsFormContextType) => ({ - cityName, - checkin, - checkout, - onCheckinChange, - onCheckoutChange, - onAdultsChange: setAdults, - onChildrenChange: setChildren, - adultsCount, - childrenCount: children.length, -}); - -export default withHotelsFormContext(select)(HotelsForm); diff --git a/app/core/src/screens/hotelsStack/HotelsFormContext.js b/app/core/src/screens/hotelsStack/HotelsFormContext.js index 52cab1b2..d13c3d35 100644 --- a/app/core/src/screens/hotelsStack/HotelsFormContext.js +++ b/app/core/src/screens/hotelsStack/HotelsFormContext.js @@ -2,7 +2,6 @@ import * as React from 'react'; import { DateUtils } from '@kiwicom/mobile-localization'; -import { withContext } from '@kiwicom/mobile-shared'; type Props = {| +children: React.Node, @@ -59,9 +58,9 @@ const defaultState = { }, }; -const { Consumer, Provider: ContextProvider } = React.createContext( - defaultState, -); +export const HotelsFormContext = React.createContext(defaultState); + +const { Provider: ContextProvider } = HotelsFormContext; export default class Provider extends React.Component { constructor() { @@ -118,7 +117,4 @@ export default class Provider extends React.Component { } } -export const withHotelsFormContext = (select: (state: State) => Object) => - withContext(select, Consumer); - export type HotelsFormContextType = State; diff --git a/app/core/src/screens/hotelsStack/placepicker/HotelCityItem.js b/app/core/src/screens/hotelsStack/placepicker/HotelCityItem.js index 836d0361..bf1f9bee 100644 --- a/app/core/src/screens/hotelsStack/placepicker/HotelCityItem.js +++ b/app/core/src/screens/hotelsStack/placepicker/HotelCityItem.js @@ -7,39 +7,42 @@ import { defaultTokens } from '@kiwicom/mobile-orbit'; import type { HotelCityItem_data as City } from './__generated__/HotelCityItem_data.graphql'; import { - withHotelsFormContext, + HotelsFormContext, type HotelsFormContextType, - type SaveCity, } from '../HotelsFormContext'; type Props = {| - +data: City, + +data: ?City, +onPress: () => void, - +setCity: SaveCity => void, |}; -class HotelCityItem extends React.Component { - onPress = () => { - const { data } = this.props; - this.props.setCity({ - cityId: data.id, - cityName: data.name ?? '', - coordinates: { - lng: data.location?.lng ?? Number.MAX_SAFE_INTEGER, - lat: data.location?.lat ?? Number.MAX_SAFE_INTEGER, - }, - }); - this.props.onPress(); - }; +const HotelCityItem = (props: Props) => { + const { + actions: { setCity }, + }: HotelsFormContextType = React.useContext(HotelsFormContext); - render() { - return ( - - - - ); + function onPress() { + const { data } = props; + const cityId = data?.id; + if (cityId != null) { + setCity({ + cityId, + cityName: data?.name ?? '', + coordinates: { + lng: data?.location?.lng ?? Number.MAX_SAFE_INTEGER, + lat: data?.location?.lat ?? Number.MAX_SAFE_INTEGER, + }, + }); + props.onPress(); + } } -} + + return ( + + + + ); +}; const styles = StyleSheet.create({ row: { @@ -50,22 +53,15 @@ const styles = StyleSheet.create({ }, }); -const select = ({ actions: { setCity } }: HotelsFormContextType) => ({ - setCity, -}); - -export default createFragmentContainer( - withHotelsFormContext(select)(HotelCityItem), - { - data: graphql` - fragment HotelCityItem_data on HotelCity { - id - name - location { - lat - lng - } +export default createFragmentContainer(HotelCityItem, { + data: graphql` + fragment HotelCityItem_data on HotelCity { + id + name + location { + lat + lng } - `, - }, -); + } + `, +}); diff --git a/app/core/src/screens/hotelsStack/placepicker/HotelCityList.js b/app/core/src/screens/hotelsStack/placepicker/HotelCityList.js index 1ec6d2d7..9684e33f 100644 --- a/app/core/src/screens/hotelsStack/placepicker/HotelCityList.js +++ b/app/core/src/screens/hotelsStack/placepicker/HotelCityList.js @@ -20,7 +20,7 @@ type ItemType = {| class HotelCityList extends React.Component { keyExtractor = (item: ItemType) => item.node?.id; - renderItem = ({ item }: { item: ItemType }) => ( + renderItem = ({ item }) => ( ); diff --git a/packages/shared/index.js b/packages/shared/index.js index a0519f3e..922fb388 100644 --- a/packages/shared/index.js +++ b/packages/shared/index.js @@ -127,7 +127,6 @@ export { default as WithStorage } from './src/WithStorage'; export { default as WithNativeNavigation } from './src/WithNativeNavigation'; export { default as WithStandaloneScreen } from './src/WithStandaloneScreen'; export { default as PassBook } from './src/PassBook'; -export { default as withContext } from './src/withContext/withContext'; // Flow types: diff --git a/packages/shared/src/withContext/__tests__/withContext.test.js b/packages/shared/src/withContext/__tests__/withContext.test.js deleted file mode 100644 index 0141f34d..00000000 --- a/packages/shared/src/withContext/__tests__/withContext.test.js +++ /dev/null @@ -1,86 +0,0 @@ -// @flow - -import * as React from 'react'; -import renderer from 'react-test-renderer'; - -import withContext from '../withContext'; - -const defaultValue = { - count: 0, - note: '', - incrementCount: () => {}, - setNote: () => {}, -}; -const { Provider: ContextProvider, Consumer } = React.createContext( - defaultValue, -); - -type State = {| - count: number, - note: string, - +incrementCount: () => void, - +setNote: (note: string) => void, -|}; - -type Props = {| - +children: React.Node, -|}; - -class Provider extends React.Component { - constructor() { - super(); - - this.state = { - ...defaultValue, - incrementCount: this.incrementCount, - setNote: this.setNote, - }; - } - - incrementCount = () => this.setState(state => ({ count: state.count + 1 })); - - setNote = (note: string) => this.setState({ note }); - - render() { - return ( - - {this.props.children} - - ); - } -} - -const myWithContext = (select: State => Object) => - withContext(select, Consumer); - -type CountProps = {| - +count: number, - +incrementCount: () => void, - +setNote: (text: string) => void, -|}; - -class MyComponent extends React.Component { - render() { - return null; - } -} -const MyComponentWithContext = myWithContext( - ({ count, incrementCount, setNote }) => ({ count, incrementCount, setNote }), -)(MyComponent); - -describe('withContext', () => { - it('injects only the props from the select function', () => { - const wrapper = renderer.create( - - - , - ); - - const WrappedComponent = wrapper.root.findByType(MyComponent); - - expect(WrappedComponent.props.count).toBe(0); - expect(WrappedComponent.props.note).toBeUndefined(); - expect(typeof WrappedComponent.props.incrementCount).toEqual('function'); - expect(typeof WrappedComponent.props.setNote).toEqual('function'); - }); -}); diff --git a/packages/shared/src/withContext/withContext.js b/packages/shared/src/withContext/withContext.js deleted file mode 100644 index 867f7e64..00000000 --- a/packages/shared/src/withContext/withContext.js +++ /dev/null @@ -1,52 +0,0 @@ -// @flow - -import * as React from 'react'; -import isEqual from 'react-fast-compare'; - -export default function withContext( - select: T => Object, - Consumer: React.ComponentType | React.StatelessFunctionalComponent, -) { - return (Component: React.ElementType) => { - class WithShouldComponentUpdate extends React.Component<{| - ...T, - +children: React.Element, - |}> { - /** - * All components that subscribes to context would re render even when props they are not subsrcibing - * too changes. This shouldComponentUpdate should make the subscriber only re render when its - * props are changing - */ - shouldComponentUpdate(nextProps: T) { - return !isEqual(nextProps, this.props); - } - - render() { - const children = React.cloneElement( - this.props.children, - (this.props: Object), - ); - return children; - } - } - - class WithContext extends React.Component { - // $FlowExpectedError: We need to pass on the navigationOptions if any, flow does not know about it, but a react component might have it - static navigationOptions = Component.navigationOptions; - renderInner = (state: T) => { - const stateProps = select(state); - return ( - - - - ); - }; - - render() { - return {this.renderInner}; - } - } - - return WithContext; - }; -}