Skip to content

Instantly share code, notes, and snippets.

@itszero
Last active October 26, 2018 17:20
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save itszero/6fa54d6c16f1e166fe125512129aea60 to your computer and use it in GitHub Desktop.
Save itszero/6fa54d6c16f1e166fe125512129aea60 to your computer and use it in GitHub Desktop.
Attempt to recreate react-redux using hooks
import * as React from "react";
import { useContext, useEffect, useState } from "react";
import { render } from "react-dom";
import { applyMiddleware, bindActionCreators, createStore } from "redux";
import ReduxThunk from "redux-thunk";
import { createSelector, createStructuredSelector } from "reselect";
import { action, getType } from "typesafe-actions";
interface Post {
id: number;
title: string;
body: string;
}
interface Comment {
id: number;
name: string;
email: string;
body: string;
}
interface State {
readonly posts: Post[];
readonly comments: {
[key: number]: Comment,
};
}
const initialState: State = { posts: [], comments: {} };
const ADD_POSTS = "posts/ADD_POSTS";
const addPosts = (posts: Post[]) => action(ADD_POSTS, posts);
const ADD_COMMENTS = "posts/ADD_COMMENTS";
const addComments = (postId: number, comments: Comment[]) => action(ADD_COMMENTS, comments, { postId });
function fetchPosts() {
return (dispatch) => {
fetch("https://jsonplaceholder.typicode.com/posts")
.then((resp) => resp.json())
.then((data) => dispatch(addPosts(data)));
};
}
function fetchComments(postId: number) {
return (dispatch) => {
fetch(`https://jsonplaceholder.typicode.com/comments?postId=${postId}`)
.then((resp) => resp.json())
.then((data) => dispatch(addComments(postId, data)));
};
}
const rootReducer = (state: State, action): State => {
switch (action.type) {
case ADD_POSTS:
return {
...state,
posts: [...state.posts, ...action.payload],
};
break;
case ADD_COMMENTS:
return {
...state,
comments: {
...state.comments,
[action.meta.postId]: action.payload,
},
};
break;
default:
return state;
}
};
const store = createStore(rootReducer, initialState, applyMiddleware(ReduxThunk));
const ReduxProvider = React.createContext();
function useRedux(mapStateToProps, mapDispatchToProps) {
const store = useContext(ReduxProvider);
const [state, setState] = useState(mapStateToProps(store.getState()));
useEffect(() => {
return store.subscribe(() => {
setState(mapStateToProps(store.getState()));
});
}, [mapStateToProps]);
return [state, bindActionCreators(mapDispatchToProps, store.dispatch)];
}
const selectPosts = (state) => state.posts;
const selectComments = (state) => state.comments;
function App() {
const [{ posts, comments }, actions] = useRedux(createStructuredSelector({
posts: selectPosts,
comments: selectComments,
}), { fetchPosts, fetchComments });
return (
<div>
{posts.length === 0 && <button onClick={actions.fetchPosts}>Load Posts</button>}
<ul>
{posts.map((post) => (
<li key={post.id}>
<p>{post.title}</p>
{ comments[post.id] ? (
<ul>
{comments[post.id].map((comment) => (
<li key={comment.id}>
{comment.name}({comment.email}): {comment.body}
</li>
))}
</ul>
) : (
<button onClick={() => actions.fetchComments(post.id)}>Load Comments</button>
) }
</li>
))}
</ul>
</div>
);
}
render(<ReduxProvider.Provider value={store}><App/></ReduxProvider.Provider>, document.getElementById("root"));
function useRedux(mapStateToProps, mapDispatchToProps) {
const store = useContext(ReduxProvider);
const [state, setState] = useState(mapStateToProps(store.getState()));
useEffect(() => {
return store.subscribe(() => {
setState(mapStateToProps(store.getState()));
});
}, [mapStateToProps]);
return [state, bindActionCreators(mapDispatchToProps, store.dispatch)];
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment