React Redux Toolkit Tips

From bibbleWiki
Jump to navigation Jump to search

Introduction

Taken from YouTube https://www.youtube.com/watch?v=NqzdVN2tyvQ
[Githun]

Tip 1 Probably should know this

Instead of using

const posts = useSelector((state) => state.posts);

We can use

// In the component
const posts = useSelector(selectAllPosts)
// In the Slice
export const selectAllPosts = (state) => state.posts;

// We can make more with like
export const selectPostById= (state, postId) => state.posts => 
   state.posts.find(post => post.id === postId);

Tip 2 Prepare Callback

We started with this where we pass an object in the action

const postsSlice = createSlice({
    name: 'posts',
    initialstate,
    reducers: {
       postAdded(state, action) {
           state.push(action.payload)
       }
    }
})

They like to provide an API parameter to minimize the work and make a prepare function

const postsSlice = createSlice({
    name: 'posts',
    initialstate,
    reducers: {
       postAdded: {  
           reducer(state, action) {
           state.push(action.payload)
       },
       prepare(title, content) {
           return {
              payload: {
                id: nanoid(),
                title,
                content 
              } 
           }
       }
    }
  }
})

Tip 3 Unwrap

Adding try catch to dispatch will unwrap the returning result which is a promise

try {
  setAddRequestStatus('pending')
  dispatch(addNewPost({title, body:content, userId }).unwrap()

  setTitle('')
  setContent('')
  setUserId('')
} catch(err) {
  console.error('Failed to save', err)
} finally {
  setAddRequestStatus('idle')
}_

Tip 4 Layout

You can use this like angular for headers and footers

import {
  BrowserRouter,
  Routes,
  Route,
  Link,
  Outlet
} from 'react-router-dom';

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="users" element={<Users />}>
          <Route path="/" element={<UsersIndex />} />
          <Route path=":id" element={<UserProfile />} />
          <Route path="me" element={<OwnUserProfile />} />
        </Route>
      </Routes>
    </BrowserRouter>
  );
}

function Users() {
  return (
    <div>
      <nav>
        <Link to="me">My Profile</Link>
      </nav>

      <Outlet />
    </div>
  );
}

Tip 5 Memorize and createSelector

We can optimize code by using createSelector to stop rerendering

// Filter returns a new array
const postsFortUser = useSelect(state => {
   const allPosts = selectAllPosts(state)
   return allPosts.filter(post => post.userId ==== Number(userId))
})

We can do this in the slice with createSelector

export const selectPostByUser = createSelector(
    [selectAllPosts, (state, userId) => userId],
    (posts,  userId)_ => posts.filter(post => post.userId === userId)_
)

// Now becomes
const postsFortUser = useSelector(state = > selectPsotByUser(state, Number(userId)))

To prove it works they created a counter on the header separate from the page with the selector. Previously the UserPage was being re-rendered as well as the header when the counter was pressed. Now is does not
Performance.png

Tip 6 Use createEntityAdapter

const postsAdapter = createEntityAdapter({
    sortComparer: (a, b) => b.date.localeCompare(a.date)
})

This provides several selectors which you can rename using destructuring.

export const {
    selectAll: selectAllPosts,
    selectById: selectPostById,
    selectIds: selectPostIds
    // Pass in a selector that returns the posts slice of state
} = postsAdapter.getSelectors(state => state.posts)

It also provides crude operations too! Before

            .addCase(updatePost.fulfilled, (state, action) => {
                if (!action.payload?.id) {
                    console.log('Update could not complete')
                    console.log(action.payload)
                    return;
                }
                const { id } = action.payload;
                action.payload.date = new Date().toISOString();
                const posts = state.posts.filter(post => post.id !== id);
                state.posts = [...posts, action.payload];
            })
            .addCase(deletePost.fulfilled, (state, action) => {
                if (!action.payload?.id) {
                    console.log('Delete could not complete')
                    console.log(action.payload)
                    return;
                }
                const { id } = action.payload;
                const posts = state.posts.filter(post => post.id !== id);
                state.posts = posts;
            })

After

            .addCase(updatePost.fulfilled, (state, action) => {
                if (!action.payload?.id) {
                    console.log('Update could not complete')
                    console.log(action.payload)
                    return;
                }
                action.payload.date = new Date().toISOString();
                postsAdapter.upsertOne(state, action.payload)
            })
            .addCase(deletePost.fulfilled, (state, action) => {
                if (!action.payload?.id) {
                    console.log('Delete could not complete')
                    console.log(action.payload)
                    return;
                }
                const { id } = action.payload;
                postsAdapter.removeOne(state, id)
            })