简体   繁体   中英

What's the best way to use react-redux with list of items?

I have a list of items (JSON objects returned from API) in a redux store. This data is normalized currently, so it's just an array. It'll roughly have 10-30 objects and each object will have about 10 properties.

Currently we have a top level container (uses react-redux connect) that reads this list from the store, maps over the array and renders a component called ListItem , which basically needs 3-4 fields from the object to render the UI.

We don't have any performance issue with this now. But I wonder if it makes sense to have a redux container component for each list item? I think this will require data to be normalized and we'd need the unique id of each object to be passed to this container which can then read the object from redux store?

This question arises from the Redux docs' Style Guide - https://redux.js.org/style-guide/style-guide#connect-more-components-to-read-data-from-the-store

Just trying to understand which is the recommended way to use react-redux in this scenario.

Thanks!

I wonder if it makes sense to have a redux container component for each list item?

Besides possibly better performance there is also better code reuse. If the logic of what an item is is defined in the list then how can you reuse the list to render other items?

Below is an example where item is a combination of data and edit so props for item will be recreated for all items if you'd create the props in List instead of Item.

List can also not be used as a general list that passes id to Item component.

 const { Provider, useDispatch, useSelector } = ReactRedux; const { createStore, applyMiddleware, compose } = Redux; const { useMemo } = React; const { createSelector } = Reselect; const { produce } = immer; const initialState = { people: { data: { 1: { id: 1, name: 'Jon' }, 2: { id: 2, name: 'Marie' }, }, edit: {}, }, places: { data: { 1: { id: 1, name: 'Rome' }, 2: { id: 2, name: 'Paris' }, }, edit: {}, }, }; //action types const SET_EDIT = 'SET_EDIT'; const CANCEL_EDIT = 'CANCEL_EDIT'; const SAVE = 'SAVE'; const CHANGE_TEXT = 'CHANGE_TEXT'; //action creators const setEdit = (dataType, id) => ({ type: SET_EDIT, payload: { dataType, id }, }); const cancelEdit = (dataType, id) => ({ type: CANCEL_EDIT, payload: { dataType, id }, }); const save = (dataType, item) => ({ type: SAVE, payload: { dataType, item }, }); const changeText = (dataType, id, field, value) => ({ type: CHANGE_TEXT, payload: { dataType, id, field, value }, }); const reducer = (state, { type, payload }) => { if (type === SET_EDIT) { const { dataType, id } = payload; return produce(state, (draft) => { draft[dataType].edit[id] = draft[dataType].data[id]; }); } if (type === CANCEL_EDIT) { const { dataType, id } = payload; return produce(state, (draft) => { delete draft[dataType].edit[id]; }); } if (type === CHANGE_TEXT) { const { dataType, id, field, value } = payload; return produce(state, (draft) => { draft[dataType].edit[id][field] = value; }); } if (type === SAVE) { const { dataType, item } = payload; return produce(state, (draft) => { const newItem = {...item }; delete newItem.edit; draft[dataType].data[item.id] = newItem; delete draft[dataType].edit[item.id]; }); } return state; }; //selectors const createSelectData = (dataType) => (state) => state[dataType]; const createSelectDataList = (dataType) => createSelector([createSelectData(dataType)], (result) => Object.values(result.data) ); const createSelectDataById = (dataType, itemId) => createSelector( [createSelectData(dataType)], (dataResult) => dataResult.data[itemId] ); const createSelectEditById = (dataType, itemId) => createSelector( [createSelectData(dataType)], (dataResult) => (dataResult.edit || {})[itemId] ); const createSelectItemById = (dataType, itemId) => createSelector( [ createSelectDataById(dataType, itemId), createSelectEditById(dataType, itemId), ], (item, edit) => ({...item, ...edit, edit: Boolean(edit), }) ); //creating store with redux dev tools const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; const store = createStore( reducer, initialState, composeEnhancers( applyMiddleware(() => (next) => (action) => next(action) ) ) ); const Item = ({ item, dataType }) => { const dispatch = useDispatch(); return ( <li> {item.edit? ( <React.Fragment> <input type="text" value={item.name} onChange={(e) => dispatch( changeText( dataType, item.id, 'name', e.target.value ) ) } /> <button onClick={() => dispatch(cancelEdit(dataType, item.id)) } > cancel </button> <button onClick={() => dispatch(save(dataType, item))} > save </button> </React.Fragment> ): ( <React.Fragment> {item.name} <button onClick={() => dispatch(setEdit(dataType, item.id)) } > edit </button> </React.Fragment> )} </li> ); }; const createItem = (dataType) => React.memo(function ItemContainer({ id }) { const selectItem = useMemo( () => createSelectItemById(dataType, id), [id] ); const item = useSelector(selectItem); return <Item item={item} dataType={dataType} />; }); const Person = createItem('people'); const Location = createItem('places'); const List = React.memo(function List({ items, Item }) { return ( <ul> {items.map(({ id }) => ( <Item key={id} id={id} /> ))} </ul> ); }); const App = () => { const [selectPeople, selectPlaces] = useMemo( () => [ createSelectDataList('people'), createSelectDataList('places'), ], [] ); const people = useSelector(selectPeople); const places = useSelector(selectPlaces); return ( <div> <List items={people} Item={Person} /> <List items={places} Item={Location} /> </div> ); }; ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') );
 <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/reselect/4.0.0/reselect.min.js"></script> <script src="https://unpkg.com/immer@7.0.5/dist/immer.umd.production.min.js"></script> <div id="root"></div>

If your application has repeating logic you may want to think of splitting the component up in container and presentation (container also called connected component in redux). You can re use the container but change the presentation.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM