Created
November 24, 2018 09:55
-
-
Save littlemooon/1e63fcd7510e62b0eb44ea84b388aac5 to your computer and use it in GitHub Desktop.
Country context state component
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import to from 'await-to-js'; | |
import countries from 'common/config/world-countries.js'; | |
import { waitUntilResolved } from 'common/lib/fetch-helpers'; | |
import { formatOperatorResponse } from 'common/lib/operator'; | |
import { | |
getStorageItem, | |
removeStorageItem, | |
setStorageItem, | |
StorageKey, | |
} from 'common/lib/storage'; | |
import { Country } from 'common/types'; | |
import * as React from 'react'; | |
import { apiClient } from '../lib/api-client'; | |
export enum CountryStateStatus { | |
LOADING = 'LOADING', | |
PARTIAL = 'PARTIAL', | |
SUCCESS = 'SUCCESS', | |
ERROR = 'ERROR', | |
} | |
export interface CountryState { | |
country?: Country; | |
isCountryRoute: boolean; | |
shouldStore: boolean; | |
error?: Error; | |
status: CountryStateStatus; | |
setCountry: (c?: Country) => void; | |
} | |
interface CountryStateProps { | |
pathLast?: string; | |
children: (props: { countryState: CountryState }) => React.ReactNode; | |
} | |
const noop = () => {}; | |
const defaultState: CountryState = { | |
status: CountryStateStatus.LOADING, | |
isCountryRoute: false, | |
shouldStore: true, | |
setCountry: noop, | |
}; | |
export const CountryStateContext = React.createContext<CountryState>( | |
defaultState | |
); | |
function isValidCountry(country?: Country) { | |
return ( | |
!country || | |
Boolean(countries.find((c: Country) => c.alpha2 === country.alpha2)) | |
); | |
} | |
function hasChanged(prop: string, oldCountry?: Country, newCountry?: Country) { | |
return (newCountry && newCountry[prop]) !== (oldCountry && oldCountry[prop]); | |
} | |
export class CountryStateProvider extends React.Component< | |
CountryStateProps, | |
CountryState | |
> { | |
state = defaultState; | |
fetchCountryLookup = waitUntilResolved<void>(async () => { | |
const [error, countryRes] = await to<Country | undefined, Error>( | |
apiClient.fetch('/lookup_country', {}) | |
); | |
if (error) { | |
this.setCountry(countries.find(c => c.alpha2 === 'XI')); | |
} else if (countryRes && countryRes.alpha2) { | |
this.setCountry(countryRes); | |
} | |
}); | |
fetchCountryProducts = waitUntilResolved<Country | undefined>( | |
async country => { | |
const query = | |
country && country.alpha2 !== 'XI' | |
? { country: country.alpha2 } | |
: undefined; | |
const [error, countryRes] = await to<Country | undefined, Error>( | |
apiClient.fetch('/accounts/products', { query }) | |
); | |
if (error) { | |
this.setState({ | |
error, | |
status: CountryStateStatus.ERROR, | |
}); | |
} else if (countryRes && countryRes.alpha2) { | |
const { operators = [], popular = [], name = '' } = countryRes; | |
this.setCountry( | |
{ | |
...countryRes, | |
operators: operators.map(o => formatOperatorResponse(o, name)), | |
popular: popular.map(o => formatOperatorResponse(o)), | |
}, | |
{ | |
status: CountryStateStatus.SUCCESS, | |
} | |
); | |
} | |
} | |
); | |
componentDidMount = () => { | |
this.getInitialCountry(); | |
}; | |
getInitialCountry() { | |
const { pathLast } = this.props; | |
const storedCountry = getStorageItem(StorageKey.STATE_COUNTRY); | |
const pathCountry = countries.find( | |
(c: Country) => c.name.toLowerCase().replace(/ /g, '-') === pathLast | |
); | |
if (pathCountry) { | |
this.setCountry(pathCountry, { | |
isCountryRoute: true, | |
shouldStore: false, | |
}); | |
} else if (storedCountry && isValidCountry(storedCountry)) { | |
this.setCountry(storedCountry); | |
} else { | |
this.fetchCountryLookup(); | |
} | |
} | |
setCountry = ( | |
newCountry?: Country, | |
otherState: Partial<CountryState> = {} | |
) => { | |
const { country } = this.state; | |
if ( | |
hasChanged('alpha2', country, newCountry) || | |
hasChanged('slug', country, newCountry) || | |
Boolean(Object.keys(otherState).length) | |
) { | |
this.setState( | |
state => { | |
const newState: CountryState = { | |
...state, | |
country: newCountry, | |
...otherState, | |
}; | |
if (newState.shouldStore) { | |
if (newCountry) { | |
setStorageItem(StorageKey.STATE_COUNTRY, newCountry); | |
} else { | |
removeStorageItem(StorageKey.STATE_COUNTRY); | |
} | |
} | |
return newState; | |
}, | |
() => this.fetchCountryProducts(newCountry) | |
); | |
} | |
}; | |
render() { | |
const countryState = { | |
...this.state, | |
setCountry: this.setCountry, | |
}; | |
return ( | |
<CountryStateContext.Provider value={countryState}> | |
{this.props.children({ countryState })} | |
</CountryStateContext.Provider> | |
); | |
} | |
} | |
export const withCountryState = <P extends object>( | |
Component: React.ComponentType<P & { countryState: CountryState }> | |
) => | |
class WithCountryState extends React.Component<P> { | |
render() { | |
return ( | |
<CountryStateContext.Consumer> | |
{(countryState: CountryState) => ( | |
<Component countryState={countryState} {...this.props} /> | |
)} | |
</CountryStateContext.Consumer> | |
); | |
} | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export function waitUntilResolved<T>(fn: (arg: T) => Promise<any>) { | |
let waiting = false; | |
return async (arg: T) => { | |
if (waiting) { | |
return; | |
} | |
waiting = true; | |
await fn(arg); | |
waiting = false; | |
}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment