React Redux Toolkit Tips: Difference between revisions
(14 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
=Introduction= | =Introduction= | ||
Taken from YouTube https://www.youtube.com/watch?v=NqzdVN2tyvQ | Taken from YouTube https://www.youtube.com/watch?v=NqzdVN2tyvQ<br> | ||
=Tip 1= | [[https://github.com/gitdagray/react_redux_toolkit Githun]] | ||
=Tip 1 Probably should know this= | |||
Instead of using | Instead of using | ||
<syntaxhighlight lang="js"> | <syntaxhighlight lang="js"> | ||
Line 12: | Line 14: | ||
// In the Slice | // In the Slice | ||
export const selectAllPosts = (state) => state.posts; | 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); | |||
</syntaxhighlight> | |||
=Tip 2 Prepare Callback= | |||
We started with this where we pass an object in the action | |||
<syntaxhighlight lang="js" highlight="5-7"> | |||
const postsSlice = createSlice({ | |||
name: 'posts', | |||
initialstate, | |||
reducers: { | |||
postAdded(state, action) { | |||
state.push(action.payload) | |||
} | |||
} | |||
}) | |||
</syntaxhighlight> | |||
They like to provide an API parameter to minimize the work and make a prepare function | |||
<syntaxhighlight lang="js"> | |||
const postsSlice = createSlice({ | |||
name: 'posts', | |||
initialstate, | |||
reducers: { | |||
postAdded: { | |||
reducer(state, action) { | |||
state.push(action.payload) | |||
}, | |||
prepare(title, content) { | |||
return { | |||
payload: { | |||
id: nanoid(), | |||
title, | |||
content | |||
} | |||
} | |||
} | |||
} | |||
} | |||
}) | |||
</syntaxhighlight> | |||
=Tip 3 Unwrap= | |||
Adding try catch to dispatch will unwrap the returning result which is a promise | |||
<syntaxhighlight lang="js"> | |||
try { | |||
setAddRequestStatus('pending') | |||
dispatch(addNewPost({title, body:content, userId }).unwrap() | |||
setTitle('') | |||
setContent('') | |||
setUserId('') | |||
} catch(err) { | |||
console.error('Failed to save', err) | |||
} finally { | |||
setAddRequestStatus('idle') | |||
}_ | |||
</syntaxhighlight> | |||
=Tip 4 Layout= | |||
You can use this like angular for headers and footers | |||
<syntaxhighlight lang="js"> | |||
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> | |||
); | |||
} | |||
</syntaxhighlight> | |||
=Tip 5 Memorize and createSelector= | |||
We can optimize code by using createSelector to stop rerendering | |||
<syntaxhighlight lang="js"> | |||
// Filter returns a new array | |||
const postsFortUser = useSelect(state => { | |||
const allPosts = selectAllPosts(state) | |||
return allPosts.filter(post => post.userId ==== Number(userId)) | |||
}) | |||
</syntaxhighlight> | |||
We can do this in the slice with createSelector | |||
<syntaxhighlight lang="js"> | |||
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))) | |||
</syntaxhighlight> | |||
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<br> | |||
[[File:Performance.png]]<br> | |||
=Tip 6 Use createEntityAdapter= | |||
<syntaxhighlight lang="js"> | |||
const postsAdapter = createEntityAdapter({ | |||
sortComparer: (a, b) => b.date.localeCompare(a.date) | |||
}) | |||
</syntaxhighlight> | |||
This provides several selectors which you can rename using destructuring. | |||
<syntaxhighlight lang="js"> | |||
export const { | |||
selectAll: selectAllPosts, | |||
selectById: selectPostById, | |||
selectIds: selectPostIds | |||
// Pass in a selector that returns the posts slice of state | |||
} = postsAdapter.getSelectors(state => state.posts) | |||
</syntaxhighlight> | |||
It also provides crude operations too! Before | |||
<syntaxhighlight lang="js"> | |||
.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; | |||
}) | |||
</syntaxhighlight> | |||
After | |||
<syntaxhighlight lang="js"> | |||
.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) | |||
}) | |||
</syntaxhighlight> | </syntaxhighlight> |
Latest revision as of 07:04, 31 December 2022
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
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)
})