I'm implementing stripe payment to my website so I'm reading stripe payment docs. The Stripe payment document I'm following.
I even copied and pasted the code but I'm still having
Invalid Hooks Error.
I have two files one is checkoutPayment.js other one is stripeCheckoutForm.js
In stripeCheckoutForm.js
import React, { useState, useEffect } from "react";
import { CardElement, useStripe, useElements } from "#stripe/react-stripe-js";
export default function CheckoutForm() {
const [succeeded, setSucceeded] = useState(false);
const [error, setError] = useState(null);
const [processing, setProcessing] = useState("");
const [disabled, setDisabled] = useState(true);
const [clientSecret, setClientSecret] = useState("");
const stripe = useStripe();
const elements = useElements();
useEffect(() => {
window
.fetch("/create-payment-intent", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ items: [{ id: "xl-tshirt" }] }),
})
.then((res) => {
return res.json();
})
.then((data) => {
setClientSecret(data.clientSecret);
});
}, []);
const cardStyle = {
style: {
base: {
color: "#32325d",
fontFamily: "Arial, sans-serif",
fontSmoothing: "antialiased",
fontSize: "16px",
"::placeholder": {
color: "#32325d",
},
},
invalid: {
color: "#fa755a",
iconColor: "#fa755a",
},
},
};
const handleChange = async (event) => {
setDisabled(event.empty);
setError(event.error ? event.error.message : "");
};
const handleSubmit = async (ev) => {
ev.preventDefault();
setProcessing(true);
const payload = await stripe.confirmCardPayment(clientSecret, {
payment_method: {
card: elements.getElement(CardElement),
},
});
if (payload.error) {
setError(`Payment failed ${payload.error.message}`);
setProcessing(false);
} else {
setError(null);
setProcessing(false);
setSucceeded(true);
}
};
return (
<form id="payment-form" onSubmit={handleSubmit}>
<CardElement
id="card-element"
options={cardStyle}
onChange={handleChange}
/>
<button disabled={processing || disabled || succeeded} id="submit">
<span id="button-text">
{processing ? (
<div className="spinner" id="spinner"></div>
) : (
"Pay now"
)}
</span>
</button>
{error && (
<div className="card-error" role="alert">
{error}
</div>
)}
<p className={succeeded ? "result-message" : "result-message hidden"}>
Payment succeeded, see the result in your
<a href={`https://dashboard.stripe.com/test/payments`}>
Stripe dashboard.
</a>
Refresh the page to pay again.
</p>
</form>
);
}
In checkoutPayment.js
import React from "react";
import { loadStripe } from "#stripe/stripe-js";
import { Elements } from "#stripe/react-stripe-js";
import CheckoutForm from "./stripeCheckoutForm";
const promise = loadStripe("pk_test_6XtghloNmnIJt2Bov5bGRCAg00ozfbAMRE");
export default function App() {
return (
<div className="App">
<Elements stripe={promise}>
<CheckoutForm />
</Elements>
</div>
);
}
My error is:
Uncaught Error: Invalid hook call. Hooks can only be called inside of the body of a function component.
This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem
It's so funny. It's just because of I forgot to install npm packages correctly.
npm install #stripe/react-stripe-js
npm install #stripe/stripe-js
Installing the packages resolved the problem.
Related
I know, there is a lot of similar questions although I could not find a solution to my problem. It is the first time I am using Next.js and TypeScrypt.
I am simulating a login with REQRES storing the token in the localStorage as shown below:
import {
FormControl,
FormLabel,
Input,
Heading,
Flex,
Button,
useToast,
} from '#chakra-ui/react';
import { useRouter } from 'next/router';
import { useState } from 'react';
import LStorage from '../utils/localStorage/index';
const Login = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const handleEmail = (e: any) => setEmail(e.target.value);
const handlePassword = (e: any) => setPassword(e.target.value);
const router = useRouter();
const toast = useToast();
const success = () => toast({
title: 'Login Successfull',
description: 'You will be redirected now.',
status: 'success',
duration: 1200,
isClosable: true,
});
const failure = (error: string) => toast({
title: 'Login unsuccessfull',
description: error,
status: 'error',
duration: 3000,
isClosable: true,
});
const login = async () => {
const res = await fetch('/api', {
method: 'POST',
body: JSON.stringify({
email,
password,
}),
headers: {
'Content-type': 'application/json; charset=UTF-8',
},
});
const json = await res.json();
console.log(json);
if (json.error) {
failure(json.error);
setEmail('');
setPassword('');
} else {
LStorage.set('userToken', json.token);
LStorage.set('userInfo', email);
success();
setTimeout(() => {
router.push('/users');
}, 1500);
}
};
return (<div>
<Flex justifyContent="center">
<Heading my="5">Login</Heading>
</Flex>
<FormControl>
<FormLabel htmlFor="email">Email:</FormLabel>
<Input id="email" type="email" onChange={handleEmail} value={email}/>
<FormLabel htmlFor="password">Password:</FormLabel>
<Input id="password" type="password" onChange={handlePassword} value={password}/>
</FormControl>
<br />
<Button onClick={login}>Login</Button>
</div>);
};
export default Login;
which seem to work fine. Although when trying to get the userInfo from localStorage at the _app.tsx component I get the localStorage not defined, looking for the error I found out the solution below inside the useEffect.
import '../styles/globals.sass';
import { ChakraProvider } from '#chakra-ui/react';
import type { AppProps } from 'next/app';
import { useState, useEffect } from 'react';
import NavBar from '../components/NavBar';
import MainLayout from '../layouts/mainLayout';
import theme from '../styles/theme';
import LStorage from '../utils/localStorage/index';
function MyApp({ Component, pageProps }: AppProps) {
const [userInfo, setUserInfo] = useState<string | null>(null);
const logout = () => {
LStorage.remove('userToken');
LStorage.remove('userInfo');
setUserInfo(null);
};
useEffect(() => {
if (typeof window !== 'undefined') {
if (LStorage.get('userInfo')) {
setUserInfo(LStorage.get('userInfo'));
}
}
console.log('i am here');
}, []);
return (
<ChakraProvider theme={theme}>
<NavBar user={userInfo} logout={logout} />
<MainLayout>
<Component {...pageProps}/>
</MainLayout>
</ChakraProvider>
);
}
export default MyApp;
I understood that the first run will be on the server-side and that is why I got the error, nevertheless, using the useEffect should fix it. The thing is the useEffect does not even run unless I refresh the page... What am I missing??!??
The Login.js is a page inside page folder and the NavBar is a component inside components folder in the root.
import {
Flex, Spacer, Box, Heading, Button,
} from '#chakra-ui/react';
import Link from 'next/link';
import { FC } from 'react';
interface NavBarProps {
user: string | null;
logout: () => void;
}
const NavBar: FC<NavBarProps> = ({ user, logout }: NavBarProps) => (
<Flex bg="black" color="white" p="4">
<Box p="2">
<Heading size="md">
<Link href="/">My Sanjow App</Link>
</Heading>
</Box>
<Spacer />
{user && (
<Box pt="2" pr="4">
<Heading size="md">
<Link href="/users">Users</Link>
</Heading>
</Box>
)}
{user ? (
<Button
variant="ghost"
pr="4"
onClick={logout}
>
<Heading size="md">
<Link href="/">Logout</Link>
</Heading>
</Button>
) : (
<Box pt="2" pr="4">
<Heading size="md">
<Link href="/login">Login</Link>
</Heading>
</Box>
)}
</Flex>
);
export default NavBar;
The utils/localStorage/index
const lsType = {
set: 'setItem',
get: 'getItem',
remove: 'removeItem',
};
const ls = (type: string, itemName: string, itemData?: string): void | string => {
if (typeof window !== 'undefined') {
// eslint-disable-next-line no-undef
const LS = window.localStorage;
if (type === lsType.set && itemData) {
LS[type](itemName, itemData);
return;
}
return LS[type](itemName);
}
};
export default {
set(itemName: string, itemData: string): void {
ls(lsType.set, itemName, itemData);
},
get(itemName: string): string {
return ls(lsType.get, itemName) as string;
},
remove(itemName: string): void {
ls(lsType.remove, itemName);
},
};
You are running the effect only once by passing the [] empty array, pass the props that you expect to change instead of a blank array.
via the docs:
If you want to run an effect and clean it up only once (on mount and >unmount), you can pass an empty array ([]) as a second argument. This tells >React that your effect doesn’t depend on any values from props or state, so it never needs to re-run. This isn’t handled as a special case — it follows directly from how the dependencies array always works.
Generally speaking, managing userInfo only via localStorage is not a good idea, since you might want to re-render the application when user logs in or logs out, or any other change to the user data (i.e. change the username), and React is not subscribed to changes done to localStorage.
Instead, React has an instrument for runtime data management like that, it's called React Context. That context (let's call it UserContext) could be initializing from localStorage, so that the case when you refresh the page for example. But after that initial bootstrapping all state management should go thru the context. Just don't forget to update both context and localStorage every time you login/logout.
I hope this is just enough to give you the right direction.
I have a custom hook(useData) that takes query as an argument and then returns data and runtime(time to fetch the data from the API). But I need access to the runtime to my Editor component when I click on the run button. Right now what is happening is when I click on run button(inside Editor.js), it sets the query to the App component using the setter function and then it passes that query to the Table component and then calls the custom hook using that query and then table make use of that data. but I want the runtime in the Editor component, not in the Table component. I know I can call useData hook in the Editor component but my editor component gets rerender every time when we write on the editor, so It calls the useData() hook on each change.
If I create a context using this hook then I can able to access the runtime and data wherever I want.
Anyone, please help me how to convert that to context!
App.js code
import React, { useState } from "react";
import "./assets/output.css";
import Footer from "./components/layouts/Footer";
import Navbar from "./components/layouts/Navbar";
import Sidebar from "./components/layouts/Sidebar";
import TableSection from "./components/table/TableSection";
import Editor from "./components/editor/Editor";
const App = () => {
const [query, setQuery] = useState("");
const [value, setValue] = useState("select * from customers");
return (
<>
<div className="grid grid-cols-layout-desktop grid-rows-layout-desktop bg-gray-600 h-screen">
<Navbar />
<Sidebar setQuery={setQuery} setValue={setValue} />
<Editor setQuery={setQuery} value={value} setValue={setValue} />
{query ? <TableSection query={query} /> : null}
<Footer />
</div>
</>
);
};
export default App;
Editor.js
import React from "react";
import AceEditor from "react-ace";
import "ace-builds/src-min-noconflict/ext-language_tools";
import "ace-builds/src-min-noconflict/mode-mysql";
import "ace-builds/src-noconflict/theme-github";
import useData from "../../hooks/useData";
const Editor = ({ setQuery, value, setValue }) => {
const { runtime } = useData();
const onChange = (newValue) => {
setValue(newValue);
};
const onSubmit = () => {
var Z = value.toLowerCase().slice(value.indexOf("from") + "from".length);
setQuery(Z.split(" ")[1]);
};
return (
<div className="col-start-2 col-end-3 row-start-2 row-end-3 m-6">
<AceEditor
aria-label="query editor input"
mode="mysql"
theme="github"
name={Math.floor(Math.random() * 100000).toString()}
fontSize={16}
minLines={15}
maxLines={10}
width="100%"
showPrintMargin={false}
showGutter
placeholder="Write your Query here..."
editorProps={{ $blockScrolling: true }}
setOptions={{
enableBasicAutocompletion: true,
enableLiveAutocompletion: true,
enableSnippets: true,
}}
value={value}
onChange={onChange}
showLineNumbers
/>
<div className="">
<button
className="bg-white text-gray-800 rounded-md font-semibold px-4 py-2 my-4"
onClick={onSubmit}
>
<i className="fas fa-play"></i> Run SQL
</button>
</div>
</div>
);
};
export default Editor;
Hook code:
import { useEffect, useState } from "react";
import alasql from "alasql";
import toast from "react-hot-toast";
import TABLE_NAMES from "../utils/tableNames";
const getURL = (name) =>
`https://raw.githubusercontent.com/graphql-compose/graphql-compose-examples/master/examples/northwind/data/csv/${name}.csv`;
const useData = (tableName) => {
const [data, setData] = useState([]);
const [error, setError] = useState(false);
const [runtime, setRuntime] = useState("");
const convertToJson = (data) => {
alasql
.promise("SELECT * FROM CSV(?, {headers: false, separator:','})", [data])
.then((data) => {
setData(data);
toast.success("Query run successfully");
})
.catch((e) => {
toast.error(e.message);
});
};
const fetchData = (tableName) => {
setData([]);
const name = TABLE_NAMES.find((name) => name === tableName);
if (name) {
setError(false);
fetch(getURL(tableName))
.then((res) => res.text())
.then((data) => convertToJson(data));
} else {
setError(true);
toast.error("Please enter a valid query");
}
};
useEffect(() => {
let t0 = performance.now(); //start time
fetchData(tableName);
let t1 = performance.now(); //end time
setRuntime(t1 - t0);
console.log(
"Time taken to execute add function:" + (t1 - t0) + " milliseconds"
);
}, [tableName]);
return { data, runtime, error };
};
export default useData;
If you want to create a context and use it wherever you want, you can create a context, and add the state in this component and pass it to the value prop in the Provider component.
See the sample code.
import React, { createContext, useState } from "react";
export const UserContext = createContext({});
export interface User {
uid: string;
email: string;
}
export const UserProvider = ({ children }: any) => {
const [user, setUser] = useState<User>();
// you can defined more hooks at here
return (
// Pass the data to the value prop for sharing data
<UserContext.Provider value={{ user, setUser }}>
{children}
</UserContext.Provider>
);
};
Then wrap components with the provider function like this
<UserProvider>
<MyComponment1>
</MyComponment1>
<MyComponment2>
</MyComponment2>
<MyComponment3>
</MyComponment3>
</UserProvider>
At This time, Whatever Component in the UserProvider can access the context right now and you can use useContext hook to access the data that you pass in the value props
export const MyComponment1 = () => {
const { user, setUser } = useContext<any>(UserContext);
...
}
I am using useEffect to hit an api and display some data from the response.It works well in console but when i try to display the data in a component it throws an error.I am checking for the loading state though.I am showing the data after a i get a response then where does this null coming from
App.js file:
import { useState, useEffect } from 'react';
import Details from './components/Details/Details';
import Header from './components/Header/Header';
import GlobalStyle from './globalStyles';
const API_KEY = 'Private';
// const URL = `https://geo.ipify.org/api/v1?apiKey=${API_KEY}&ipAddress=${ip}`;
function App() {
const [ip, setIp] = useState('8.8.8.8');
const [response, setResponse] = useState(null);
const [error, setError] = useState(null);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
const fetchData = async () => {
setIsLoading(true);
try {
const res = await fetch(
`https://geo.ipify.org/api/v1?apiKey=${API_KEY}&ipAddress=${ip}`
);
const json = await res.json();
setResponse(json);
setIsLoading(false);
} catch (error) {
setError(error);
}
};
fetchData();
// return { response, error, isLoading };
}, [ip]);
return (
<>
<GlobalStyle />
<Header getIp={(q) => setIp(q)} />
<Details isLoading={isLoading} res={response} error={error} />
</>
);
}
export default App;
Header.js file:
import { useState } from 'react';
import { FaArrowRight } from 'react-icons/fa';
import React from 'react';
import { Form, FormInput, Head, HeadLine, Button } from './Header.elements';
// import { useFetch } from '../../useFetch';
const Header = ({ getIp }) => {
const [input, setInput] = useState('');
const onChange = (q) => {
setInput(q);
getIp(q);
};
return (
<>
{/* styled components */}
<Head>
<HeadLine>IP Address Tracker</HeadLine>
<Form
onSubmit={(e) => {
e.preventDefault();
onChange(input);
setInput('');
}}
>
<FormInput
value={input}
onChange={(e) => {
setInput(e.target.value);
}}
placeholder='Search for any IP address or Domain'
/>
<Button type='submit'>
<FaArrowRight />
</Button>
</Form>
</Head>
</>
);
};
export default Header;
Details.js file:
import React from 'react';
import { Box, Location } from './Details.elements';
const Details = ({ res, error, isLoading }) => {
console.log(res);
return isLoading ? (
<div>loading...</div>
) : (
<>
<Box>
<Location>{res.location.city}</Location>
</Box>
</>
);
};
export default Details;
the error it shows:
That happens because on the first render, Details component will receive isLoading=false and res=null, so it will try to render the box so it's throwing the error.
You can initialize isLoading as true.
const [isLoading, setIsLoading] = useState(true);
Or render the Location if res has some value.
<Box>
{res && <Location>{res.location.city}</Location>}
</Box>
According to React documentation :
https://reactjs.org/docs/hooks-reference.html
By default, effects run after every completed render, but you can
choose to fire them only when certain values have changed.
So your component is rendering at least once with isLoading as false before even the API call starts.
You have two choices here:
Set isLoading initial value to true
Add optional chaining res?.location.city
https://codesandbox.io/s/stackoverflow-67755606-uuhqk
Hi i am writing a basic messaging app with react and firebase everything is working except for passing in the firebase data as props to display on the screen. Right now I am getting this error:react-dom.development.js:11340 Uncaught Error: Objects are not valid as a React child (found: object with keys {id, message, timestamp, user}). If you meant to render a collection of children, use an array instead
Here is the code for my App.js:
import "./App.css";
import { FormControl, InputLabel, Input, Button } from "#material-ui/core";
import SendIcon from "#material-ui/icons/Send";
import { useEffect, useState } from "react";
import { db } from "./firebase";
// import firebase from "firebase";
import Message from "./Message";
import firebase from "#firebase/app";
function App() {
const [userName, setUserName] = useState("");
const [input, setInput] = useState("");
const [messages, setMessages] = useState([]);
console.log(messages);
console.log(db);
const getMessages = () => {
db.collection("messages").onSnapshot(function (querySnapshot) {
setMessages(
querySnapshot.docs.map((doc) => ({
id: doc.id,
message: doc.data().message,
timestamp: doc.data().timestamp,
user: doc.data().user,
}))
);
});
};
console.log(messages);
const sendMessage = (e) => {
e.preventDefault();
db.collection("messages").add({
message: input,
timestamp: firebase.firestore.FieldValue.serverTimestamp(),
user: userName,
});
setInput("");
};
useEffect(() => {
setUserName(prompt("Enter Your Name"));
getMessages();
}, []);
return (
<div className="App">
<h1>Welcome {userName}!</h1>
<div className="form-div">
<form className="form">
<FormControl className="form-input">
<InputLabel>Enter a Message</InputLabel>
<Input
value={input}
onChange={(e) => {
setInput(e.target.value);
}}
/>
</FormControl>
<div className="button-div">
<Button
style={{ backgroundColor: "rgb(0, 110, 255)" }}
className="send-button"
variant="contained"
color="primary"
type="submit"
onClick={sendMessage}
disabled={!input}
>
<SendIcon></SendIcon>
</Button>
</div>
</form>
</div>
{messages.map((message, id) => (
<>
<Message key={id} message={message}></Message>
</>
))}
</div>
);
}
export default App;
and here is the code for my Message component:
import React from "react";
const Message = (message) => {
return (
<div>
<p>{message.message}</p>
</div>
);
};
export default Message;.
Help would be much appreciated as i am beginner i have tried other soultions like downgrading firebase but i still get this error. i have also tried passing the props as individuls e.g message=message.message but i still get same error
There is confusion in your code as you are using message keyword everywhere, I would suggest you to use proper variable names.
Seems your messages structure looks like
[{id: 1, message: 'hi', user: 'asd', timestamp: ''}, {id: 2, message: 'hi1', user: 'asd1', timestamp: ''}]
Just change your Message component like below
const Message = ({message: msgInst}) => {
return (
<div>
<p>{msgInst.message}</p>
<p>{msgInst.user}</p>
</div>
);
};
I am trying to pass a filter that shows notifications for the user but the data keeps coming back blank when I console log it. I think the issue may be a mistake in how I'm making the comparison but I'm not sure. I had the same issue with how the timestamp was called from the database but that is fixed. I'm wondering if this could be the same issue
The issue is on line 33.
Here is my code:
import React, { useEffect, useState } from "react";
import { makeStyles } from "#material-ui/core/styles";
import Popper from "#material-ui/core/Popper";
import NotificationsIcon from "#material-ui/icons/Notifications";
import "../Style/Header.css";
import db, { auth } from "../firebase";
const useStyles = makeStyles((theme) => ({
paper: {
border: "1px solid",
padding: theme.spacing(1),
backgroundColor: theme.palette.background.paper,
zIndex: "10",
},
}));
export default function SimplePopper() {
const classes = useStyles();
const [anchorEl, setAnchorEl] = useState(null);
const [notifications, setNotifications] = useState([]);
const handleClick = (event) => {
setAnchorEl(anchorEl ? null : event.currentTarget);
};
const open = Boolean(anchorEl);
const id = open ? "simple-popper" : undefined;
useEffect(() => {
let mounted = true;
let unsub = db
.collection("notifications")
.where("user.askerUserId", "==", auth.currentUser.uid)
.orderBy("timestamp", "desc")
.onSnapshot((snapshot) => {
if (mounted) {
setNotifications(
snapshot.docs.map((doc) => ({
id: doc.id,
content: doc.data().content,
}))
);
}
});
return () => {
unsub();
};
}, []);
console.log(notifications);
return (
<div className="header__icon">
<NotificationsIcon
aria-describedby={id}
type="button"
onClick={handleClick}
/>
<Popper id={id} open={open} anchorEl={anchorEl} style={{ zIndex: 100 }}>
<div className={classes.paper}>
<ul className="notifications">
{notifications.map((notification) => (
<li key={notification.id}>{notification.content}</li>
))}
</ul>
</div>
</Popper>
</div>
);
}
The issue was that because I was making making a comparison with the filter I had to create a composite index in the firestore settings. If you're getting this issue you should see an error like the following appear:
FirebaseError: The query requires an index. That index is currently
building and cannot be used yet.
It should have a link for you to click that will take you to your firebase console and allow for it to automatically create an index. Once I did this my snapshot worked.