Skip to content

Instantly share code, notes, and snippets.

@goloveychuk
Last active November 19, 2018 18:51
Show Gist options
  • Save goloveychuk/7ccc26fac1ee99333bdf0451a098bb1f to your computer and use it in GitHub Desktop.
Save goloveychuk/7ccc26fac1ee99333bdf0451a098bb1f to your computer and use it in GitHub Desktop.
redux-hooks.tsx
import React, {
Component,
useContext,
useMemo,
useState,
useEffect
} from "react";
import { ReactReduxContext, Provider } from "react-redux";
import { Selector } from "reselect";
import {
combineReducers,
AnyAction,
Dispatch,
ActionCreator,
Store
} from "redux";
import { createStore } from "redux";
const MyStoreContext = React.createContext<Store | null>(null);
export function bindDispatch<T extends ActionCreator<any>>(
fn: T,
dispatch: Dispatch
): T {
return (((...args: any[]) => dispatch((fn as any)(...args))) as any) as T;
}
function createRedux(context: React.Context<Store | null> = MyStoreContext) {
function useSelector<S>(selector: Selector<AppState, S>) {
const store = useContext(context)!;
const [compState, setCompState] = useState(() =>
selector(store.getState())
);
let prevState = compState;
useEffect(() => {
console.log("calling subscribe");
return store.subscribe(() => {
const newState = selector(store.getState());
if (prevState !== newState) {
// debugger;
// console.log('setting new state')
prevState = newState;
setCompState(newState);
}
});
}, []);
return compState;
}
function useAction<T extends ActionCreator<any>>(action: T): T {
const store = useContext(context)!;
// const [compState, setCompState] = useState(()=>selector(store.getState()));
return bindDispatch(action, store.dispatch);
}
const ReduxProvider: React.SFC<{ store: Store }> = ({ store, children }) => (
<context.Provider value={store}>
<Provider store={store}>{children}</Provider>
</context.Provider>
);
return { useAction, useSelector, ReduxProvider };
}
const { useAction, useSelector, ReduxProvider } = createRedux();
////////////////////////////////////////////////////////////////////////////////////
interface ListState {
list: {}[];
}
interface CountState {
count: number;
}
interface AppState {
list: ListState;
count: CountState;
}
function countSelector(state: AppState) {
return state.count.count;
}
function listSelector(state: AppState) {
return state.list.list;
}
function incrementAction() {
return {
type: "INCREMENT"
};
}
function updateList() {
return {
type: "UPDATE_LIST"
};
}
function countReducer(
state: CountState = { count: 13 },
action: AnyAction
): CountState {
switch (action.type) {
case "INCREMENT":
return {
...state,
count: state.count + 1
};
default:
return state;
}
}
function listReducer(state: ListState = { list: [] }, action: any): ListState {
switch (action.type) {
case "UPDATE_LIST":
return {
...state,
list: [...state.list, {}]
};
default:
return state;
}
}
const rootReducer = combineReducers({
list: listReducer,
count: countReducer
});
////////////////////////////////////////////////////////////////////////////////////////
const Button = () => {
const num = useSelector(countSelector);
const onClick = useAction(incrementAction);
console.log("rerendering button");
return (
<div>
<button onClick={onClick}>{`button ${num}`}</button>
</div>
);
};
const List = () => {
const list = useSelector(listSelector);
const onClick = useAction(updateList);
console.log("rerendering list");
return (
<div>
<button onClick={onClick}>{`update list`}</button>
{list.map((_, ind) => (
<div key={ind}>listItem</div>
))}
</div>
);
};
function StatelessList() {
console.log("rerendering stateless list!!!!!!!, WHY?");
return null;
}
class App extends Component {
store = createStore(rootReducer);
render() {
return (
<ReduxProvider store={this.store}>
<div className="App">
<Button />
<List />
<StatelessList />
</div>
</ReduxProvider>
);
}
}
export default App;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment