-
Notifications
You must be signed in to change notification settings - Fork 47.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add get
function to useState
#14543
Comments
@liyuanqiu you can use updater function in setCount
|
@Saranchenkov Thank you very much, it's all my fault haven't read the document carefully. But I have another question. As I said before:
Sometimes I want to do some side effect in hooks like const [count, setCount] = useState(0);
useEffect(() => {
// send count to server every 5 seconds
const id = setInterval(() => {
xhr(count);
}, 5000);
return () => clearInterval(id);
}, []); If I pass In this situation, maybe a get function is needed? |
You could probably do something like this const [count, setCount] = useState(0);
const countRef = useRef(count)
useEffect(() => {
countRef.current = count
}, [count])
useEffect(() => {
// send count to server every 5 seconds
const id = setInterval(() => {
xhr(countRef.current);
}, 5000);
return () => clearInterval(id);
}, []); |
Thank you @escaton , And the official document thinks this is
But I think it is more like a workaround, couldn't be a paradigm. One component may have many states. Using three kinds of hooks and five lines of code(or using a custom hook to replace |
I really like the suggestion for having pair of getter and setter returned from const useGetterState = (initialState) => {
const [state, setState] = useState(initialState);
const stateRef = useRef(state);
useEffect(() => {
stateRef.current = state
}, [state]);
return [() => stateRef.current, setState];
} ? |
@liyuanqiu
const [count, setCount] = useState(0);
const timerAdjustment = useRef(0)
useEffect(() => {
let id;
let absoluteTimeout;
function tick(firstTime) {
// call xhr only on subsequent calls
!firstTime && xhr(count)
// schedule timer considering previous call
const adjustment = timerAdjustment.current
const timeout = adjustment > 0 ? adjustment : 5000
// remember absolute time to calc adjusted timeout later
absoluteTimeout = Date.now() + timeout
// reset timer adjustment
timerAdjustment.current = 0
id = setTimeout(tick, timeout)
}
tick(true)
return () => {
clearTimeout(id)
// set timer adjustment
timerAdjustment.current = absoluteTimeout - Date.now()
}
}, [count]) @ignatiusreza there is the problem with your solution: [getCount, setCount] = useGetterState(0)
return (
<button onClick={() => setCount(c => c+1)}>
{getCount()} - increment
</button>
) on the first render in would be "0 - increment", but after click it would be still "0 - increment" and only on second click it will update. That's because you mutate the reference in function useStateWithGetter(initial) {
const [state, setState] = useState(initial)
const ref = useRef(state)
const updater = value => {
if (typeof value === 'function') {
setState(prev => {
const result = value(prev);
ref.current = result
return result
})
} else {
ref.current = value
setState(value)
}
}
const getter = () => ref.current
return [state, updater, getter]
} there are still issues, because now we referencing last scheduled state, not the current. upd: function useStateWithRef(initial) {
const [state, setState] = useState(initial)
const ref = useRef()
ref.current = state
return [state, setState, ref]
} |
Just to set expectations, we’ve considered all these options a few months ago and decided against them at the time. I’ll keep this open so we can later provide a better response. I don't remember off the top of my mind what the problems were. |
I think this is an elegant solution: https://codesandbox.io/s/m1y7vl0vp function App() {
const [count, setCount] = useState(0);
// ***** Initialize countRef.current with count
const countRef = useRef(count);
const handleClick = useCallback(() => setCount(add1), []);
useEffect(() => {
// ***** countRef.current is xhr function argument
const intervalId = setInterval(() => xhr(countRef.current), 5000);
return () => clearInterval(intervalId);
}, []);
// ***** Set countRef.current to current count
countRef.current = count;
return (
<div>
<p>You clicked {count} times</p>
<button onClick={handleClick}>Click me</button>
</div>
);
} |
@escaton Maybe only the second option is effective.
Do you mean https://codesandbox.io/s/jj40lz07l3 ? /* code snippets */
const [count, setCount] = useState(0);
const repeat = useRef(() => xhr(count));
useEffect(() => {
const intervalId = setInterval(repeat.current, 1000);
return () => clearInterval(intervalId);
}, []); Not working too.
It's not the timer's problem. When you use event dispatcher, WebSocket, ajax, promise, same problem. I think no one dares to reimplement them just for adopting React Hooks API. |
@btraljic yeah, i suggested the same thing
it should work, but it brings side effect @liyuanqiu https://codesandbox.io/s/72jlzz1o86 +useEffect(
+ () => {
+ repeat.current = () => xhr(count);
+ },
+ [count]
+ );
-const intervalId = setInterval(repeat.current, 1000);
+const intervalId = setInterval(() => repeat.current(), 1000);
By the reimplementation i mean the proper restart of effect, not the |
@btraljic Thank you. countRef.current = count; @see https://reactjs.org/docs/hooks-reference.html#useeffect
Any state change will lead this line been executed. Although in this example, there's no wrong. If doing so, I think maybe it's better to put it in useLayoutEffect(() => {
countRef.current = count;
}); And think deeper, here we rely on the layout update to update So finally, it turns back to: |
Is it ok now :) |
@btraljic not really :) |
@escaton Ok, but we are living in an asynchronous world. Aren't we? :) |
Speaking about accessing state in const [count, setCount] = useState(0)
const [tick, setTick] = useState(0)
useEffect(() => {
const timerId = setInterval(() => {
setTick(t => t+1)
}), 5000)
return () => clearInterval(timerId)
}, [])
useEffect(() => {
xhr(count)
}, [tick]) Pros: easy to understand what happens, no refs |
I usually use this way with reducer const [count, setCount] = useState(0)
const [commitIndex, commit] = React.useReducer(state => state +1, 0)
useEffect(() => {
const timerId = setInterval(commit, 5000)
return () => clearInterval(timerId)
}, [])
useEffect(() => {
xhr(count)
}, [commitIndex]) |
If we can use reft to hold state, why not just use an global object? const obj = {}
function Comp() {
const [count, setCount] = useState(0)
useEffect(() => {
xhr(obj.count)
})
obj.count = count
} So |
Doing so, you can't use more than one function CompFabric () {
const obj = {}
return function Comp() {
const [count, setCount] = useState(0)
useEffect(() => {
xhr(obj.count)
})
obj.count = count
}
} Although it's almost same as |
Everybody here tells many solutions, but really like a sentence: "Life, Uh, Finds a Way." Look back, my requirement is so easy, just want to repeatedly send a state to server. But with React Hooks API, it became strange and complex. Let's see how to program in old class style: https://codesandbox.io/s/40p9qqr009 class App extends React.Component {
state = {
count: 0,
};
handleClick = () => {
const { count } = this.state;
this.setState({
count: count + 1,
});
};
xhr = () => {
const { count } = this.state;
console.log(`Send ${count} to server.`);
// TODO send count to my server by XMLHttpRequest
};
componentDidMount() {
this.intervalId = setInterval(this.xhr, 1000);
}
componentWillUnmount() {
clearInterval(this.intervalId);
}
render() {
const { count } = this.state;
return (
<div>
<p>You clicked {count} times</p>
<button onClick={this.handleClick}>Click me</button>
</div>
);
}
} Naturally and reasonable, right? No magic, no tricks. I hope Hooks API will finally be so. I complain a lot, don't diss me 🙏 XD |
Sure, it looks familiar. |
I guess this is it https://overreacted.io/making-setinterval-declarative-with-react-hooks/ |
In this post, a pattern is introduced to capsulate those APIs who has “impedance mismatch” with the React programming model. So we have to write helper functions(custom hooks) to help us using these APIs. That's still annoying. Although Dan's We may encounter many APIs that has “impedance mismatch” with the React programming model. Capsulating them one by one just like
|
Ok so it seems like the solution to this is this:??
I def see the value in having a getter on hooks. |
On second thought.. why not just do this??
|
mark |
@gaearon You mentioned this was discussed previously and left it open. Can we start this conversation back up? Or at the very least, verify that the solution above is an ok practice. |
https://overreacted.io/how-are-function-components-different-from-classes/ |
Just read that article... it basically reassures the solution above would work for adding a getter. |
|
Until now, Do we still have to do use |
@joepuzzo I like the version with no
|
Yup That would do the trick @johnnysprinkles ! I have done that in the past as well. For anyone here that is saying that a getter is not a good pattern I disagree. I have been developing an OS form library for years and this pattern becomes valuable. The key is some things in JS might have a reference to a value in state and they cant use the outdated version. You either pass around a ref or you use a getter. |
I think set |
Just for discussion, with setter, getter:
or with a Proxy:
|
Do you want to request a feature or report a bug?
What is the current behavior?
Code from Introducing Hooks:
I don't think it is good to create a fixed function many times, so I try to modify the code:
(Update on Jul 2022: No matter using the inline anonymous function or wrapping with
useCallback
, the function will always be created. The difference is that, inuseCallback
approach, the function reference will not be changed, which could be helpful if we usememo
to wrap the component who receives the function as a property)But obviously the callback in
useCallback
couldn't get the latestcount
because I pass in an empty inputs array to avoid this callback been generated again and again.So, in fact, the inputs array decide two things:
In most situation, the two things are one thing, but here they conflict.
So I think maybe it's good to add a
get
function touseState
like this:Maybe it's confusing because
getCount
can totally replacecount
, but it brings the possible to avoid creating callbacks again and again.Edited
#14543 (comment) exactly resolves the case above. But there‘re many other scenarios can't use
updater
to resolve. Here are some more code snippets:1. Access states in a timer.
2. Access states in WebSocket callbacks
3. Access states in Promise
4. Access states in event callbacks
We had to use some workaround patterns to resolve those cases, like
#14543 (comment)
#14543 (comment)
#14543 (comment)
Or a funny way:
So let's discuss and wait...
#14543 (comment)
If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem. Your bug will get fixed much faster if we can run your code and it doesn't have dependencies other than React. Paste the link to your JSFiddle (https://jsfiddle.net/Luktwrdm/) or CodeSandbox (https://codesandbox.io/s/new) example below:
What is the expected behavior?
Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?
The text was updated successfully, but these errors were encountered: