Skip to content

Instantly share code, notes, and snippets.

@nickoneill
Created November 24, 2020 17:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nickoneill/3bc23b9ffe5ed7b73768fe523fdda86e to your computer and use it in GitHub Desktop.
Save nickoneill/3bc23b9ffe5ed7b73768fe523fdda86e to your computer and use it in GitHub Desktop.
Lightweight Redux alternative, context through HOCs
// in your main app file, the state provider wraps all components in the render tree
ReactDOM.render(
<StateProvider>
<Location />
</StateProvider>,
document.getElementById("location")
);
// first, start with the usage in a react component, very simple and you just get access to state in stateProvider below,
// HOC injects our location-relation props from our state provider
import { WithLocationProps } from "../state/locationState";
import { withLocation } from "../state/stateProvider";
interface Props {}
class Location extends React.Component<Props & WithLocationProps> {
render() {
return (
<div>{this.props.location}</div>
)
}
}
export default withLocation(Location);
// definitions for the location context, state and what the HOC provides
import { createContext } from "react";
export const LocationContext = createContext<WithLocationProps>({
locationState: undefined,
setLocationAddress: () => {},
});
export type WithLocationProps = {
locationState?: LocationState;
setLocationAddress(address: string, display: string): void;
};
export interface LocationState {
address: string;
cachedCity: string;
}
// the state provider wraps the contexts and provides the HOC access to them
// most of the complexity is here
interface Props {}
interface State {
locationState?: LocationState;
savedStateRestored: boolean;
}
export default class StateProvider extends React.Component<Props, State> {
state: State = {
locationState: undefined,
savedStateRestored: false,
};
// if you only need this for transient state in a SPA, no need to load local storage here
componentDidMount() {
try {
// Storage not shown here, but it loads and parses local storage into objects
const appState = Storage.getStorageAsObject();
this.setState({ locationState: appState.locationState, savedStateRestored: true });
} catch (error) {
console.log("could not parse localstorage:", error);
}
}
setLocationAddress(address: string, display: string) {
// save the location and cached city in localstorage
const locationState: LocationState = {
address: address,
cachedCity: display,
};
Storage.saveLocation(locationState);
this.setState({ locationState });
}
render(): JSX.Element {
// if you don't want to show components before localstorage is loaded, return an empty fragment beforehand
if (!this.state.savedStateRestored) {
return <></>;
}
return (
// stack different contexts here, we only have location in this example
<LocationContext.Provider
value={{
locationState: this.state.locationState,
setLocationAddress: (address: string, display: string) => this.setLocationAddress(address, display),
}}
>
{this.props.children}
</LocationContext.Provider>
);
}
}
// I am the first to admit I don't fully understand the generics used here, and it would
// be nice to have the component props type-checked, but I am not that good
export const withLocation = <P extends object>(
Component: React.ComponentType<P>
): React.FC<Omit<P, keyof WithLocationProps>> => (props) => (
<LocationContext.Consumer>
{({ locationState, setLocation, setLocationAddress }) => (
<Component
{...(props as P)}
locationState={locationState}
setLocationAddress={setLocationAddress}
/>
)}
</LocationContext.Consumer>
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment