Using Flutter Hooks library, I'm getting a race conflict when I try to use the navigator in useEffect

1.3k views Asked by At

Flutter Hooks useEffect Docs

I dispatch an API request in my onSubmit event that has a side effect of turning signupHelper.state.success to true. I would like to navigate to another screen when success == true. Instead I get an error for setState() or markNeedsBuild() called during build

My current workaround is to wait 50 milliseconds before navigation to make sure there is no rebuild going on.

My code looks like this

import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import '../hooks/use_signup_helper.dart'


class SignupForm extends HookWidget {
  const SignupForm({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    // this wraps useReducer and gives me access to state and dispatch
    final signupHelper = useSignupHelper();  

    useEffect(() {
     
      if (signupHelper.state.success) {
        
        // this is my workaround - delay 50 milliseconds to avoid rebuild conflict
        // Future<void>.delayed(const Duration(milliseconds: 50))
        //    .then((_) => Navigator.pushNamed(context, '/home'));
    
        Navigator.pushNamed(context, '/home'));
      }
      return null;
    }, [signupHelper.state.success]);

    return ... // UI goes here
2

There are 2 answers

0
Irrevocable On

It looks like we can use the ScheduleBinding and SchedulerPhase class. It is imported like this -

import 'package:flutter/scheduler.dart';

And the new useEffect function looks like this -

    useEffect(() {
      if (signupHelper.state.success) {
        if (SchedulerBinding.instance.schedulerPhase != SchedulerPhase.idle)
          SchedulerBinding.instance.endOfFrame.then((_) {
            Navigator.pushNamed(context, '/home');
          });
        else
          Navigator.pushNamed(context, '/home');
      }
      return null;
    }, [signupHelper.state.success]);
0
Allen Wong On

I have same issues and I came across wrap the Navigator.push with Future.microtask

    useEffect(() {
      if (condition) {
        Future.microtask(() async {
          PageResult result = await Navigator.push(
            context,
            MaterialPageRoute(
              builder: (context) => NextPage(),
            ),
          );
          if (result == null) {
            Navigator.of(context).pop();
          } else {
            // ...
          }
        });
      }
      return;
    }, [value]);