rrousselGit / flutter_hooks

React hooks for Flutter. Hooks are a new kind of object that manages a Widget life-cycles. They are used to increase code sharing between widgets and as a complete replacement for StatefulWidget.
MIT License
3.07k stars 175 forks source link

Returning a new Dart 3 type `Record` from the `useState` hook #366

Open jezsung opened 1 year ago

jezsung commented 1 year ago

This library currently returns a ValueNotifier which is a wrapper class for the value of the useState.

With class destructuring, we can destructure a ValueNotifier like the below:

var ValueNotifier(:value) = useState<String>('Hello World!');

In this approach, it's impossible to give another variable name for the value which is not flexible.

Dart 3 has introduced a new feature called Pattern with array destructuring.

Basically, we can write code like the following:

var numList = [1, 2, 3];
var [a, b, c] = numList;

I propose to change the return value to an array and return the value and the function that can modify the value.

final [state, setState] = useState('Hello World!');

In this way, we can declare the state as a final variable and make a change by calling the setState function.

rrousselGit commented 1 year ago

In this approach, it's impossible to give another variable name for the value which is not flexible.

That isn't true. You can do:

var ValueNotifier(value: myName) = useState(..);

But to begin with, you probably wouldn't use destructuring for useState with ValueNotifier.

We could change useState to return a tuple (not a list though). But that would be quite breaking.

jezsung commented 1 year ago

@rrousselGit

Thanks for correcting me. I didn't know that I can give a custom name when destructuring class.

I realized returning a List isn't type-safe. So I made a custom hook that returns a Record:

typedef Updater<T> = T Function(T previousData);

typedef SetState<T> = void Function(Updater<T> updater);

(T, SetState<T>) useDart3State<T>(T initialData) {
  final valueNotifier = useState<T>(initialData);

  setState(Updater<T> updater) {
    valueNotifier.value = updater(valueNotifier.value);
  }

  return (valueNotifier.value, setState);
}

I can be used like this:

final (text, setText) = useDart3State<String>('Hello World');

It does exactly what I described, but implementing this to the useState hook would be a quite breaking change. I wonder if this is worth it.

jezsung commented 12 months ago

I found this syntax is just a new way of declaring variables and it doesn't really call the underlying setter of the ValueNotifier.

var ValueNotifier(:value) = useState(0);

// Does not actually increment. 
value++;

I believe added advantage of returning a Record over a ValueNotifier would be:

• Separated way of reading and updating the value • Succinct one-line variable declaration • More React-like syntax

I think this change is worth it. This would affect a huge codebase for developers but considering that it's just a minor syntax change, there wouldn't be much headache while migrating to this new syntax. Also, this would attract more developers who have a React background. What do you think? @rrousselGit

rrousselGit commented 12 months ago

I found this syntax is just a new way of declaring variables and it doesn't really call the underlying setter of the ValueNotifier.

var ValueNotifier(:value) = useState(0);

There's no reason to use destructuring though.

You'd write:

final myState = useState(0);
myState.value++;
rrousselGit commented 12 months ago

Making this change is heavily breaking. The only value is being closer to react, and wining a tiny bit of memory. I'm not sure that's worth it.

bruce3x commented 10 months ago

Maybe provide a new API?

final [count, setCount] = useStateValue(123);
listepo commented 6 months ago

Nice idea