Skip to content

Instantly share code, notes, and snippets.

@lmiller1990
Last active May 11, 2020 11:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lmiller1990/d027b47350bf17379181fc0747c5d95b to your computer and use it in GitHub Desktop.
Save lmiller1990/d027b47350bf17379181fc0747c5d95b to your computer and use it in GitHub Desktop.
/**
* 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