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.14k stars 179 forks source link

useState is rebuilding the whole widget, what should I do if I need only Text widget rebuilt when the button pressed? #258

Closed selegiline closed 3 years ago

selegiline commented 3 years ago

class UseStateExample extends HookWidget {
  @override
  Widget build(BuildContext context) {
    final counter = useState(0);
    print("UseStateExample build");
    return Scaffold(
      appBar: AppBar(
        title: const Text('useState example'),
      ),
      body: Center(
        child: HookBuilder(
          builder: (_) => Text('Button tapped ${counter.value} times'),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => counter.value++,
        child: const Icon(Icons.add),
      ),
    );
  }
}
`
selegiline commented 3 years ago
final counterProvider = StateNotifierProvider<Counter, int>((_) => Counter());

class Counter extends StateNotifier<int> {
  Counter() : super(0);

  void increment() => state++;
}

class UseStateExample extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    print('build');
    return Scaffold(
      appBar: AppBar(
        title: const Text('Riverpod counter example'),
      ),
      body: Center(
        child: HookBuilder(
          builder: (context) {
            final count = ref.watch(counterProvider);
            return Text(
              '$count',
              style: Theme.of(context).textTheme.headline4,
            );
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => ref.read(counterProvider.notifier).increment(),
        child: const Icon(Icons.add),
      ),
    );
  }
}

another sample as the riverpod document

francipvb commented 3 years ago

Hello,

This is the expected behavior, as I know.

davidmartos96 commented 3 years ago

@selegiline You could use useValueNotifierinstead of useState because it doesn't trigger a rebuild and combine it with listening to the ValueNotifier near the Text widget (HookBuilder or a different Hook widget required).

In any case, if you are doing this too much, that's where provider or riverpod shine.

class UseStateExample extends HookWidget {
  @override
  Widget build(BuildContext context) {
    final counterVN = useValueNotifier(0);
    print("UseStateExample build");
    return Scaffold(
      appBar: AppBar(
        title: const Text('useState example'),
      ),
      body: Center(
        child: HookBuilder(
          builder: (_) {
            final counter = useValueListenable(counterVN);
            return Text('Button tapped ${counter} times');
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => counterVN.value++,
        child: const Icon(Icons.add),
      ),
    );
  }
}
rrousselGit commented 3 years ago

Indeed this is the expected behavior, and David's example is correct.

Although I would argue that the gain is so minimal that I would prefer the more readable option.