SignUpSaga is broken. It doesn't wait for "yield put(updateUserAction(user)); " to return a value, it just returns undefined breaking the app then, sign up service returns after.
How do I make it wait? I thought this is what yielding does.
Parts of redux package:
export const updateUserAction = (user: User | null): UpdateUserActionType => ({
type: UPDATE_USER,
user,
});
...
export const userReducer = (
state: StateSlice = initialState.user,
action: UpdateUserActionType
): StateSlice => {
switch (action.type) {
case UPDATE_USER:
return updateHandler(state, action);
case SIGN_OUT:
return signOutHandler();
default:
return state;
}
};
Interactor
export class SignUpInteractor {
signUpService: SignUpService;
constructor(signUpService: SignUpService) {
this.signUpService = signUpService;
}
async signUp(
firstName: string,
lastName: string,
credential: Credential
): Promise<User> {
const user = new User(firstName, lastName, credential.email);
return this.signUpService.signUpUser(user, credential);
}
}
saga
function* signUpSaga(action: SignUpActionType) {
const { firstName, lastName, credential } = action;
const service = new FirebaseLogin();
const interactor = new SignUpInteractor(service);
const user = yield call(() => {
interactor.signUp(firstName, lastName, credential);
});
yield put(updateUserAction(user));
}
Finally, sign up service:
export class FirebaseLogin implements SignUpService {
async signUpUser(user: User, credential: Credential): Promise<User> {
var user: User;
try {
db.app
.auth()
.createUserWithEmailAndPassword(credential.email, credential.password)
.then((authData) => {
db.app
.database()
.ref("users/" + authData.user.uid)
.set({
uid: authData.user.uid,
email: credential.email,
firstName: user.firstName,
lastName: user.lastName,
})
.then(() => {
db.app
.database()
.ref("users/" + authData.user.uid)
.once("value")
//do this to make sure data was returned as values, not resolved later by firebase.
.then((snapchat) => {})
.then(() => {
console.log("returning");
return user;
});
})
.catch((error) => {
return new User("error", "error", "err@err.com");
});
})
.catch((error) => {
console.log("error here");
return new User("error", "error", "err@err.com");
});
} catch (error) {
console.log("error here");
return new User("error", "error", "err@err.com");
}
}
I think the primary problem is how you're using call
:
const user = yield call(() => {
interactor.signUp(firstName, lastName, credential);
});
You're passing a synchronous function that will start an async call and return immediately. In addition, that function returns nothing, hence the undefined
.
What you really want is to tell the middleware to make the async call, and thus wait for the promise to be resolved:
const user = yield call(interactor.signUp, firstName, lastName, credential);
Also, it looks like your code is wayyyy more complicated than it needs to be: writing Redux code by hand, extra functions in the reducers, defining separate TS types for action objects, and also this "interactor" and "service" bit. Even using sagas is overkill here for this example. You should be using our official Redux Toolkit package and following our recommendations for using Redux with TS. That will simplify your code dramatically.
If I was writing this myself, I'd have the signUp
part be a standalone async function, and use RTK's createAsyncThunk
:
async function signUpUser(user: User, credential: Credential): Promise<User> {
// omit Firebase code
return user;
}
interface SignUpArgs {
firstName: string;
lastName: string;
credential: Credential;
}
const signUp = createAsyncThunk(
'users/signUp',
async ({firstName, lastName, credential}: SignUpArgs) => {
const user = new User(firstName, lastName, credential.email);
return signUpUser(user, credential);
}
)
type UserState = User | null;
const initialState : UserState = null;
const userSlice = createSlice({
name: 'users',
initialState, // whatever your state actually is,
reducers: {
// assorted reducer logic here
signOut(state, action) {
return null;
}
},
extraReducers: builder => {
builder.addCase(signUp.fulfilled, (state, action) => {
return action.payload
});
}
})
Much less code in the end, especially if you're doing any actual meaningful state updates in the reducers.