I just can’t decide the pattern I want to follow.
I’m implementing what I call a UserParent component. Basically a list of users and when you click on a user, it loads their resources.
Approach 1: Redux
import React, { useEffect, useState } from ‘react’ import { useDispatch, useSelector } from ‘react-redux’ import { NavList } from ‘../nav/NavList’ import { ResourceList } from ‘../resource/ResourceList’ import { getUserResources, clearResources } from ‘./userSlice’ import CircularProgress from ‘@mui/material/CircularProgress’; import { getAllUsers } from ‘./userSlice’; export const UserParent = () => { const users = useSelector((state) => state.users.users ) const resources = useSelector((state) => state.users.user.resources ) const [highLightedUsers, setHighLightedItems] = useState([]); const isLoading = useSelector((state) => state.users.isLoading) let dispatch = useDispatch(); useEffect(() => { dispatch(getAllUsers()); }, []) const onUserClick = (user) => { if (highLightedUsers.includes(user.label)) { setHighLightedItems([]) dispatch(clearResources()) } else { setHighLightedItems([…highLightedUsers, user.label]) dispatch(getUserResources(user.id)) } } return( <> { isLoading === undefined || isLoading ? <CircularProgress className=”search-loader” /> : <div className=”search-container”> <div className=”search-nav”> <NavList items={users} onItemClick={onUserClick} highLightedItems={highLightedUsers} /> </div> <div className=”search-results”> <ResourceList resources={resources} /> </div> </div> } </> ) }
And then we have the reducer code:
import { createSlice } from ‘@reduxjs/toolkit’; import Api from ‘../../services/api’; const INITIAL_STATE = { users: [], isLoading: true, user: { resources: [] } }; export const userSlice = createSlice({ name: ‘users’, initialState: INITIAL_STATE, reducers: { loadAllUsers: (state, action) => ({ …state, users: action.payload, isLoading: false }), toggleUserLoader: (state, action) => ({ …state, isLoading: action.payload }), loadUserResources: (state, action) => ({ …state, user: { resources: action.payload } }), clearResources: (state) => ({ …state, isLoading: false, user: { resources: [] } }) } }); export const { loadAllUsers, toggleUserLoader, loadUserResources, clearResources } = userSlice.actions; export const getAllUsers = () => async (dispatch) => { try { const res = await Api.fetchAllUsers() if (!res.errors) { dispatch(loadAllUsers(res.map(user => ({id: user.id, label: user.full_name})))); } else { throw res.errors } } catch (err) { alert(JSON.stringify(err)) } } export const getUserResources = (userId) => async (dispatch) => { try { const res = await Api.fetchUserResources(userId) if (!res.errors) { dispatch(loadUserResources(res)); } else { throw res.errors } } catch (err) { alert(JSON.stringify(err)) } } export default userSlice.reducer;
This is fine but I am following this pattern on every page in my app. While it is easy follow I don’t believe I’m using global state properly. Every page makes and API call and loads the response into redux, not necessarily because it needs to be shared (although it may be at some point) but because it’s the pattern I’m following.
Approach 2: Local State
import React, { useEffect, useState } from ‘react’ import { NavList } from ‘../nav/NavList’ import { ResourceList } from ‘../resource/ResourceList’ import CircularProgress from ‘@mui/material/CircularProgress’; import Api from ‘../../services/api’; export const UserParent = () => { const [users, setUsers] = useState([]) const [resources, setResources] = useState([]) const [highLightedUsers, setHighLightedItems] = useState([]); const [isLoading, setIsLoading] = useState(true) const getUsers = async () => { try { const res = await Api.fetchAllUsers() setUsers(res.map(user => ({id: user.id, label: user.full_name}))) setIsLoading(false) } catch (error) { console.log(error) } } const getUserResources = async (userId) => { try { setIsLoading(true) const res = await Api.fetchUserResources(userId) setResources(res) setIsLoading(false) } catch (error) { console.log(error) } } useEffect(() => { getUsers() }, []) const onUserClick = (user) => { if (highLightedUsers.includes(user.label)) { setHighLightedItems([]) } else { setHighLightedItems([…highLightedUsers, user.label]) getUserResources(user.id) } } return( <> { isLoading === undefined || isLoading ? <CircularProgress className=”search-loader” /> : <div className=”search-container”> <div className=”search-nav”> <NavList items={users} onItemClick={onUserClick} highLightedItems={highLightedUsers} /> </div> <div className=”search-results”> <ResourceList resources={resources} /> </div> </div>} </> ) }
What I like about this is that it uses local state and doesn’t bloat global state however, I don’t like that it still has business logic in the component, I could just move these to a different file but first I wanted to try React Query instead.
Approach 3: React Query
import React, { useState } from ‘react’ import { NavList } from ‘../nav/NavList’ import { ResourceList } from ‘../resource/ResourceList’ import CircularProgress from ‘@mui/material/CircularProgress’; import Api from ‘../../services/api’; import { useQuery } from “react-query”; export const UserParent = () => { const [resources, setResources] = useState([]) const [highLightedUsers, setHighLightedItems] = useState([]); const getUsers = async () => { try { const res = await Api.fetchAllUsers() return res } catch (error) { console.log(error) } } const { data, status } = useQuery(“users”, getUsers); const getUserResources = async (userId) => { try { const res = await Api.fetchUserResources(userId) setResources(res) } catch (error) { console.log(error) } } const onUserClick = (user) => { if (highLightedUsers.includes(user.label)) { setHighLightedItems([]) } else { setHighLightedItems([…highLightedUsers, user.label]) getUserResources(user.id) } } return( <> { status === ‘loading’ && <CircularProgress className=”search-loader” /> } <div className=”search-container”> <div className=”search-nav”> <NavList items={data.map(user => ({id: user.id, label: user.full_name}))} onItemClick={onUserClick} highLightedItems={highLightedUsers} /> </div> <div className=”search-results”> <ResourceList resources={resources} /> </div> </div> </> ) }
This is great but there is still business logic in my component, so I can move those functions to a separate file and import them and then I end up with this:
import React, { useState } from ‘react’ import { UserList } from ‘../users/UserList’ import { ResourceList } from ‘../resource/ResourceList’ import CircularProgress from ‘@mui/material/CircularProgress’; import { getUsers, getUserResources } from ‘./users’ import { useQuery } from “react-query”; export const UserParent = () => { const [resources, setResources] = useState([]) const [highLightedUsers, setHighLightedItems] = useState([]); const { data, status } = useQuery(“users”, getUsers); const onUserClick = async (user) => { if (highLightedUsers.includes(user.full_name)) { setHighLightedItems([]) } else { setHighLightedItems([…highLightedUsers, user.full_name]) const res = await getUserResources(user.id) setResources(res) } } return( <> { status === ‘loading’ && <CircularProgress className=”search-loader” /> } <div className=”search-container”> <div className=”search-nav”> <UserList users={data} onUserClick={onUserClick} highLightedUsers={highLightedUsers} /> </div> <div className=”search-results”> <ResourceList resources={resources} /> </div> </div> </> ) }
In my opinion this is so clean! However, is there anything wrong with the first approach using Redux? Which approach do you prefer?
submitted by /u/jxdx1978
[link] [comments]