We are using React with Redux (and Redux Saga) and React functional components in our project. Each component has a reactive form. When a submit button in a certain form is clicked, a backend request is made. I have a task to send the access token with that request. Here’s how it works right now:
Component:
export const MyComponent: React.FC = () => {
...
const form = useReactiveForm({
initialState: {
...
},
validation: ...,
callback: (values: MyFormInputs) => {
...
dispatch(doBackendCall());
}
},
...
return (
<QuestionStack onSubmit={form.handleSubmit}>
/* form fields */
</QuestionStack>
);
});
Action Creators:
export const doBackendCall = () =>
({
type: BACKEND_CALL,
}) as const;
Saga:
function* makeBackendCall() {
...
const response: AxiosResponse<MyBackendResponse> = yield call(
callMyBackend,
myRequestData,
myKey,
);
...
}
...
export function* watchMyComponent(): Generator<ForkEffect> {
try {
yield takeEvery(
myActionTypes.BACKEND_CALL,
makeBackendCall,
);
...
}
}
Backend call:
export const callMyBackend = (
information: MyRequest,
myKey = '',
): Promise<AxiosResponse<MyResponse>> =>
myService.put<MyResponse>(
`/my-service/${myKey}`,
information,
);
Now, I understood that to get the accessToken, I should call getAccessTokenSilently()
. So I tried calling that at the beginning of my functional component (which required me to make the component async
since I need to use await
), passing the token to the doBackendCall
method, adding a reducer entry to store the access key to the state, creating a selector to fetch that token from the state, calling the selector in the saga to obtain the token, and then passing that token to the method call in the saga. Here’s the modified code:
Component:
export const MyComponent: React.FC = async () => {
...
const { getAccessTokenSilently } = useAuth0();
const accessToken = await getAccessTokenSilently();
console.log(accessToken);
const form = useReactiveForm({
initialState: {
...
},
validation: ...,
callback: (values: MyFormInputs) => {
...
dispatch(doBackendCall(accessToken));
}
},
...
return (
<QuestionStack onSubmit={form.handleSubmit}>
/* form fields */
</QuestionStack>
);
});
Action Creators:
export const doBackendCall = (accessToken: string) =>
({
type: BACKEND_CALL,
accessToken
}) as const;
Reducer:
case BACKEND_CALL:
return {
...state,
accessToken: action.accessToken,
};
Selector:
export const getAccessToken = (state: State): string | undefined => {
return state.accessToken;
};
Saga:
function* makeBackendCall() {
...
const accessToken: string | undefined = yield select(getAccessToken);
let config;
if (!!accessToken) {
config = {
headers: {
Authorization: `Bearer ${accessToken}`,
},
};
}
const response: AxiosResponse<MyBackendResponse> = yield call(
callMyBackend,
myRequestData,
myKey,
config,
);
...
}
...
export function* watchMyComponent(): Generator<ForkEffect> {
try {
yield takeEvery(
myActionTypes.BACKEND_CALL,
makeBackendCall,
);
...
}
}
Backend call:
export const callMyBackend = (
information: MyRequest,
myKey = '',
config?: Record<string, unknown>,
): Promise<AxiosResponse<MyResponse>> =>
myService.put<MyResponse>(
`/my-service/${myKey}`,
information,
config,
);
When I do this, the application breaks when this page is loaded, and I get the error message:
Server Error
Error: Objects are not valid as a React child (found: [object Promise]).
If you meant to render a collection of children, use an array instead.
Also, the console.log
doesn’t happen, so I guess that the getAccessTokenSilently
call is the step where the app breaks.
How can I fix this?