I've read many SO posts about using Accounts.onEmailVerificationLink and Accounts.verifyEmail, but I haven't yet found one that seems to explain how to call them from a React client.
I have React Router set up to route clicks on the email verification link, to a React component called ConfirmEmail. The router captures the validation token to props.match.params.emailValidationToken.
The ConfirmEmail component looks like this:
import React, {useEffect, useRef, useState} from "react";
function ConfirmEmail(props) {
const {client, match, history} = props;
const doSetUp = useRef(true);
const emailVerificationToken = useRef(props.match.params.emailValidationToken);
if (doSetUp.current){
doSetUp.current = false;
debugger;
Accounts.onEmailVerificationLink(function(token, done) {
Accounts.verifyEmail(emailVerificationToken.current, function(error) {
debugger;
if (error) {
console.log(error)
} else {
console.log(success)
}
})
});
}
return (
<p>Your email address has been verified.</p>
)
}
export default ConfirmEmail;
I've tried it a few different ways, but I haven't yet found an approach that works.
What is the correct way to handle a click on a Meteor email validation link from a React client?
Ok, I don't use React Hooks but I guess you will find easy to port it up to your flavor.
It all need to happen in componentDidMount(). This ensures that you avail of the token and that you only run this process once.
In the following code, toastr is a UI notification system for the client.
import React, { Component } from 'react'
import { connect } from 'react-redux' // if you use redux, here it is used to get the user slug from props.
import { Link } from 'react-router-dom'
import PropTypes from 'prop-types' // not relevant
import { Accounts } from 'meteor/accounts-base'
import { toastr } from 'react-redux-toastr' // not relevant
import { TiWarningOutline, TiThumbsOk } from '../../components/shared/Icons/icons' // not relevant
class ConfirmEmail extends Component {
constructor (props) {
super(props)
this.state = {
linkExpired: false,
logged: true // I need to know if the user is logged in before I test the token
}
}
componentDidMount () {
const { match, history, slug } = this.props
const token = match.params.token
// If the user is not logged in I cannot test the token against an email.
if (!slug) {
this.setState({ logged: false })
return
}
Accounts.verifyEmail(token, err => {
if (err && err.reason === 'Verify email link expired') {
this.setState({ linkExpired: true })
}
// Inform the user what went wrong
if (err) {
toastr.light('Could not verify email!', `Error: ${err.reason}`, { timeOut: 2000, icon: (<TiWarningOutline style={{ fontSize: 42, color: 'red' }} />) })
} else {
// Confirm to user and move on to where you want to direct the user first or ask the user to close the window...or else
toastr.light('You\'ve Successfully Confirmed Your Email Address!', { timeOut: 4000, icon: (<TiThumbsOk style={{ fontSize: 42, color: 'rgb(74, 181, 137)' }} />) })
history.push('/feeds')
}
})
}
render () {
const { linkExpired, logged } = this.state
return (
<div style={{ textAlign: 'center', paddingTop: 80 }}>
{logged
? <>
{linkExpired
? <p>This link has expired.</p>
: <>
<img src={`${IMAGE_BANK}/images/6.svg`} style={{ width: 36 }} /> // this is a spinner
<p>Awaiting confirmation ...</p>
</>}
</>
: <>
<p style={{ maxWidth: 360, margin: '0 auto' }}>
In order to verify your email address you need to be authenticated. Please sign in and try the verification link from your email one more time.
</p>
<br />
<Link to='/signin' className='btn btn-primary'>Sign In</Link>
</>
}
</div>
)
}
}
// this block is relevant in Redux context. Just gets the slug (or some other way to check if the user is logged in.)
const mapStateToProps = state => {
const currentUser = state.user.currentUser
return {
slug: currentUser?.slug
}
}
export default connect(mapStateToProps, { })(ConfirmEmail)
// from here, not so relevant
ConfirmEmail.propTypes = {
params: PropTypes.object
}
Related
I'm trying to figure out how to set up a nextjs index.tsx page, that renders a page if the user is authenticated and another component if the user is not authenticated.
I can have the not authenticated component rendered properly, but I cannot have the authenticated page rendered correctly. I cant find a tutorial to explain how to put a page in the if statement so that the main nextjs index.tsx page renders the page I specify if there is an authenticated user.
I have an index.tsx in pages with:
import * as React from "react"
import { Box, Center, Spinner, VStack } from "#chakra-ui/react"
import Head from "next/head"
// import NextLink from "next/link"
import { useMe } from "lib/hooks/useMe"
import { DashLayout } from "components/DashLayout"
import { AuthedHomeLayout } from "components/AuthedHomeLayout"
import LandingPage from "components/landing/lp"
import { HomeLayout } from "components/HomeLayout"
export default function Home() {
const { me, loading } = useMe()
if (loading)
return (
<Center>
<Spinner />
</Center>
)
return (
<Box>
<Head>
<title>test</title>
</Head>
<Center flexDir="column" w="100%">
<VStack>
{me? <AuthedHomeLayout><DashLayout /></AuthedHomeLayout> : (
<HomeLayout><LandingPage /></HomeLayout>
)}
</VStack>
</Center>
</Box>
)
}
When I try this as an authenticated user, the DashLayout does load, but the links in it do not render.
The DashLayout has a set of links in it that form the pages of the dashboard:
import * as React from "react"
import { Box, Flex, Heading, Link, LinkProps, Stack, useColorModeValue } from "#chakra-ui/react"
import NextLink from "next/link"
import { useRouter } from "next/router"
const DashLayout: React.FC = ({ children }) => {
return (
<Box pt={10} pb={20} w="100%">
<Flex flexWrap={{ base: "wrap", md: "unset" }}>
<Box pos="relative">
<Stack
position="sticky"
top="100px"
minW={{ base: "unset", md: "200px" }}
mr={8}
flexDir={{ base: "row", md: "column" }}
mb={{ base: 8, md: 0 }}
spacing={{ base: 0, md: 4 }}
>
<ProfileLink href="/dash">Dashboard</ProfileLink>
<ProfileLink href="/dash/library">Library</ProfileLink>
<ProfileLink href="/dash/help">Help</ProfileLink>
</Stack>
</Box>
<Box w="100%">{children}</Box>
</Flex>
</Box>
)
}
export default DashLayout
interface ProfileLinkProps extends LinkProps {
href: string
}
const ProfileLink: React.FC<ProfileLinkProps> = ({ href, ...props }) => {
const { asPath } = useRouter()
const isActive = asPath === href
const activeColor = useColorModeValue("black", "white")
const inactiveColor = useColorModeValue("gray.600", "gray.500")
return (
<NextLink href={href} passHref>
<Link
pr={4}
h="25px"
justifyContent={{ base: "center", md: "flex-start" }}
textDecoration="none !important"
color={isActive ? activeColor : inactiveColor}
_hover={{ color: useColorModeValue("black", "white") }}
fontWeight={isActive ? "semibold" : "normal"}
>
{props.children}
</Link>
</NextLink>
)
}
The page I want to render if there is an auth user, is:
import * as React from "react"
import { gql } from "#apollo/client"
import { Center, Spinner, Stack, Text } from "#chakra-ui/react"
import { useUpdateMeMutation } from "lib/graphql"
import { useForm } from "lib/hooks/useForm"
import { useMe } from "lib/hooks/useMe"
import { useMutationHandler } from "lib/hooks/useMutationHandler"
import { UPLOAD_PATHS } from "lib/uploadPaths"
import Yup from "lib/yup"
import { ButtonGroup } from "components/ButtonGroup"
import { Form } from "components/Form"
import { withAuth } from "components/hoc/withAuth"
import { AuthedHomeLayout } from "components/AuthedHomeLayout"
import { ImageUploader } from "components/ImageUploader"
import { Input } from "components/Input"
import { DashLayout } from "components/DashLayout"
const _ = gql`
mutation UpdateMe($data: UpdateUserInput!) {
updateMe(data: $data) {
...Me
}
}
`
const ProfileSchema = Yup.object().shape({
email: Yup.string().email().required("Required").nullIfEmpty(),
firstName: Yup.string().required("Required").nullIfEmpty(),
lastName: Yup.string().required("Required").nullIfEmpty(),
})
function Dash() {
const { me, loading } = useMe()
const handler = useMutationHandler()
const [updateUser] = useUpdateMeMutation()
const updateAvatar = (avatar: string | null) => {
return handler(() => updateUser({ variables: { data: { avatar } } }), {
onSuccess: (_, toast) => toast({ description: "Avatar updated." }),
})
}
const defaultValues = {
email: me?.email || "",
firstName: me?.firstName || "",
lastName: me?.lastName || "",
}
const form = useForm({ defaultValues, schema: ProfileSchema })
const handleUpdate = (data: typeof defaultValues) => {
return form.handler(() => updateUser({ variables: { data } }), {
onSuccess: (_, toast) => {
toast({ description: "Info updated!" })
form.reset(data)
},
})
}
if (loading)
return (
<Center>
<Spinner />
</Center>
)
if (!me) return null
return (
<Stack spacing={6}>
<Tile>
<Text>alskjf</Text>
</Tile>
</Stack>
)
}
Dash.getLayout = (page: React.ReactNode) => (
<AuthedHomeLayout>
<DashLayout>{page}</DashLayout>
</AuthedHomeLayout>
)
export default withAuth(Dash)
I also tried defining the index.tsx condition as:
{me?
<Dash /> // Dash is defined as a page in the pages folder at dash/index
///<AuthedHomeLayout><DashLayout /></AuthedHomeLayout>
: (
<HomeLayout><LandingPage /></HomeLayout>
)}
How can I have index.tsx defined to render one page if there is an authed user and another if there is not?
I saw this post and tried using one of the suggestions it makes, as follows:
import Router from 'next/router';
{me? Router.push('/dash') : (
<HomeLayout><LandingPage /></HomeLayout>
)}
When I try this, I get errors that read:
[{
"resource": "/src/pages/index.tsx",
"owner": "typescript",
"code": "2322",
"severity": 8,
"message": "Type 'Element | Promise<boolean>' is not assignable to type 'ReactNode'.\n Type 'Promise<boolean>' is not assignable to type 'ReactNode'.",
"source": "ts",
"startLineNumber": 32,
"startColumn": 13,
"endLineNumber": 34,
"endColumn": 15,
"relatedInformation": [
{
"startLineNumber": 1360,
"startColumn": 9,
"endLineNumber": 1360,
"endColumn": 17,
"message": "The expected type comes from property 'children' which is declared here on type 'IntrinsicAttributes & OmitCommonProps<DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>, keyof StackProps> & StackProps & { ...; }'",
"resource": "repo/node_modules/#types/react/index.d.ts"
}
]
}]
In the solutions you tried, the last one was almost correct.
You were on the right path, that you should redirect the user to the /dash page if he is authenticated. But you were doing the redirection in the return statement of your component, which is not where you want to do any side effect logic.
Your attempt:
import Router from 'next/router';
{me? Router.push('/dash') : (
<HomeLayout><LandingPage /></HomeLayout>
)}
will not work because Router.push returns a <Promise<boolean>>.
Don't forget that React components must return React elements. In your case when the user is authenticated, you are returning a promise not a React element.
So your redirection (which is a side effect) should be done inside a useEffect hook.
In order to fix this, Next documentation provides a clear example of how to do it correctly. What you are looking for is the last code block of this section (the one just before this section).
Don't forget to use a valid router instance via the useRouter hook provided by next/router.
So your code now becomes something like:
import { useEffect } from 'react';
import { useRouter } from 'next/router';
// Whatever Component you were doing the redirect
const export YourComponent = () => {
// your component hooks and states
const { me, loading } = useMe();
const router = useRouter();
// Here is what you were missing
useEffect(() => {
if (me) {
router.push('/dash');
}
}, [me]);
// you can add a loader like you did before
return loading ? (
<Center><Spinner /></Center>
) : (
<HomeLayout><LandingPage /></HomeLayout>
);
};
It should be enough to get to what you're looking for.
As a side note, your first solution:
{me?
<Dash /> // Dash is defined as a page in the pages folder at dash/index
///<AuthedHomeLayout><DashLayout /></AuthedHomeLayout>
: (
<HomeLayout><LandingPage /></HomeLayout>
)}
cannot work, as <Dash /> is a Next Page which is associated with a route based on its file name. You can look at it like an entry point.
So I'm forking off a sample Twilio video chat app (https://github.com/twilio/twilio-video-app-react). For the chat feature, Snackbar messages are employed. Which works fine. But I want to allow the user to send a message starting with http, so that the message will then be sent as a hyperlink URL.
The ChatInput component works fine for displaying this message as a hyperlink URL for the local user (i.e. - the sender). But the DataTrack event handler for the remote users doesn't display the message as a hyperlink. Just displays the literal text.
Here is the ChatInput.tsx, where any message starting with http will show up correctly for the local user.
import React, { useState } from 'react';
import { Button, FormControl, TextField } from '#material-ui/core';
import { useSnackbar } from 'notistack';
import useVideoContext from '../../../hooks/useVideoContext/useVideoContext';
export default function ChatInput() {
const [message, setMessage] = useState('');
const { room } = useVideoContext();
const { enqueueSnackbar } = useSnackbar();
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => setMessage(e.target.value);
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
if (message) {
// Get the LocalDataTrack that we published to the room.
const [localDataTrackPublication] = [...room.localParticipant.dataTracks.values()];
// Construct a message to send
const fullMessage = `${room.localParticipant.identity} says: ${message}`;
if (message.startsWith('http')) {
// Send the message
localDataTrackPublication.track.send(`${message}`);
// Render the message locally so the local participant can see that their message was sent.
enqueueSnackbar(<a href={message}>{message}</a>);
} else {
// Send the full message
localDataTrackPublication.track.send(fullMessage);
// Render the full message locally so the local participant can see that their message was sent.
enqueueSnackbar(fullMessage);
}
//Reset the text field
setMessage('');
}
};
return (
<form autoComplete="off" style={{ display: 'flex', alignItems: 'center' }} onSubmit={handleSubmit}>
<FormControl>
<label htmlFor="chat-snack-input" style={{ color: 'black' }}>
Say something:
</label>
<TextField value={message} autoFocus={true} onChange={handleChange} id="chat-snack-input" size="small" />
</FormControl>
<Button type="submit" color="primary" variant="contained" style={{ marginLeft: '0.8em' }}>
Send
</Button>
</form>
);
}
And here is the DataTrack.ts, which only displays the string literal for any remote user.
import { useEffect } from 'react';
import { DataTrack as IDataTrack } from 'twilio-video';
import { useSnackbar } from 'notistack';
var stringToHTML = function (str) {
var parser = new DOMParser();
var doc = parser.parseFromString(str, 'text/html');
return doc.body;
};
export default function DataTrack({ track }: { track: IDataTrack }) {
const { enqueueSnackbar } = useSnackbar();
useEffect(() => {
const handleMessage = (message: string) => {
if (message.startsWith('http')) {
const newMessage = stringToHTML(message);
enqueueSnackbar(newMessage);
}
else {
enqueueSnackbar(message); }
};
track.on('message', handleMessage);
return () => {
track.off('message', handleMessage);
};
}, [track, enqueueSnackbar]);
return null; // This component does not return any HTML, so we will return 'null' instead.
}
Any suggestions as to how I can get the remote users to receive the same hyperlink URL that the sender sees?
TL;DR: the function stringToHTML is returnign a DOM element reference not a React element when passing the message to NotiStack, try wrapping it with a React element:
//const newMessage = stringToHTML(message);
enqueueSnackbar(<div dangerouslySetInnerHTML={{__html:message}} />);
NL;PR: And/Or I'm not sure why your are passing the message value to NotiStack differently in the two components:
if (message.startsWith('http')) {
//local user
enqueueSnackbar(<a href={message}>{message}</a>); //1)
//vs. remote user
const newMessage = stringToHTML(message);
enqueueSnackbar(newMessage); // should it be the same as 1)?
Appreciate the feedback. I was handling the message two different ways depending if a URL was being passed along or not. Actually the project author actually provided a clean solution. Installing the Linkify package allows just those string elements inferred to be HTML to be formatted as such.
Here is the reworked DataTrack.tsx contents. Works like a champ!
import React from 'react';
import { useEffect } from 'react';
import { DataTrack as IDataTrack } from 'twilio-video';
import { useSnackbar } from 'notistack';
import Linkify from 'react-linkify';
export default function DataTrack({ track }: { track: IDataTrack }) {
const { enqueueSnackbar } = useSnackbar();
useEffect(() => {
const handleMessage = (message: string) =>
enqueueSnackbar(
<Linkify
componentDecorator={(decoratedHref, decoratedText, key) => (
<a target="_blank" rel="noopener" href={decoratedHref} key={key}>
{decoratedText}
</a>
)}
>
{message}
</Linkify>
);
track.on('message', handleMessage);
return () => {
track.off('message', handleMessage);
};
}, [track, enqueueSnackbar]);
return null; // This component does not return any HTML, so we will return 'null' instead.
}
I'm trying to get a constant, resetLink (A JWT token), sent from a dynamic URL (/api/users/resetpassword/:resetLink) after a user resets their password.
on componendDidMount, I call "this.props.startPassReset(resetlink)", where it seems resetLink is lost, and I can't track it down through any params/props/state.
PassResetForm.js
import React, { Component } from 'react';
import {
Button,
Row,
Col,
Modal,
ModalHeader,
ModalBody,
Form,
FormGroup,
Label,
Input,
NavLink,
Alert,
Container,
} from "reactstrap";
import { connect } from 'react-redux'
import PropTypes from "prop-types";
import { register, passReset, startPassReset } from "../../actions/authActions";
import { clearErrors } from "../../actions/errorActions";
class PassResetForm extends Component {
state = {
modal: false,
name: "",
email: "",
password: "",
msg: null,
resetLink: "test",
};
static propTypes = {
isAuthenticated: PropTypes.bool,
error: PropTypes.object.isRequired,
resetLink: PropTypes.string.isRequired,
clearErrors: PropTypes.func.isRequired,
};
componentDidMount() {
// Idea here is to get the token, and set it to resetLink, and pass that to the Action when the component mounts
const { resetLink } = this.props.match.params;
const { error, isAuthenticated } = this.props;
if (!resetLink) {
console.log("!resetLink");
this.setState({ msg: "Token error" });
} else {
console.log("resetLink!");
this.setState({
resetLink: resetLink
}, (resetLink) => {
console.log("updated state:", resetLink)
console.log("resetLink state within: " + this.state.resetLink);
this.props.startPassReset(resetLink);
});
console.log("resetLink is: " + resetLink);
}
}
componentDidUpdate(prevProps) {
const { error, isAuthenticated, auth } = this.props;
if (auth !== prevProps.auth) {
if (auth.msg !== null) {
this.setState({ msg: auth.msg })
} else {
this.setState({ msg: null });
}
}
if (error !== prevProps.error) {
// Check for register error
if (error.id === "RESET_FAIL") {
this.setState({ msg: error.msg.msg });
} else {
this.setState({ msg: null });
}
}
}
toggle = () => {
console.log();
// startPassReset();
this.setState({
modal: !this.state.modal,
});
};
onChange = (e) => {
this.setState({ [e.target.name]: e.target.value });
};
onSubmit = (e) => {
e.preventDefault();
const { newPass, newPassMatch } = this.state;
if (newPass !== newPassMatch) {
this.setState({ msg: "Passwords don't match." })
} else {
console.log("newPass and newPassMatch are " + newPass + newPassMatch)
this.props.passReset(newPass);
}
};
// DONE Make an api call to your backend that creates a password reset token. Store it in the database and, in one form or another, associate it with the user (usually it's the same database entry).
// DONE Send an email to the user with a link that has the password reset token embedded into it. Have a route in your react-router routes that will handle the url you link to.
// Have the route mount a component that has a componentDidMount, which takes the token and makes an api to the backend to validate the token.
// Once validated, open a ui element in the react component that allows the user to set a new password
// Take the new password, password confirmation, and reset token and make an api call to the backend to change the password. You need the token a second time because you have to validate the token one last time during the actual password change process, to make sure no one is attempting anything malicious. Hiding inputs behind state is not secure.
// DONE Delete the reset token in the backend after successful password change
render() {
const { isAuthenticated } = this.props;
return (
<div>
{isAuthenticated ? null : (
<Container>
<Row>
<Col sm="12" md={{ size: 6, offset: 3 }}>
{this.state.msg ? (
<Alert align="center" color="danger">{this.state.msg}</Alert>
) : (
<>
<Button onClick={this.toggle} color="dark" style={{ marginTop: "2rem" }} block>
Reset Password
</Button>
<Modal isOpen={this.state.modal} toggle={this.toggle}>
<ModalHeader>
<h2 align="center">Reset Password</h2>
{this.state.msg ? (
<Alert align="center" color="danger">{this.state.msg}</Alert>
) : null}
</ModalHeader>
<Row>
<Col sm="12" md={{ size: 6, offset: 3 }}>
<Form onSubmit={this.onSubmit}>
<FormGroup>
<Label for="email">Enter your new password</Label>
<Input
type="password"
name="newPass"
id="newPass"
placeholder="New Password"
onChange={this.onChange}
/>
<Input
type="password"
name="newPassMatch"
id="newPassMatch"
placeholder="Retype New Password"
onChange={this.onChange}
/>
<Button color="dark" style={{ marginTop: "2rem" }} block>
Submit New Password
</Button>
</FormGroup>
</Form>
</Col>
</Row>
</Modal>
</>
)}
</Col>
</Row>
</Container>
)
}
</div>
)
}
}
const mapStateToProps = (state) => ({
error: state.error,
auth: state.auth,
resetLink: state.resetLink,
})
export default connect(mapStateToProps, { passReset, clearErrors, startPassReset })(PassResetForm)
App.js
import React, { Component } from 'react';
import { BrowserRouter as Router, Link, Route } from 'react-router-dom';
import AppNavbar from './components/AppNavbar';
import { Provider } from 'react-redux';
import store from './store';
import { Container } from 'reactstrap';
import { loadUser } from './actions/authActions';
import 'bootstrap/dist/css/bootstrap.min.css';
import './App.css';
import PassResetForm from './components/auth/PassResetForm';
class App extends Component {
componentDidMount() {
store.dispatch(loadUser());
}
render() {
return (
<Router>
<Provider store={store}>
<div className="App">
<AppNavbar />
<Container>
<Route exact={true} path="/api/users/resetpassword/:resetLink" component={PassResetForm} />
</Container>
</div>
</Provider>
</Router>
);
}
}
export default App;
authActions.js (This code is incomplete, but has worked before in a different format when adding the token manually)
export const startPassReset = () => async dispatch => {
// const resetLink = "derp"; Adding tokens here manually to check the pathway, and it has worked before.
dispatch({
type: START_PASS_RESET
});
console.log("startPassReset Begins");
axios
.get('/api/users/resetpassword/' + resetLink, {})
.then(res =>
dispatch({
type: RESET_TOKEN_SUCCESS,
payload: res.data
})
)
.catch(err => {
dispatch(
returnErrors(err.response.data, err.response.status, 'RESET_FAIL')
);
dispatch({
type: RESET_FAIL
});
});
}
authReducer.js
case START_PASS_RESET:
return {
...state,
token: null,
user: null,
isAuthenticated: false,
isLoading: false,
}
Lastly, users.js (Backend, this works when sent through manually)
router.get('/resetpassword/:resetlink', (req, res) => {
console.log("req.params: " + JSON.stringify(req.params));
console.log("req.body " + JSON.stringify(req.body));
const resetLink = req.params.resetlink;
console.log("resetLink? " + resetLink);
// verify the reset link is genuine
jwt.verify(resetLink, process.env.JWT_SECRET, function (err, decodedData) {
if (err) {
return res.status(401).json({ msg: "Incorrect token or it is expired. " + err });
} else {
User.findOne({ resetLink }, (err, user) => {
if (err || !user) {
return res.status(400).json({ msg: "User with this token doesn't exist" });
} else {
// Success condition. Go ahead and allow the form to appear to reset the password
console.log("Hi there, you made it! " + resetLink)
return res.status(200).json({ msg: "I like what you got, good job!" })
}
})
}
})
});
I've really hit a wall with this, and appreciate any help.
Turns out my error was in the first line of AuthActions. Namely:
export const startPassReset = (resetLink) => async dispatch => {
Derp. My mistake. Will leave here in case others get something from it.
I'm integrating stripe payment in react-native and wants to open an iframe with specific url from after buttton click.
Scenario : user will enter the card details, details will be given to api end point and it will return an url that will contain the authentication part like OTP. So I want that url to be opened how to do so ? Let me if is there any better way to open that authentication middleware.
Adding code below
Payment.js
import React, { Component } from 'react';
import { View, Button } from 'react-native';
import stripe from 'tipsi-stripe';
import { doPayment } from '../api/api';
import { Auth } from './Auth';
stripe.setOptions({
publishableKey: 'pk_test_********',
});
export default class Payment extends Component {
state = {
isPaymentPending: false
}
requestPayment = () => {
this.setState({ isPaymentPending: true });
return stripe
.paymentRequestWithCardForm()
.then(stripeTokenInfo => {
return doPayment(10330, stripeTokenInfo.tokenId);
})
.then((res) => {
let url = "<iFrame src='" + res.intent_url + "' />"
console.log(res, url);
openAuthentication(url); --->>>> here i'm calling a function with url
})
.catch(error => {
console.warn('Payment failed', { error });
})
.finally(() => {
this.setState({ isPaymentPending: false });
});
};
render() {
return (
<View style={styles.container}>
<Button
title="Make a payment"
onPress={this.requestPayment}
disabled={this.state.isPaymentPending}
/>
</View>
);
}
}
openAuthentication = (url) => {
console.log("Here with props :::::::", url);
// here I want to open an iframe, i'm getting correct url, I've checked it in a static html page and it is working
<Auth url={url} />
}
const styles = {
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
};
Auth.js
import React, { Component } from 'react'
import {
View, StyleSheet
} from 'react-native'
import { WebView } from 'react-native-webview'
export default class Auth extends Component {
constructor(props) {
super(props)
console.log(props, ">>>>>>>>>>>")
}
render() {
console.log("In Auth -----------");
return (
<View style={styles.container}>
<WebView
source={{ uri: 'myUrl' }}
/>
</View>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
}
})
Error:
React.createElement type is invalid, expected a string or a class/function.
Auth needs to be returned by a render function, otherwise nothing will show up.
So, you'll want something similar to this:
render() {
return (
<View style={styles.container}>
<Button
title="Make a payment"
onPress={this.requestPayment}
disabled={this.state.isPaymentPending}
/>
{this.state.url && (
<Auth url={this.state.url} />
)}
</View>
);
}
You only want to render auth when you have the url.
For this, you want to update your state to look something like this:
state = {
isPaymentPending: false,
url: undefined
}
And
.then((res) => {
let url = "<iFrame src='" + res.intent_url + "' />";
this.setState({ url });
})
In order to update you state with the received url when the promise resolves. This will update your state, set the url, and re-render. Because you have an url, Auth should be rendered as well.
LE:
Your Auth.js should look something like this in order to be able to display static HTML. this.props.url should be valid HTML.
render() {
console.log("In Auth -----------");
return (
<View style={styles.container}>
<WebView
source={{ html: this.props.url }}
originWhitelist={['*']}
/>
</View>
);
}
I have already asked the question already [LINK], And as I did not get any answer I am re-asking, I hope it is not against the rule.
The issue is that when I trigger the request the result that I get is a page markup, However if I re-trigger then everything solves.
The issue appears to be only when I trigger the request as there are no problem when I do the requests at the loading of the page.
I am using react with redux, and redux-thunk as middleware.
This is an image of the response that I get
These are the code for the components:
Action
import { BEGIN_FETCH_MOVIES, FETCHED_MOVIES, FETCH_FAILED_MOVIES } from '../constants';
import axios from 'axios';
//fetch movie
const searchQuery = (url) => {
return dispatch => {
//dispatch begin fetching
dispatch({
type : BEGIN_FETCH_MOVIES,
})
//make a get request to get the movies
axios.get(url)
.then((res) => {
//dispatch data if fetched
dispatch({type : FETCHED_MOVIES, payload : res.data});
})
.catch((err) => {
//dispatch error if error
dispatch({type : FETCH_FAILED_MOVIES});
});
}
//return the result after the request
}
export default searchQuery;
Main component
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { actionSearchMovie, actionSearchSerie } from '../actions'
import DisplayItemMovie from '../components/DisplayItemMovie';
import DisplayItemSerie from '../components/DisplayItemSerie';
import DrPagination from "../components/DrPagination";
import { Layout, Divider, Icon, Spin, Row } from 'antd';
//Home component
class Home extends Component {
constructor(){
super();
this.state = {
moviePage : 1,
seriePage : 1,
urlMovie : '',
urlSerie : ''
}
}
//make request before the render method is invoked
componentWillMount(){
//url
const discoverUrlMovies = 'https://api.themoviedb.org/3/discover/movie?api_key=72049b7019c79f226fad8eec6e1ee889&language=en-US&sort_by=popularity.desc&include_adult=false&include_video=false&page=1';
//requests
this.fetchMovie(discoverUrlMovies);
}
fetchMovie = ( url ) => {
this.props.actionSearchMovie(url);
}
//handle pagination
handleChangePage = (page) =>{
let url = 'https://api.themoviedb.org/3/discover/movie?api_key=72049b7019c79f226fad8eec6e1ee889&language=en-US&sort_by=popularity.desc&include_adult=false&include_video=false&page=' + page;
this.setState({
moviePage : page,
urlMovie : url
}, ()=> this.state);
this.fetchMovie(this.state.urlMovie);
}
//render
render() {
const movies = this.props.movies.results; //movies
let displayMovies; //display movies
const antIcon = <Icon type="loading" style={{ fontSize: 24 }} spin />; //spinner
//if movies and series is undefined, display a spinner
if(movies.results === undefined){
displayMovies = <Spin indicator={antIcon} />
}else {
//map through movies and series and then display the items
displayMovies = movies.results.map((movie) => {
return <DisplayItemMovie key = {movie.id} movie = {movie} />
});
}
return (
<div>
<div className='header'>
Home
</div>
<Divider />
<Layout style = {{paddingBottom : '1rem', margin : '0 auto' }}>
<h1 className = 'title'>Movie</h1>
<Row type = 'flex' style = {{flexWrap : 'wrap'}}>
{displayMovies}
</Row>
<DrPagination total = { movies.total_results } page = { this.handleChangePage } currentPage = { this.state.moviePage } /> </div>
)
}
};
const mapStateToProps = (state) => {
return{
movies : state.search_movies,
}
}
export default connect(mapStateToProps, { actionSearchMovie })(Home);
I am not including the code for the reducer but if needed I will post it.