I am trying to make a simple task manager app and I want to implement react memo in TaskRow (task item) but when I click the checkbox to finish the task, the component properties are the same and I cannot compare them and all tasks are re-rendered again, any suggestions? Thanks
Sand Box: https://codesandbox.io/s/interesting-tharp-ziwe3?file=/src/components/Tasks/Tasks.jsx
Tasks Component
import React, { useState, useEffect, useCallback } from 'react'
import TaskRow from "../TaskRow";
function Tasks(props) {
const [taskItems, setTaskItems] = useState([])
useEffect(() => {
setTaskItems(JSON.parse(localStorage.getItem('tasks')) || [])
}, [])
useEffect(() => {
if (!props.newTask) return
newTask({ id: taskItems.length + 1, ...props.newTask })
}, [props.newTask])
const newTask = (task) => {
updateItems([...taskItems, task])
}
const toggleDoneTask = useCallback((id) => {
const taskItemsCopy = [...taskItems]
taskItemsCopy.map((t)=>{
if(t.id === id){
t.done = !t.done
return t
}
return t
})
console.log(taskItemsCopy)
console.log(taskItems)
updateItems(taskItemsCopy)
}, [taskItems])
const updateItems = (tasks) => {
setTaskItems(tasks)
localStorage.setItem('tasks', JSON.stringify(tasks))
}
return (
<React.Fragment>
<h1>learning react </h1>
<table>
<thead>
<tr>
<th>Title</th>
<th>Description</th>
<th>Done</th>
</tr>
</thead>
<tbody>
{
props.show ? taskItems.map((task, i) =>
<TaskRow
task={task}
key={task.id}
toggleDoneTask={()=>toggleDoneTask(task.id)}>
</TaskRow>)
:
taskItems.filter((task) => !task.done)
.map((task) =>
<TaskRow
show={props.show}
task={task}
key={task.id}
toggleDoneTask={()=>toggleDoneTask(task.id)}></TaskRow>
)
}
</tbody>
</table>
</React.Fragment>
)
}
export default Tasks
Item task (TaskRow component)
import React, { memo } from 'react'
function TaskRow(props) {
return (<React.Fragment>
{console.log('render', props.task)}
<Tr show={props.show} taskDone={props.task.done}>
<td>
{props.task.title}
</td>
<td>
{props.task.description}
</td>
<td>
<input type="checkbox"
checked={props.task.done}
onChange={props.toggleDoneTask}
/>
</td>
</Tr>
</React.Fragment>)
}
export default memo(TaskRow, (prev,next)=>{
console.log('prev props', prev.task)
console.log('next props', next.task)
})
I'm afraid there are quite a lot of problems with the code you've shared, only some of them due to React.memo. I'll start there and work through the ones I've spotted.
You've provided an equality testing function to memo, but you are not using it to test anything. The default behaviour, which requires no testing function, will shallowly compare props between the previous and the next render. This means it will pick up on differences between primitive values (e.g. string, number, boolean) and references to objects (e.g. literals, arrays, functions), but it will not automatically deeply compare those objects.
Remember, memo will only allow rerenders when the equality testing function returns false. You've provided no return value to the testing function, meaning it returns undefined, which is falsy. I've provided a simple testing function for an object literal with primitive values which will do the job needed here. If you have more complex objects to pass in the future, I suggest using a comprehensive deep equality checker like the one provided by the lodash library, or, even better, do not pass objects at all if you can help it and instead try to stick to primitive values.
export default memo(TaskRow, (prev, next) => {
const prevTaskKeys = Object.keys(prev.task);
const nextTaskKeys = Object.keys(next.task);
const sameLength = prevTaskKeys.length === nextTaskKeys.length;
const sameEntries = prevTaskKeys.every(key => {
return nextTaskKeys.includes(key) && prev.task[key] === next.task[key];
});
return sameLength && sameEntries;
});
While this solves the initial memoisation issue, the code is still broken for a couple of reasons. The first is that despite copying your taskItems in toggleTaskDone, for similar reasons to those outlined above, your array of objects is not deeply copied. You are placing the objects in a new array, but the references to those objects are preserved from the previous array. Any changes you make to those objects will be directly mutating the React state, causing the values to become out of sync with the rest of your effects.
You can solve this by mapping the copy and spreading the objects. You would have to do this for every level of object reference in any state object you attempt to change, which is part of the reason that React advises against complex objects in useState (one level of depth is usually fine).
const taskItemsCopy = [...taskItems].map((task) => ({ ...task }));
Side Note: You are not doing anything with the result of taskItemsCopy in your original code. map is not a mutating method - calling it without assigning the result to a variable does nothing.
The next issue is more subtle, and demonstrates one of the pitfalls and potential complications when memoising your components. The toggleTaskDone callback has taskItems in its dependency array. However, you are passing it as a prop in an anonymous function to TaskRow. This prop is not being considered by React.memo - we're specifically ignoring it because we only want to rerender on changes to the task object itself. This means that when a task does change its done status, all the other tasks are becoming out of sync with the new value of taskItems - when they change their done status, they will be using the value of taskItems as it was the last time they were rendered.
Inline anonymous functions are recreated on every render, so they are always unequal by reference. You could actually fix this somewhat by adjusting the way the callback is passed and executed:
// Tasks.jsx
toggleDoneTask={toggleDoneTask}
// TaskRow.jsx
onChange={() => props.toggleDoneTask(props.task.id)}
In this way you would be able to check for reference changes in your memo equality function, but since the callback changes every time taskItems changes, this would make the memoisation completely useless!
So, what to do. This is where the implementation of the rest of the Tasks component starts to limit us a bit. We can't have taskItems in the dependency of toggleTaskDone, and we also can't call updateItems because that has the same (implicit) dependency. I've provided a solution which technically works, although I would consider this a hack and not really recommended for actual usage. It relies on the callback version of setState which will allow us to have access to the current value of taskItems without including it as a dependency.
const toggleDoneTask = useCallback((id) => {
setTaskItems((prevItems) => {
const prevCopy = [...prevItems].map((task) => ({ ...task }));
const newItems = prevCopy.map((t) => {
if (t.id === id) t.done = !t.done;
return t;
});
localStorage.setItem("tasks", JSON.stringify(newItems));
return newItems;
});
}, []);
Now it doesn't matter that we aren't equality checking the handler prop, because the function never alters from the initial render of the component. With these changes implemented my fork of your sandbox seems to be working as expected.
On a broader note, I really think you should consider writing React code using create-react-app when you're learning the framework. I was a little surprised to see you had a custom webpack set up, and you don't seem to have proper linting for React (bundled automatically in CRA) which would highlight a lot of these issues for you as warnings. Specifically, the misuse of the dependency array in a number of places in the Task component which is going to make it unstable and error prone even with the essential fixes I've suggested.
Related
Edit: I found the solution as a result of cbreezier's advice, as well as learning more about how the table hook was working (which I had found on YouTube and foolishly did not fully understand). Sending currentData back to ParentComponent via the props callback was what I took from their answer. However, passing currentData to the callback was not working from tableHooks. I then realized that while it was able to access the rows, it was not able to access the currentData. Changing Cell: ({ row }) to Cell: ({ currentData, row }) did the trick.
If you are trying to figure out a similar issue in your own React app, this may not be the exact answer, but I would suggest understanding cbreezier's answer and hopefully that sets you on your way.
Original Question:
I have been encountering an issue for a few days now that appears to be rooted in my inexperience with React and Javascript, but without knowing what the issue is, it has been difficult to search for a solution. Here is some example code that somewhat isolates the issue:
export const ParentComponent = () => {
const [currentData, setCurrentData] = useState([]);
const fetchCurrentData = async () => {
let fetchedCurrentData = await getFunctions.getCurrentData();
setCurrentData(fetchedCurrentData);
}
const handleButtonPress = (row) => {
console.log(currentData);
}
return (
<div>
<ChildComponent data={currentData} handleButtonPress={handleButtonPress} />
</div>
)
}
export default function ChildComponent(props) => {
const columns = useMemo(() => COLUMNS, []);
const data = useMemo(() => props.data, [props.data]);
const tableHooks = (hooks) => {
hooks.visibleColumns.push((columns) => [
...columns, {
id: "add",
Header: "Add",
Cell: ({ row }) => (
<button onClick={() => props.handleButtonPress()}>Add</button>
)
}
])
}
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow
} = useTable({
columns,
data
}, tableHooks);
return (
<div>
<table {...getTableProps()}>
<thead>
{headerGroups.map(headerGroup => (
<tr {...headerGroup.getHeaderGroupProps()}>
{
headerGroup.headers.map(column => (
<th {...column.getHeaderProps()}>{column.render('Header')}</th>
))
}
</tr>
))}
</thead>
<tbody {...getTableBodyProps()}>
{rows.map((row, i) => {
prepareRow(row);
return (
<tr {...row.getRowProps()}>
{
row.cells.map(cell => {
return <td {...cell.getCellProps()}>{cell.render('Cell')}</td>;
})
}
</tr>
);
})}
</tbody>
</table>
</div>
)
}
The flow is as so:
Data is received
currentData state variable is updated using setCurrentData
ParentComponent is re-rendered
ChildComponent is also rendered and receives currentData via props
Additionally, ChildComponent receives a function handleButtonPress via props
ChildComponent uses react-table (v7.8.0) to display the data, and then via a hook is able to create an additional column in the table, with a button for each row
The button is set to call handleButtonPress from props onClick
Button is pressed and handleButtonPress is called
The above flow is working correctly. The issue occurs at the end of this flow, when the function handleButtonPress is called from ChildComponent, as console.log(currentData) logs an empty array instead of the actual state of currentData. I have verified that currentData is populated before re-render of ParentComponent, and that it successfully reaches ChildComponent.
Would greatly appreciate some help with this! If I had to guess where I'm going wrong, it's that something about passing the function handleButtonPress via props is potentially sending a version of the function before currentData is set, but like I said I'm quite inexperienced with React and that's just a guess!
Thanks for reading.
There are two pieces of missing information that makes it impossible to answer your question with 100% certainty:
How are you invoking fetchCurrentData?
How is tableHooks used and invoked?
However, even without that information, I can give you a pretty good idea about what the issue is.
The first thing to understand is that every time ParentComponent re-renders, you are defining a completely new handleButtonPress function.
The second thing to understand is that the currentData variable in ParentComponent changes identity after setCurrentData is called within the fetchCurrentData function. What that means is that it's actually a completely difference variable on the second re-render of ParentComponent compared to the first.
The third thing to understand is that your handleButtonPress function forms a closure around the currentData variable. However, because the identity of currentData changes between re-renders, the variable being closed over is actually different. In other words, the handleButtonPress function from the first re-render has closed over the currentData variable from the first re-render, which is stuck as []. The handleButtonPress function from the second re-render has closed over the currentData variable from the second re-render, which contains your actual data.
Finally, I suspect that tableHooks is only invoked once during the first render of ChildComponent, and that the props.handleButtonPress function is the first handleButtonPress function which is stuck with the first currentData which is [].
The solution?
Pass through the relevant data through to the handleButtonPress function in the invocation of the function. Ie, change the function signature to:
const handleButtonPress = (row, data) => {
console.log(data);
}
Incidentally, now that function is a pure function - it has no side effects (well, other than logging to console) and depends purely on its inputs. You can move that function to be defined outside of your render loop for a (very small) performance improvement, and a pretty useful way to declare "this is indeed a pure function".
In general, prefer pure functions and avoid relying on side effects to avoid tricky situations like you encountered here.
Suppose we have an input for buyer id and we want to fetch the buyer details each time the buyerId is changed.
The following code looks like this for class components
componentDidUpdate(prevProps,prevState) {
if (this.state.buyerId !== prevState.buyerId) {
this.props.fetchBuyerData(this.state.buyerId); // some random API to search for details of buyer
}
}
But if we want to use useEffect hook inside a functional component how would we control it. How can we compare the previous props with the new props.
If I write it as per my understanding it will be somewhat like this.
useEffect(() => {
props.fetchBuyerData(state.buyerId);
}, [state.buyerId]);
But then react's hooks linter suggests that I need to include props into the dependency array as well and if I include props in the dependency array, useEffect will be called as soon as props changes which is incorrect as per the requirement.
Can someone help me understand why props is required in dependency array if its only purpose is to make an API call.
Also is there any way by which I can control the previous state or props to do a deep comparison or maybe just control the function execution inside useEffect.
Deconstruct props either in the function declaration or inside the component. When fetchBuyerData is used inside the useEffect hook, then only it needs to be listed as a dependency instead of all of props:
// deconstruct in declaration
function MyComponent({ fetchBuyerData }) {
useEffect(() => {
// caveat: something is wrong with the use of `this.state` here
fetchBuyerData(this.state.buyerId);
}, [fetchBuyerData, state.buyerId]);
}
// deconstruct inside component
function MyComponent(props) {
const { fetchBuyerData } = props;
useEffect(() => {
// caveat: something is wrong with the use of `this.state` here
fetchBuyerData(this.state.buyerId);
}, [fetchBuyerData, state.buyerId]);
}
I'd assume you're rewriting your class component info functional one. Then you'd be better off including your fetch request right where you set new state.bayerId (I assume it's not an external prop). Something like:
const [buyerId, setBuyerId] = React.useState('')
const handleOnChange = (ev) => {
const { value } = ev.target
if (value !== buyerId) {
props.fetchBuyerData(value)
setBuyerId(value)
}
...
return (
<input onChange={handleOnChange} value={buyerId} />
...
The code snippet is somewhat suboptimal. For production I'd assume wrap change handler into useCallback for it to not be recreated on each render.
Can I use the && style safe navigation or lodash get in useMemo's second parameter like this:
useMemo(() => {
return {
...
}
}, [state && state.data]) // or with lodash: get(state, 'data')
Yes, but not without warning.
React Hook useMemo has a complex expression in the dependency array.
Extract it to a separate variable so it can be statically checked.
(react-hooks/exhaustive-deps)eslint
export default function App() {
const [state, setState] = useState({ data: 'foobar' });
const memoizedState = useMemo(() => {
return state.data + state.data;
}, [state && state.data]);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>{memoizedState}</h2>
</div>
);
}
Better solution would be to factor it into a single variable as suggested, or use the entire state value and handle it internally. Keep in mind the more you minimize the surface area of reference mutations then the more stable your memoized value will be.
If you are certain a state value like this will always have that shape, then I think you can safely use state.data as a dependency.
I generally prefer NOT to use complex objects in react useState hooks (you certainly can) and will break the object properties into their own state hooks. This allows you to update "parts" of your state without mutating references to the rest. This is especially advantageous in situations like yours where maybe you're not sure state.a.b.c.data is a complete and valid defined object reference. Or some other hooks depend only on part of your component state.
having a parent element which requires a key (because it's in a list creating a warning message), I added key={uuid.v4()}.
This made the message disappear.
Funny things started happening with it's child components, though. When I use the functional setState hook, it doesn't actually assign it to the value anymore (see below [1]). When I remove the key from the parent Component, the same code works (but leaves me with the warning).
When adding a static key, e.g. key={'someComponent'}, the whole component doesn't render at all.
Any tips what I'm missing here?
[1] Child Component which does not update it's state:
function zoomIntoRegion(countryName, filter) {
props.changeFilter({SLMetric: filter})
if (regionZoom === countryName && filter === props.filter) {
setRegionZoom(undefined)
} else {
console.log('before setzoom', countryName) // # before setzoom GERMANY
setRegionZoom(countryName)
console.log('after', regionZoom) // # after undefined
}
}
Keys in React are used for quickly comparing the current children to the children before any change occured. When you set the key on a component to uuid.v4() in the render function, every time the parent component rerenders, you can see that it generates a new key. From the docs:
Keys should be stable, predictable, and unique. Unstable keys (like those produced by Math.random()) will cause many component instances and DOM nodes to be unnecessarily recreated, which can cause performance degradation and lost state in child components.
which seems to accurately define what you are facing.
To work around this problem,
If you do not see the order of your children changing at all, then it is okay to use index as key.
If you do see it changing and have no key in the data that you can use, then move the key generation setup to where you are fetching/generating the data so that it is executed only once. Ensure that keys do not change on render, but only when the data itself is changing.
function App() {
const [items, setItems] = useState([]);
useEffect(() => {
getData()
// mapping over array data and adding the key here
.then((data) => data.map((item) => ({...item, id: uuid.v4() })))
.then((data) => setItems(data))
}, []);
return (
<Fragment>
// using the key
{items.map((item) => {
<div key={item.id}>
</div>
})}
</Fragment>
)
}
When creating a React app, if I use the hook useSelector, I need to adhere to the hooks invoking rules (Only call it from the top level of a functional component). If I use the mapStateToProps, I get the state in the props and I can use it anywhere without any issues... Same issue for useDispatch
What are the benefits of using the hook besides saving lines of code compared to mapStateToProps?
Redux store state can be read and changed from anywhere in the component, including callbacks. Whenever the store state is changed the component rerenders. When the component rerenders, useSelector runs again, and gives you the updated data, later to be used wherever you want. Here is an example of that and a usage of useDispatch inside a callback (after an assignment in the root level):
function Modal({ children }) {
const isOpen = useSelector(state => state.isOpen);
const dispatch = useDispatch();
function handleModalToggeled() {
// using updated data from store state in a callback
if(isOpen) {
// writing to state, leading to a rerender
dispatch({type: "CLOSE_MODAL"});
return;
}
// writing to state, leading to a rerender
dispatch({type: "OPEN_MODAL"});
}
// using updated data from store state in render
return (isOpen ? (
<div>
{children}
<button onClick={handleModalToggeled}>close modal</button>
</div>
) : (
<button onClick={handleModalToggeled}>open modal</button>
);
);
}
There is nothing you can do with mapStateToProps/mapDispatchToProps that you can't do with the useSelector and useDispatch hooks as well.
With that said, there are a couple of differences between the two methods that are worth considering:
Decoupling: with mapStateToProps, container logic (the way store data is injected into the component) is separate from the view logic (component rendering).
useSelector represents a new and different way of thinking about connected components, arguing that the decoupling is more important between components and that components are self contained. Which is better? Verdict: no clear winner. source
DX (Developer experience): using the connect function usually means there should be another additional container component for each connected component, where using the useSelector and useDispatch hooks is quite straightforward. Verdict: hooks have better DX.
"Stale props" and "Zombie child": there are some weird edge cases with useSelector, if it depends on props, where useSelector can run before the newest updated props come in. These are mostly rare and avoidable edge cases, but they had been already worked out in the older connect version. verdict: connect is slightly more stable than hooks. source
Performance optimizations: both support performance optimizations in different ways: connect has some advanced techniques, using merge props and other options hidden in the connect function. useSelector accepts a second argument - an equality function to determine if the state has changed. verdict: both are great for performance in advanced situations.
Types: using typescript with connect is a nightmare. I remember myself feverishly writing three props interfaces for each connected component (OwnProps, StateProps, DispatchProps). Redux hooks support types in a rather straightforward way. verdict: types are significantly easier to work with using hooks.
The future of React: Hooks are the future of react. This may seam like an odd argument, but change to the ecosystem is right around the corner with "Concurrent mode" and "Server components". While class components will still be supported in future React versions, new features may rely solely on hooks. This change will of course also affect third party libraries in the eco system, such as React-Redux. verdict: hooks are more future proof.
TL;DR - Final verdict: each method has its merits. connect is more mature, has less potential for weird bugs and edge cases, and has better separation of concerns. Hooks are easier to read and write, as they are collocated near the place where they are used (all in one self contained component). Also, they are easier to use with TypeScript. Finally, they will easily be upgradable for future react versions.
I think you misunderstand what "top level" is. It merely means that, inside a functional component, useSelector() cannot be placed inside loops, conditions and nested functions. It doesn't have anything to do with root component or components structure
// bad
const MyComponent = () => {
if (condition) {
// can't do this
const data = useSelector(mySelector);
console.log(data);
}
return null;
}
---
// good
const MyComponent = () => {
const data = useSelector(mySelector);
if (condition) {
console.log(data); // using data in condition
}
return null;
}
If anything, mapStateToPtops is located at even higher level than a hook call
the rules of hooks make it very hard to use that specific hook. You still need to somehow access a changing value from the state inside callbacks
To be fair you almost never have to access changing value inside a callback. I can't remember last time I needed that. Usually if your callback needs the latest state, you are better off just dispatching an action and then handler for that action (redux-thunk, redux-saga, redux-observable etc) will itself access the latest state
This is just specifics of hooks in general (not just useSelector) and there are tons of ways to go around it if you really want to, for example
const MyComponent = () => {
const data = useSelector(mySelector);
const latestData = useRef()
latestData.current = data
return (
<button
onClick={() => {
setTimeout(() => {
console.log(latestData.current) // always refers to latest data
}, 5000)
}}
/>
)
}
What are the benefits of using the hook besides saving lines of code compared to mapStateToProps?
You save time by not writing connect function any time you need to access store, and removing it when you no longer need to access store. No endless wrappers in react devtools
You have clear distinction and no conflicts between props coming from connect, props coming from parent and props injected by wrappers from 3rd party libraries
Sometimes you (or fellow developers you work with) would choose unclear names for props in mapStateToProps and you will have to scroll all the way to mapStateToProps in the file to find out which selector is used for this specific prop. This is not the case with hooks where selectors and variables with data they return are coupled on the same line
By using hooks you get general advantages of hooks, the biggest of which is being able couple together and reuse related stateful logic in multiple components
With mapStateToProps you usually have to deal with mapDispatchToProps which is even more cumbersome and easier to get lost in, especially reading someone else's code (object form? function form? bindActionCreators?). Prop coming from mapDispatchToProps can have same name as it's action creator but different signature because it was overridden in mapDispatchToprops. If you use one action creator in a number of components and then rename that action creator, these components will keep using old name coming from props. Object form easily breaks if you have a dependency cycle and also you have to deal with shadowing variable names
.
import { getUsers } from 'actions/user'
class MyComponent extends Component {
render() {
// shadowed variable getUsers, now you either rename it
// or call it like this.props.getUsers
// or change import to asterisk, and neither option is good
const { getUsers } = this.props
// ...
}
}
const mapDispatchToProps = {
getUsers,
}
export default connect(null, mapDispatchToProps)(MyComponent)
See EDIT 2 at the end for the final answer
Since no one knows how to answer, it seems like the best answer is that you should NOT be using useselector when you need information in other places other than the root level of your component. Since you don't know if the component will change in the future, just don't use useselector at all.
If someone has a better answer than this, I'll change the accepted answer.
Edit: Some answers were added, but they just emphasize why you shouldn't be using useselector at all, until the day when the rules of hooks will change, and you'll be able to use it in a callback as well. That being said, if you don't want to use it in a callback, it could be a good solution for you.
EDIT 2: An answer with examples of all that I wanted was added and showed how useSelector and useDispatch are easier to use.
The redux state returned from the useSelector hook can be passed around anywhere else just like its done for mapStateToProps. Example: It can be passed to another function too. Only constraint being that the hook rules has to be followed during its declaration:
It has to be declared only within a functional component.
During declaration, it can not be inside any conditional block . Sample code below
function test(displayText) {
return (<div>{displayText}</div>);
}
export function App(props) {
const displayReady = useSelector(state => {
return state.readyFlag;
});
const displayText = useSelector(state => {
return state.displayText;
});
if(displayReady) {
return
(<div>
Outer
{test(displayText)}
</div>);
}
else {
return null;
}
}
EDIT: Since OP has asked a specific question - which is about using it within a callback, I would like to add a specific code.In summary, I do not see anything that stops us from using useSelector hook output in a callback. Please see the sample code below, its a snippet from my own code that demonstrates this particular use case.
export default function CustomPaginationActionsTable(props) {
//Read state with useSelector.
const searchCriteria = useSelector(state => {
return state && state.selectedFacets;
});
//use the read state in a callback invoked from useEffect hook.
useEffect( ()=>{
const postParams = constructParticipantListQueryParams(searchCriteria);
const options = {
headers: {
'Content-Type': 'application/json'
},
validateStatus: () => true
};
var request = axios.post(PORTAL_SEARCH_LIST_ALL_PARTICIPANTS_URI, postParams, options)
.then(function(response)
{
if(response.status === HTTP_STATUS_CODE_SUCCESS) {
console.log('Accessing useSelector hook output in axios callback. Printing it '+JSON.stringify(searchCriteria));
}
})
.catch(function(error) {
});
}, []);
}
For callback functions you can use the value returned from useSelector the same way you would use the value from useState.
const ExampleComponent = () => {
// use hook to get data from redux state.
const stateData = useSelector(state => state.data);
// use hook to get dispatch for redux store.
// this allows actions to be dispatched.
const dispatch = useDispatch();
// Create a non-memoized callback function using stateData.
// This function is recreated every rerender, a change in
// state.data in the redux store will cause a rerender.
const callbackWithoutMemo = (event) => {
// use state values.
if (stateData.condition) {
doSomething();
}
else {
doSomethingElse();
}
// dispatch some action to the store
// can pass data if needed.
dispatch(someActionCreator());
};
// Create a memoized callback function using stateData.
// This function is recreated whenever a value in the
// dependency array changes (reference comparison).
const callbackWithMemo = useCallback((event) => {
// use state values.
if (stateData.condition) {
doSomething();
}
else {
doSomethingElse();
}
// dispatch some action to the store
// can pass data if needed.
dispatch(someActionCreator());
}, [stateData, doSomething, doSomethingElse]);
// Use the callbacks.
return (
<>
<div onClick={callbackWithoutMemo}>
Click me
</div>
<div onClick={callbackWithMemo}>
Click me
</div>
</>
)
};
Rules of hooks says you must use it at the root of your component, meaning you CANT use it anywhere.
As Max stated in his answer just means that the hook statement itself must not be dynamic / conditional. This is because the order of the base hooks (react's internal hooks: useState, etc) is used by the backing framework to populate the stored data each render.
The values from hooks can be used where ever you like.
While I doubt this will be close to answering your complete question, callbacks keep coming up and no examples had been posted.
not the answer but this hook can be very helpful if you want to get decoupled nature of mapDispatchToProps while keeping simplicity and dev experience of hooks:
https://gist.github.com/ErAz7/1bffea05743440d6d7559afc9ed12ddc
the reason I don't mention one for mapStatesToProps is that useSelector itself is more store-logic-decoupling than mapStatesToProps so don't see any advantage for mapStatesToProps. Of course I dont mean using useSelector directly but instead create a wrapper on it in your store files (e.g. in reducer file) and import from there, like this:
// e.g. userReducer.js
export const useUserProfile = () => useSelector(state => state.user.profile)