Skip to content

Instantly share code, notes, and snippets.

@amsterdamharu
Last active March 17, 2021 19:14
Show Gist options
  • Save amsterdamharu/b6d6ed6f6103f15ba70186b13a5fc67a to your computer and use it in GitHub Desktop.
Save amsterdamharu/b6d6ed6f6103f15ba70186b13a5fc67a to your computer and use it in GitHub Desktop.
connect react context
import React from 'react';
import ReactDOM from 'react-dom';
import * as Reselect from 'reselect';
const {
useMemo,
useState,
useContext,
useRef,
useCallback,
} = React;
const { createSelector } = Reselect;
const MyContext = React.createContext({ count: 0 });
//react-redux connect like function to connect component to context
const connect = (context) => (mapContextToProps) => {
const select = (selector, state, props) => {
if (selector.current !== mapContextToProps) {
return selector.current(state, props);
}
const result = mapContextToProps(state, props);
if (typeof result === 'function') {
selector.current = result;
return select(selector, state, props);
}
return result;
};
return (Component) => (props) => {
const selector = useRef(mapContextToProps);
const contextValue = useContext(context);
const contextProps = select(
selector,
contextValue,
props
);
return useMemo(() => {
const combinedProps = {
...props,
...contextProps,
};
return (
<React.Fragment>
<Component {...combinedProps} />
</React.Fragment>
);
}, [contextProps, props]);
};
};
//connect function that connects to MyContext
const connectMyContext = connect(MyContext);
// simple context that increments a timer
const Context = ({ children }) => {
const [count, setCount] = useState(0);
const increment = useCallback(
() => setCount((c) => c + 1),
[]
);
return (
<MyContext.Provider value={{ count, increment }}>
{children}
</MyContext.Provider>
);
};
//functional component
function FunctionComponent({ count }) {
console.log('in function', count);
return <div>Function Component {count}</div>;
}
//selectors
const selectCount = (state) => state.count;
const selectIncrement = (state) => state.increment;
const selectCountDiveded = createSelector(
selectCount,
(_, divisor) => divisor,
(count, { divisor }) => Math.floor(count / divisor)
);
const createSelectConnectedContext = () =>
createSelector(selectCountDiveded, (count) => ({
count,
}));
//connected component
const ConnectedComponent = connectMyContext(
createSelectConnectedContext
)(FunctionComponent);
//app is also connected but won't re render when count changes
// it only gets increment and that never changes
const App = connectMyContext(
createSelector(selectIncrement, (increment) => ({
increment,
}))
)(function App({ increment }) {
const [divisor, setDivisor] = useState(0.5);
return (
<div>
<button onClick={increment}>increment</button>
<button onClick={() => setDivisor((d) => d * 2)}>
double divisor
</button>
<ConnectedComponent divisor={divisor} />
<ConnectedComponent divisor={divisor * 2} />
<ConnectedComponent divisor={divisor * 4} />
</div>
);
});
ReactDOM.render(
<Context>
<App />
</Context>,
document.getElementById('root')
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment