-
Notifications
You must be signed in to change notification settings - Fork 145
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Can selectors be supported? #68
Comments
I implemented one myself with import React from 'react';
export type SelectorFn<Value, Selected> = (value: Value) => Selected;
export interface ContainerProviderProps<State = void> {
initialState?: State;
}
export function createContainer<Value, State = void>(useHook: (initialState?: State) => Value) {
const Context = React.createContext<Value | null>(null, () => 0);
const listeners: Set<(value: Value) => void> = new Set();
const Provider: React.FC<ContainerProviderProps<State>> = React.memo(
({ initialState, children }) => {
const value = useHook(initialState);
if (process.env.NODE_ENV !== 'production') {
// eslint-disable-next-line react-hooks/rules-of-hooks
React.useLayoutEffect(() => {
listeners.forEach(listener => {
listener(value);
});
});
} else {
listeners.forEach(listener => {
listener(value);
});
}
return <Context.Provider value={value}>{children}</Context.Provider>;
},
);
function useSelector<Selected = any>(selector: SelectorFn<Value, Selected>): Selected {
const [, forceUpdate] = React.useReducer(c => c + 1, 0);
const value = React.useContext(Context);
if (value === null) {
throw new Error();
}
const selected = selector(value);
const ref = React.useRef<{
selector: SelectorFn<Value, Selected>;
value: Value;
selected: Selected;
} | null>(null);
React.useLayoutEffect(() => {
ref.current = {
selector,
value: value!,
selected,
};
});
React.useLayoutEffect(() => {
const callback = (nextValue: Value) => {
try {
if (!ref.current) {
return;
}
const refValue = ref.current;
if (refValue.value === nextValue) {
return;
}
const nextSelected = refValue.selector(nextValue);
if (Object.is(refValue.selected, nextSelected)) {
return;
}
if (typeof refValue.selected === 'object' && typeof nextSelected === 'object') {
if (
Object.entries(refValue.selected).every(([k, v]) => Object.is(v, nextSelected[k]))
) {
return;
}
}
} catch (e) {
// ignore
}
forceUpdate();
};
listeners.add(callback);
return () => {
listeners.delete(callback);
};
}, []);
return selected;
}
function useContainer(): Value {
return useSelector(x => x);
}
return {
Provider,
/**
* Usage `useSelector(state => state.count)`
* @param selector
*/
useSelector,
useContainer,
};
} |
Isn't a selector regarding this "framework" just an import { useCallback } from "react";
import { User } from "lib/user-container";
import { Products } from "lib/products-container";
export default function useShoppingCart(initialState = 0) {
let [products] = useState([]);
let currentUser = User.useContainer();
let products = Products.useContainer();
const selectShoppingCartItems = useCallback(
() => currentUser.shoppingCart.itemIds.map((id) => products[id]),
[products, currentUser.shoppingCart.itemIds]
);
return { selectShoppingCartItems };
}
Example from https://daveceddia.com/redux-selectors/ |
@StarpTech I ’m lazy and I do n’t want to optimize manually Your example still renders through |
I got your point but this is something you will do once while writing your selector.
But the expensive parts aren't re-executed the same as with selectors. Keep containers small and focused. |
e.g.
Doing this only triggers rerender when
count
changes.It would be perfect if you supported this.
The text was updated successfully, but these errors were encountered: