Object is possibly undefined even with if statement - reactjs

I've got a Typescript error TS2532: Object is possibly 'undefined'. I'm creating an SPA using Typescript, and on one of the pages, then you have the possibility of uploading a file. The backend is already written, so the file stored in state will be made into a binary code which the server side reads. The file setstate is set to undefined.
I've done a few searches, but the solutions don't seem to work for me. The first solution was to create an if statement:
if (this.state.uploadFile !== undefined) {
const terms = this.state.uploadFile;
// Logic
}
Whenever terms is used in the logic portion, then I get the error mentioned above.
The other solution is to tell the compiler that it is definetly defined:
const terms = this.state!.uploadFile;
This still throws the same error, and thought I might have placed the ! in the wrong place, but when I move it to const terms = this.state.termSheet!; then that causes a new error when calling terms.getAsBinary() I get the error Property 'getAsBinary' does not exist on type 'never'
Code in context:
// Imports
class SubmitIssue extends React.Component<StylesProps> {
state = {
alert: false,
amount: '',
uploadFile: undefined,
verify: false,
}
handleAmountChange(e) {
this.setState({ amount: e.target.value });
}
handleFileChange(e) {
this.setState({ uploadFile: e.target.files[0] });
}
handleVerifyChange() {
this.setState({ verify: !this.state.verify });
}
handleClick() {
const config = { headers: { 'Content-Type': 'multipart/form-data' } };
const bodyFormData = new FormData();
bodyFormData.set('Amount', this.state.amount);
bodyFormData.set('uploadFile', this.state.termSheet.getAsBinary());
bodyFormData.set('Verify', this.state.verify.toString());
axios.post(
`${process.env.API_URL}RegisterIssue/Create`,
bodyFormData,
config
).then(res => {
console.log(res);
this.setState({ alert: true }, function() { this.sendData(); });
}).catch((error) => {
console.log(error);
})
}
sendData() {
const alertState = this.state.alert;
this.props.parentAlertCallback(alertState);
}
render() {
return (
// Design
);
}
}
export default withStyles(styles)(SubmitIssue);
So, I'm a little stumped as to what the correct way to handle this error is.

It is because you check only state.uploadFile but use state.termSheet.
There are two possible solutions:
You cat set a default value (in case terms is not defined):
const terms = this.state.termSheet ? this.state.termSheet : defaultValue;
Or you check the terms in the if statement, too:
if (this.state.uploadFile && this.state.termSheet)

Related

Saving an ID value from an API to a User with GraphQL

I'm working on a video game website where a user can save a game to a list. How this is supposed to work is when the user clicks "Complete Game", the ID of the game is saved to a state that holds the value. The value is then passed into the mutation, then the mutation runs, saving the ID of the game to the users list of completed games. However, all I'm seeing in the console is this:
"GraphQLError: Variable \"$addGame\" got invalid value { gameId: 740, name: \"Halo: Combat Evolved\",
The above error continues, listing the entirety of the API response, instead of just the gameId.
I was able to successfully add the game to the list in the explorer with the following mutation:
mutation completeGame($addGame: AddNewGame!) {
completeGame(addGame: $addGame) {
_id
completedGameCount
completedGames {
gameId
}
}
}
with the following variable:
{
"addGame": {"gameId": 740}
}
How can I trim down what is being passed into the mutation to just be the gameId?
Below is the entirety of the page, except the return statement at the bottom.
const [selectedGame, setSelectedGame] = useState([]);
const [savedGameIds, setSavedGameIds] = useState(getSavedGameIds());
const [completeGame, { error }] = useMutation(COMPLETE_GAME);
const { id: gameId } = useParams();
useEffect(() => {
return () => saveGameIds(savedGameIds);
});
useEffect(() => {
async function getGameId(gameId) {
const response = await getSpecificGame(gameId);
if (!response.ok) {
throw new Error('Something went wrong...');
}
const result = await response.json();
const gameData = result.map((game) => ({
gameId: game.id,
name: game.name,
cover: game.cover,
summary: game.summary,
platforms: game.platforms,
platformId: game.platforms,
genres: game.genres,
genreId: game.genres,
}));
setSelectedGame(gameData);
}
getGameId(gameId);
}, [])
const handleCompleteGame = async (gameId) => {
const gameToComplete = selectedGame.find((game) => game.gameId === gameId);
const token = Auth.loggedIn() ? Auth.getToken() : null;
if (!token) {
return false;
}
try {
const { data } = await completeGame({
variables: { addGame: { ...gameToComplete } },
});
console.log(data);
setSavedGameIds([...savedGameIds, gameToComplete]);
} catch (err) {
console.error(err);
}
};
With the mutation working in the explorer when I'm able to explicitly define the variable, I am led to believe that the issue is not with the resolver or the typedef, so I'm going to omit those from this post because I don't want it to get too long.
However, I'd be happy to attach any extra code (resolver, typeDef, getSavedGameIds function, etc) if it would allow anyone to assist. The issue (I think) lies in getting my response to match the syntax I used in the explorer, which means trimming down everything except the gameId.
I specifically am extremely suspicious of this line
const gameToComplete = selectedGame.find((game) => game.gameId === gameId)
but I have fiddled around with that for awhile to no avail.
Thank you to anyone who is able to help!
It sounds like you're trying to pass more into your mutation then your schema is defined to allow. In this part:
const { data } = await completeGame({
variables: { addGame: { ...gameToComplete } },
});
You're spreading gameToComplete here which means everything in the gameToComplete object is going to be sent as a variable. If your schema is setup to just expect gameId to be passed in, but your error message is showing that name is also being passed in, you just need to adjust your variables to exclude everything you can't accept. Try:
const { data } = await completeGame({
variables: { addGame: { gameId } },
});

Cannot convert undefined or null to object : Next.js || React

Currently receiving a error stating Cannot convert undefined or null to object
The data the app is looking for comes from initial props.
I assume on the initial check no data is present, so it throws that error.
Would this be solved with an async/await ?
The initial posts_mentions is defaulted as an empty object
Here is the error image:
Here is the current code snippet
const { posts_mentions: postsMentions } = useData();
const data = Object.keys(postsMentions).map(label => {
return {
name: shortName(label),
posts: postsMentions[label].posts,
mentions: postsMentions[label].mentions
}
})
async function something(){
const { posts_mentions: postsMentions } = await useData();
const data = Object.keys(postsMentions).map(label => {
return {
name: shortName(label),
posts: postsMentions[label].posts,
mentions: postsMentions[label].mentions
}
})
}
Yes. Try adding await before useData(). And if this is all part of bigger function than mark it as async. How does the useData() look? Is something async in it?

How to reset recaptcha when using react-redux-firebase

I am working with React-Redux-Firebase. I implemented signing in with phone number. Now I am trying to implement error handling. When number is invalid I display window alert with error message. The only thing left to do is to reset recaptcha. Without it, I am getting error:
reCAPTCHA has already been rendered in this element
I was trying to do according to Firebase documentation
grecaptcha.reset(window.recaptchaWidgetId);
// Or, if you haven't stored the widget ID:
window.recaptchaVerifier.render().then(function(widgetId) {
grecaptcha.reset(widgetId);
}
but it does not work in my code. I dont have grecaptcha implemented. I tried to add it with react-grecaptcha, but it did not work.
Could someone give me a hint how to reset recaptcha after each error, please?
state = {
phone: "",
confirmationResult: {},
};
handleClick = () => {
const recaptchaVerifier = new firebase.auth.RecaptchaVerifier(
"sign-in-button",
{
size: "invisible",
}
);
firebase
.signInWithPhoneNumber(`+${this.state.phone}`, recaptchaVerifier)
.then((confirmationResult) => {
this.setState({ confirmationResult });
})
.catch((error) => {
// Error; SMS not sent
// Handle Errors Here
window.alert(`${error.code}, ${error.message}`);
recaptchaVerifier.reset(); // How can I do that?
});
};
I've been struggling with this problem for several days, maybe my answer will help someone.
export const requestRecaptchVerifier = () => {
window.recaptchaVerifier = new RecaptchaVerifier(
"recapcha-container",
{
size: "invisible",
},
auth
);
};
I then call signInWithPhone from another function and handle the error like this:
await signInWithPhone(formik.values.phone)
.then(() => {
// ... my code
})
.catch(() => {
window.recaptchaVerifier.recaptcha.reset();
window.recaptchaVerifier.clear();
});
All the difference in
window.recaptchaVerifier.recaptcha.reset()
And
window.recaptchaVerifier.clear()
I'm no expert but from the documentation and by talking with you in the comment section I think you need to pass a callback. Like this:
const recaptchaVerifier = new firebase.auth.RecaptchaVerifier('sign-in-button', {
'size': 'invisible',
'callback': function(response) {
// reCAPTCHA solved, allow signInWithPhoneNumber.
firebase
.signInWithPhoneNumber(`+${this.state.phone}`, recaptchaVerifier)
.then((confirmationResult) => {
this.setState({ confirmationResult });
})
.catch((error) => {
// Error; SMS not sent
// Handle Errors Here
window.alert(`${error.code}, ${error.message}`);
recaptchaVerifier.reset();
});
}
});
Reference: https://firebase.google.com/docs/auth/web/phone-auth#use-invisible-recaptcha
Hope this helps!

How to emit multiple actions in one epic using redux-observable?

I'm new to rxjs/redux observable and want to do two things:
1) improve this epic to be more idiomatic
2) dispatch two actions from a single epic
Most of the examples I see assume that the api library will throw an exception when a fetch fails, but i've designed mine to be a bit more predictable, and with Typescript union types I'm forced to check an ok: boolean value before I can unpack the results, so understanding how to do this in rxjs has been a bit more challenging.
What's the best way to improve the following? If the request is successful, I'd like to emit both a success action (meaning the user is authorized) and also a 'fetch account' action, which is a separate action because there may be times where I need to fetch the account outside of just 'logging in'. Any help would be greatly appreciated!
const authorizeEpic: Epic<ActionTypes, ActionTypes, RootState> = action$ =>
action$.pipe(
filter(isActionOf(actions.attemptLogin.request)),
switchMap(async val => {
if (!val.payload) {
try {
const token: Auth.Token = JSON.parse(
localStorage.getItem(LOCAL_STORAGE_KEY) || ""
);
if (!token) {
throw new Error();
}
return actions.attemptLogin.success({
token
});
} catch (e) {
return actions.attemptLogin.failure({
error: {
title: "Unable to decode JWT"
}
});
}
}
const resp = await Auth.passwordGrant(
{
email: val.payload.email,
password: val.payload.password,
totp_passcode: ""
},
{
url: "localhost:8088",
noVersion: true,
useHttp: true
}
);
if (resp.ok) {
return [
actions.attemptLogin.success({
token: resp.value
})
// EMIT action 2, etc...
];
}
return actions.attemptLogin.failure(resp);
})
);
The docs for switchMap indicate the project function (the lambda in your example) may return the following:
type ObservableInput<T> = SubscribableOrPromise<T> | ArrayLike<T> | Iterable<T>
When a Promise<T> is returned, the resolved value is simply emitted. In your example, if you return an array from your async scope, the array will be sent directly to the Redux store. Assuming you have no special Redux middlewares setup to handle an array of events, this is likely not what you want. Instead, I would recommend returning an observable in the project function. It's a slight modification to your example:
const authorizeEpic: Epic<ActionTypes, ActionTypes, RootState> = action$ =>
action$.pipe(
filter(isActionOf(actions.attemptLogin.request)), // or `ofType` from 'redux-observable'?
switchMap(action => {
if (action.payload) {
try {
const token: Auth.Token = JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY) || "")
if (!token) {
throw new Error()
}
// return an observable that emits a single action...
return of(actions.attemptLogin.success({
token
}))
} catch (e) {
// return an observable that emits a single action...
return of(actions.attemptLogin.failure({
error: {
title: "Unable to decode JWT"
}
}))
}
}
// return an observable that eventually emits one or more actions...
return from(Auth.passwordGrant(
{
email: val.payload.email,
password: val.payload.password,
totp_passcode: ""
},
{
url: "localhost:8088",
noVersion: true,
useHttp: true
}
)).pipe(
mergeMap(response => response.ok
? of(
actions.attemptLogin.success({ token: resp.value }),
// action 2, etc...
)
: of(actions.attemptLogin.failure(resp))
),
)
}),
)
I don't have your TypeScript type definitions, so I can't verify the example above works exactly. However, I've had quite good success with the more recent versions of TypeScript, RxJS, and redux-observable. Nothing stands out in the above that makes me think you should encounter any issues.
You could zip your actions and return them.
zip(actions.attemptLogin.success({
token: resp.value
})
// EMIT action 2, etc...
So that, now both your actions will be called.

simplify react function to get current user

I am writing a function that using an ajax call to hit an API endpoint to get current user, it could be anonymous user or a actual one. the initial variable is set to true if it's used in constructor, false if it's used elsewhere. Don't know if this is the right way to do, or if the code is clean, for example: Is using initial to switch setState and this.state assignment the right way to do in react?
loadCurrentUser(initial = false) {
$.ajax({
type: 'GET',
url: 'accounts/api/users/current',
}).done((responseData) => {
const data = responseData.data;
const isAuthenticated = data.is_authenticated;
let stateData;
if (isAuthenticated) {
stateData = {
displayName: data.display_name,
reputationPoint: data.reputation_point,
};
} else {
stateData = {
displayName: null,
reputationPoint: null,
};
}
if (initial) {
this.state = stateData;
} else {
this.setState(stateData);
}
});
}
This can be simplified to this:
state = {displayName: null, reputationPoint: null}
componentWillMount() {
this.loadCurrentUser()
}
loadCurrentUser() {
$.ajax({
type: 'GET',
url: '/accounts/api/users/current',
}).done(responseData => {
const {is_authenticated, display_name, reputation_point} = responseData.data;
if (is_authenticated) {
this.setState({
displayName: display_name,
reputationPoint: reputation_point,
});
}
});
}
Mainly it's always a good idea to set the initial value in constructor level, let it be empty/null values. Then you can call setState from anywhere as necessary. Otherwise as the async AJAX request might get delyed, anywhere you reference the this.state.displayName or anything, will generate error.

Resources