Last active
May 11, 2020 11:27
-
-
Save lmiller1990/d027b47350bf17379181fc0747c5d95b to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* usesEntity - a higher order component for usage with the flux-entities pattern. | |
* The goal here is to wrap components that depend on a specific part or parts of the | |
* redux store to ensure they will trigger a request to fetch them if they are not loaded. | |
* In the meantime, the component will fall back to a loading state. | |
* | |
* Basic Usage | |
* | |
* The <TodosList /> component requires the todos state, so we wrap it using the | |
* usesEntity higher order component. | |
* | |
* const TodosList = usesEntity({ entity: 'todos', fetchFunction: fetchTodo })(Todos) | |
* | |
* <TodosList /> <= It will trigger the required ajax request and fallback to loading | |
* in the meantime. | |
*/ | |
import React, { useEffect } from 'react' | |
import { createStore } from 'redux' | |
import { Provider, useDispatch, useSelector, shallowEqual } from 'react-redux' | |
import { ajaxBaseState, mapEntities, isLoaded } from 'flux-entities' | |
// Store/Actions | |
const fetchTodoRequest = () => ({ type: 'REQUEST' }) | |
const fetchTodoSuccess = (payload) => ({ | |
type: 'SUCCESS', payload | |
}) | |
const fetchTodo = dispatch => { | |
dispatch(fetchTodoRequest()) | |
return new Promise(res => { | |
setTimeout(() => { | |
const newTodo = { id: '2', title: 'create a custom hook' } | |
dispatch(fetchTodoSuccess(newTodo)) | |
res() | |
}, 1000) | |
}) | |
} | |
// Reducer | |
const rootReducer = (state, action) => { | |
if (action.type === 'REQUEST') { | |
return { | |
...state, | |
todos: { | |
...state.todos, | |
loading: true, | |
touched: true, | |
} | |
} | |
} | |
if (action.type === 'SUCCESS') { | |
return { | |
...state, | |
todos: { | |
...state.todos, | |
loading: false, | |
ids: [...state.todos.ids, action.payload.id ], | |
all: {...state.todos.all, [action.payload.id]: action.payload } | |
} | |
} | |
} | |
return state | |
} | |
// Create Store | |
const store = createStore(rootReducer, { | |
todos: { | |
...ajaxBaseState(), | |
all: { | |
1: { | |
title: 'do some work', | |
id: '1' | |
} | |
}, | |
ids: ['1'] | |
} | |
}) | |
const Todos = () => { | |
const todos = useSelector(state => state.todos, shallowEqual) | |
return ( | |
<ul> | |
{mapEntities(todos).map(x => <li key={x.id}>{x.title}</li>)} | |
</ul> | |
) | |
} | |
// This is bit trippy at first - basically it's a higher order component | |
// a function that returns a Component (first argument to a component is props) | |
// You pass your component, then the part of the store and the function | |
// that will trigger the fetching of the entities. | |
// It can easily be extended to work with multiple entities | |
const usesEntity = ({ entity, fetchFunction }) => (Comp) => (props) => { | |
const loaded = useSelector(state => isLoaded(state[entity])) | |
const dispatch = useDispatch() | |
useEffect(() => { | |
if (!loaded) { | |
fetchFunction(dispatch) | |
} | |
}, [dispatch, loaded]) | |
if (!loaded) { | |
return <div>Loading {entity}...</div> | |
} | |
return <Comp /> | |
} | |
// Usage: | |
const TodosList = usesEntity({ entity: 'todos', fetchFunction: fetchTodo })(Todos) | |
function App() { | |
return ( | |
<Provider store={store}> | |
<TodosList /> | |
</Provider> | |
); | |
} | |
window.store = store | |
export default App |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment