How to create reusable chakra ui toast - reactjs

I'm using chakra ui with my next js app.
On some event, I want to show a notification using chakra ui toast, after performing some action.
(For ex. on clicking sign in, I'll send request to backend, and will show success or error toast depending on the result)
And as this toast can only be invoked after click and not programatically, I've created a function to do so
import { useToast } from '#chakra-ui/react';
...
export default function SignInPage() {
const toast = useToast();
const resultToast = (status, title) => {
return toast({
position: "top",
title: title,
status: status,
duration: 3000,
isClosable: true,
});
const handleSubmit = (e) => {
// talking to backend / database
if(success) {
resultToast("success", "sign in successful");
}
else {
resultToast("error", "sign in failed");
}
}
// sign in form
};
}
This is working totally fine, but the thing is I want to use this on multiple pages, and want to make it reusable, but the issue is:
I can't make it a jsx component, as it returns only toast element and gets invoked only on click.
And I can't make it a normal function in separate file, as it uses useToast hook from chakra ui which can't be used in a function (Or maybe I'm wrong).
And also not able to export the resultToast function from one file, it shows "Modifiers can't appear here"
As I'm using chakra ui, so didn't want to install any other toast library. So is there any way to make this reusable or I'll have to use any external library or copy paste the function on all pages :D

I don't know if you ever figured this out, but I ran into the same problem today, and here's how I resolved it with Chakra-UI. Create a separate function and write the logic like this...
import { useToast } from "#chakra-ui/react";
export const CustomToast = () => {
const toast = useToast();
// types are: "success", "info", "warning", "error"
const addToast = (newRes) => {
toast({
description:newRes.message,
status: newRes.type,
position:"top-right",
isClosable: true,
duration: 5000,
variant: 'left-accent'
})
}
return { addToast };
}
Then import the addToast method anywhere in your application import { addToast } from "../path/to/file";, create an instance const { addToast } = CustomToast(); and use it by passing a destructured object to the addToast function addToast({message: "sign in successful", type: "success"})... I hope this helps someone else out here.

You can use react-universal-flash library to flash notification across nextjs pages .
Just add Flasher to _app.js
import { ChakraProvider} from "#chakra-ui/react";
import { Flasher } from "react-universal-flash";
import Message from "./Message";
function MyApp({ Component, pageProps }) {
return (
<ChakraProvider>
<Flasher>
<Message />
</Flasher>
<Component {...pageProps} />
</ChakraProvider>
);
}
Codesandbox sample of chakra-ui along with react-universal-flash is below
react-universal-flash + chakra-ui

here is my solution: I did make an object and used it:
const defaultToastProps = {
position: "top-right" as ToastPosition,
duration: 2000,
isClosable: true,
};
const submitHandler = () => {
Axios.post("/user/register", formData)
.then((res) => {
console.log(res);
toast({
title: "Account created.",
description: "We've created your account for you.",
status: "success",
...defaultToastProps,
});
nav("/login");
})
.catch((err) => {
if (err.response) {
console.log(err.response.data.message);
toast({
title: "Something went wrong.",
description: err.response.data.message,
status: "error",
...defaultToastProps,
});
} else {
console.log(err);
toast({
title: "Something went wrong.",
description: "server-error",
status: "error",
...defaultToastProps,
});
}
});
};

Related

React - PayPal Button fires without checking conditions

I'm using react-paypal-express-checkout
I've to options: Cash and PayPal.
Cash working fine and checks all conditions.
But bcs PayPal is a seperate component in my CartScreen component it opens and don't check a single if conditions and opens the PayPal window
The CashButton comes with function "cashTranSuccess" it's the same function as "TranSuccess"
just without the paymentID bcs it's only needed for react-paypal-express-checkout
So what I'm looking for is, to check all TranSuccess() conditions before open the PayPal window.
PayPalButton.js
import React from 'react';
import PaypalExpressBtn from 'react-paypal-express-checkout';
export default class PayPalButton extends React.Component {
render() {
const onSuccess = (payment) => {
// Congratulation, it came here means everything's fine!
console.log('The payment was succeeded!', payment);
// You can bind the "payment" object's value to your state or props or whatever here, please see below for sample returned data
this.props.tranSuccess(payment);
};
const onCancel = (data) => {
// User pressed "cancel" or close Paypal's popup!
console.log('The payment was cancelled!', data);
// You can bind the "data" object's value to your state or props or whatever here, please see below for sample returned data
};
const onError = (err) => {
// The main Paypal's script cannot be loaded or somethings block the loading of that script!
console.log('Error!', err);
// Because the Paypal's main script is loaded asynchronously from "https://www.paypalobjects.com/api/checkout.js"
// => sometimes it may take about 0.5 second for everything to get set, or for the button to appear
};
let env = 'sandbox'; // you can set here to 'production' for production
let currency = 'EUR'; // or you can set this value from your props or state
let carttotal = this.props.carttotal; // same a s above, this is the total amount (based on currency) to be paid by using Paypal express checkout
// Document on Paypal's currency code: https://developer.paypal.com/docs/classic/api/currency_codes/
const client = {
sandbox:
'',
production: 'YOUR-PRODUCTION-APP-ID',
};
// In order to get production's app-ID, you will have to send your app to Paypal for approval first
// For sandbox app-ID (after logging into your developer account, please locate the "REST API apps" section, click "Create App"):
// => https://developer.paypal.com/docs/classic/lifecycle/sb_credentials/
// For production app-ID:
// => https://developer.paypal.com/docs/classic/lifecycle/goingLive/
// NB. You can also have many Paypal express checkout buttons on page, just pass in the correct amount and they will work!
// Style Options: https://developer.paypal.com/docs/checkout/standard/customize/buttons-style-guide/ ; https://wise.com/gb/blog/custom-paypal-button
let style = {
size: 'medium',
color: 'gold',
label: 'pay',
tagline: false,
};
return (
<PaypalExpressBtn
env={env}
client={client}
currency={currency}
total={carttotal}
onError={onError}
shipping={1}
onSuccess={onSuccess}
onCancel={onCancel}
style={style}
/>
);
}
}
CartScreen
const tranSuccess = async (payment) => {
const { paymentID } = payment;
// Check time, min amoint, for delivery add delivery fees
if (timeValidation === true) {
if (sliderDeliveryValue === 'delivery') {
if (carttotal > settings[0]?.minDeliveryAmount) {
await axios.post(
'/api/payment',
{ cartItems, paymentID, time, sliderDeliveryValue, carttotal },
{
headers: { Authorization: token },
}
);
cartItems.map((remove) => {
dispatch(deleteFromCart(remove));
});
//console.log(cartItems.length);
toast.success(
'Order successful',
{
position: toast.POSITION.TOP_RIGHT,
}
);
} else {
toast.error(
`Min amount${settings[0]?.minDeliveryAmount}€`,
{
position: toast.POSITION.TOP_RIGHT,
}
);
}
} else if (sliderDeliveryValue === 'pickup') {
if (carttotal > 2) {
await axios.post(
'/api/payment',
{ cartItems, paymentID, time, sliderDeliveryValue, carttotal },
{
headers: { Authorization: token },
}
);
cartItems.map((remove) => {
dispatch(deleteFromCart(remove));
});
//console.log(cartItems.length);
toast.success(
'Order successful',
{
position: toast.POSITION.TOP_RIGHT,
}
);
} else {
toast.error(`Min amount 2.00€`, {
position: toast.POSITION.TOP_RIGHT,
});
}
} else {
toast.error('Choose delivery method', {
position: toast.POSITION.TOP_RIGHT,
});
}
} else {
toast.error('closed', {
position: toast.POSITION.TOP_RIGHT,
});
}
};
<PayPalButton
carttotal={carttotal}
tranSuccess={tranSuccess}
/>
<div onClick={cashTranSuccess}>
<CashButton />
</div>
Consider using the official #paypal/react-paypal-js
An example of validation using onInit and onClick functions and the actions.enable/disable callbacks or returning a promise (actions.resolve/reject) can be found in the developer documentation. Adapt this to check whatever condition you need.

Antd Notification appearing twice?

I am using antd notification component as follows
import { notification } from "antd";
const Notification = ({ msg, type, clearMsg }) => {
return (
<div>
{notification[type]({
description: msg,
})}
{clearMsg()}
</div>
);
};
export default Notification;
And I am simply using it anywhere I need a notification to pop-up. For instance after API response failure:
const onSubmit = async (e) => {
try {
const res = await login(data);
if (res.message) {
setError({ state: true, msg: res.message });
}
} catch (err) {
console.log(err);
}
};
And then depending on error state, I am showing the Notification in the return body as following:
{ error.state ?
<Notification
type="error"
msg="some msg"
clearMsg={() => setError({state: false, msg: ""})} :
null
}
But simultaneously two pop-ups appear instead of just one, can anyone guide on why I am getting this behaviour?
Assign a unique identifier by using the key property. See: https://ant.design/components/notification/#API
const Notification = ({ key, msg, type, clearMsg }) => {
return (
<div>
{notification[type]({
key,
description: msg,
})}
{clearMsg()}
</div>
);
};
I resolved my problem by using notification.destroy(); before showing notification. An example below here:
const showNotification = () => {
notification.destroy();
notification.open({message: 'message here'});
}

How to display comments coming from Redux store on individual component

I have created a basic single page app, on initial page there is some dummy data and on click of each item I direct user to individual details page of that item. I wanted to implement comment and delete comment functionality which I successfully did but now when I comment or delete the comment it doesn't only happen at that individual page but in every other page too. Please see the sandbox example for better clarify.
https://codesandbox.io/s/objective-feistel-g62g0?file=/src/components/ProductDetails.js
So once you add some comments in individual page, go back and then click to another products, apparently you will see that the comments you've done in other pages are also available there. What do you think causing this problem ?
The same state being reused by all the different pages.
Try to load dynamically load reducers for each page/router differently to use distinct state values.
You can start from here
Redux modules and code splitting
I found my own logical solution. You probably might find a better solution but this works pretty well too. I thought of passing another property in the object with the params I get from url and then filter the comments by their url params. So that I could do filtering based on the url parameters and display the comments only made on that specific page.
So ProductDetails.js page should be looking like this:
import React, { useState, useEffect } from 'react';
import { Input, Button } from 'semantic-ui-react'
import { connect } from 'react-redux';
const ProductDetails = (props) => {
const [commentObject, setCommentObject] = useState({
text: "",
date: "",
id: ""
});
const clickHandler = () => {
if (!commentObject.text.trim()) {
return
}
props.addNewComment(commentObject)
setCommentObject({
...commentObject,
text: ""
})
console.log(commentObject.id);
}
useEffect(() => {
}, []);
return (
<div>
{props.posts ? props.posts.text : null}
{props.comments.filter(comment => {
return comment.postId === props.match.params.slug
}).map(({ text, id }) => {
return (<div key={id}>
<p>{text}</p>
<Button onClick={() => props.deleteComment(id)} >Delete comment</Button></div>)
})}
<Input value={commentObject.text}
onChange={comment => setCommentObject({ text: comment.target.value, date: new Date(), id: Date.now(), postId: props.match.params.slug })}
/>
<Button onClick={clickHandler} >Add comment</Button>
</div>
);
}
const mapStateToProps = (state, ownProps) => {
let slug = ownProps.match.params.slug;
return {
...state,
posts: state.posts.find(post => post.slug === slug),
}
}
const mapDispatchToProps = (dispatch) => {
return {
addNewComment: (object) => { dispatch({ type: "ADD_COMMENT", payload: { comment: { text: object.text, date: object.date, id: object.id, postId: object.postId } } }) },
deleteComment: (id) => { dispatch({ type: "DELETE_COMMENT", id: id }) }
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ProductDetails);

Store doesn't update after GET

After DELETE a resource via a Button I update the same resource with a GET, i get the response with the correct data but the redux store doesn't update (apparently the redux action see no diff), and so the props are not updated.
But when I do a CREATE and then a GET like before (same call) this time the redux store see the diff and update the props.
The resource are the same between the 2 calls (after DELETE or CREATE), and the calls are even the same. Why in one case the redux store see the diff and don't in another case ?
crudCreate(`${CAMPAIGNS}/${id}/${LINE_ITEMS}`, data, () =>
crudGetAll(LINE_ITEMS, {}, { campaignId: id }, 1000, () => this.displayModal()),
);
crudDelete(LINE_ITEMS, lineItem.id, { id }, lineItem, () =>
crudGetAll(LINE_ITEMS, {}, { campaignId: id }, 1000, ({ payload: { data } }) =>
this.updateLineItems(data),
),
);
I don't any error on Redux debugger or on Console. I have a custom DataProvider, in that case it only redirect for the good routes
case LINE_ITEMS: {
if (type === 'DELETE') {
return { urn: `${apiUrl}/campaigns/${filter.id}/${resource.toLowerCase()}/${id}` };
}
if (filter) {
if ('campaignId' in filter) {
return {
urn: `${apiUrl}/campaigns/${filter.campaignId}/${resource.toLowerCase()}`,
filter: {
...filter,
campaignId: undefined,
},
};
}
}
return {
urn: `${apiUrl}/campaigns/${id.campaignId}/${resource.toLowerCase()}/${data.lineItemId}`,
};
}
And I only use the reducer from react-admin, here my index.js:
import { connect } from 'react-redux';
import { crudDelete, crudGetAll, translate } from 'react-admin';
export default connect(
state => ({
lineItems: Object.values(state.admin.resources.lineItems.data) || {},
swordData: Object.values(state.admin.resources.sword.data),
}),
{ crudDelete, crudGetAll },
)(withRouter(WithPermissions(translate(MyComponent))));
Does anybody have an idea? Thanks for your help

service-worker.js not receiving message

Im trying to get my service-worker.js to execute skipWaiting when a new service worker gets uploaded. However, im not successful in performing this. Please note that my main framework is reactjs
Tried following this guide (https://deanhume.com/displaying-a-new-version-available-progressive-web-app/) and adapting the code to mine but wasnt successful either.
Currently Im only able to receive notifications that the a new service worker is available, this was observed and confirmed under the browser-dev-tools as well.
Here's my code:
index.js
import "#babel/polyfill";
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));
function check() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js').then(reg => {
reg.addEventListener('updatefound', () => {
// A wild service worker has appeared in reg.installing!
const newWorker = reg.installing;
newWorker.addEventListener('statechange', () => {
// Has network.state changed?
switch (newWorker.state) {
case 'installed':
if (navigator.serviceWorker.controller) {
// new update available
window.serviceWorkerCallback('new_version')
newWorker.postMessage({ action: 'skipWaiting' });
console.log("New Version!")
}
// No update available
break;
}
});
});
});
};
}
check();
window.serviceWorkerCallback('new_version') from index.js activates _serviceWorkerCallback function under App.js which triggers my reload command. After refreshing my page, I could see that the new service worker is still waiting. Below is my reload command code:
App.js:
_serviceWorkerCallback = (state) => {
if (state === 'new_version') {
this.setState({
hasNewVersion: true
});
}
}
_renderNewVersion = () => {
return (
<Snackbar
style={{ marginBottom: 16 }}
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
open={this.state.hasNewVersion}
message={<span>A new version is available</span>}
action={<Button color="secondary" size="small" onClick={this._handleRefresh}>Refresh</Button>}
/>
);
}
_handleRefresh = (value) => {
this.setState({
hasNewVersion: false,
});
window.location.reload();
}
Here is service-worker.js :
self.addEventListener('message', event => {
console.log('Skipping');
if (event.data.action === 'skipWaiting') {
self.skipWaiting();
}
});
self.addEventListener('fetch', function (event) {
console.log('Fetching');
event.respondWith(
caches.match(event.request)
.then(function (response) {
if (response) {
return response;
}
return fetch(event.request);
})
);
});
_handleRefresh = (value) => {
this.setState({
hasNewVersion: false,
});
window.location.reload();
}
Im expecting newWorker.postMessage({ action: 'skipWaiting' }); to trigger event.data.action === 'skipWaiting' but nothing happened and no log messages has been observed from the console.
Both files are residing under the same directory.
In the tutorial you linked, the code includes a call to reload the page but I do not see that in yours.
The reload is important because it allows the newly activated service worker to become the active worker once the page calls register. Alternatively you can use Clients.claim() to let your new service worker take immediate control of existing pages.

Resources