How to dynamically create multiple alerts inside mui v5 Snackbar - javascript

I am calling an API to do few actions.
I want to take the response of each action and show it inside Snackbar/alert.
I am able to show only the first response and nothing else even after iterating the messages in a map.
Here is my business logic calling the api
try {
const deletedUser = await deleteUser({ username: username });
[deletedUser.data.status.action1, deletedUser.data.status.action2].map(
(msg) =>
setNotify({
isOpen: true,
message: msg,
type: "success",
})
);
} catch (error) {}
setNotify will open the Snackbar with the alerts
import React from "react";
import { Snackbar, Alert } from "#mui/material";
import { styled } from "#mui/material/styles";
const StyledSnackbar = styled((props) => <Snackbar {...props} />)(
({ theme }) => ({
"& .MuiSnackbar-root": {
top: theme.spacing(15),
},
})
);
export default function Notification(props) {
const { notify, setNotify } = props;
const handleClose = (event, reason) => {
setNotify({
...notify,
isOpen: false,
});
};
return (
<StyledSnackbar
open={notify.isOpen}
autoHideDuration={10000}
anchorOrigin={{ vertical: "top", horizontal: "right" }}
onClose={handleClose}
>
<Alert severity={notify.type} onClose={handleClose}>
{notify.message}
</Alert>
</StyledSnackbar>
);
}
The only issue it's only displaying the first action and nothing else.
Edit On Sandbox
I suspect the alerts are overlapping on top of each other Maybe we need to add some sort of AutoGrow prop

You have to use notistack as described in the MUI doc:
This example demonstrates how to use notistack. notistack has an
imperative API that makes it easy to display snackbars, without having
to handle their open/close state. It also enables you to stack them on
top of one another (although this is discouraged by the Material
Design guidelines).
Start by wrapping your app inside a SnackbarProvider component then use useSnackbar hook to access enqueueSnackbar in order to add a snackbar to the queue to be displayed:
App.js
import "./styles.css";
import React from "react";
import { SnackbarProvider } from "notistack";
import Notification from "./Notification";
export default function App() {
return (
<SnackbarProvider maxSnack={3}>
<div className="App">
<h1>Dynamic Snackbar Alerts</h1>
<Notification />
</div>
</SnackbarProvider>
);
}
Notification.js
import React from "react";
import { Button } from "#mui/material";
import { useSnackbar } from "notistack";
export default function Notification(props) {
const { enqueueSnackbar } = useSnackbar();
const handleClick = async () => {
try {
const deletedUser = await deleteUser({ username: username });
[deletedUser.data.status.action1, deletedUser.data.status.action2].forEach((msg) => {
enqueueSnackbar(msg, {
variant: "success",
autoHideDuration: 10000,
anchorOrigin: { vertical: "top", horizontal: "right" }
});
});
} catch (error) {}
};
return (
<Button variant="outlined" onClick={handleClick}>
Generate Snackbar Dynamicly
</Button>
);
}
Demo:

Related

Editorjs custom block renders twice on click when made as default block in react project

I followed this tutorial to integrate editorjs in react and create a custom editorjs plugin. This tutorial works fine to create a custom block but I wanted to make the custom block as a default block in editorjs. In doing so, the block renders twice when I click on empty space in the editor. What might be the problem.
This is code for Editor.jsx file to create editorjs text editor:
import { default as React, useEffect, useRef } from "react";
import EditorJS from "#editorjs/editorjs";
import { EDITOR_JS_TOOLS } from "./tools";
import { Box } from "#mui/material";
const DEFAULT_INITIAL_DATA = () => {
return {
time: new Date().getTime(),
blocks: [
{
type: "header",
data: {
text: "This is my awesome editor!",
level: 1,
},
},
],
};
};
const EDITTOR_HOLDER_ID = "editorjs";
const Editor = (props) => {
const ejInstance = useRef();
const [editorData, setEditorData] = React.useState(DEFAULT_INITIAL_DATA);
// This will run only once
useEffect(() => {
if (!ejInstance.current) {
initEditor();
}
return () => {
ejInstance.current.destroy();
ejInstance.current = null;
};
}, []);
const initEditor = () => {
const editor = new EditorJS({
holder: EDITTOR_HOLDER_ID,
logLevel: "ERROR",
data: editorData,
onReady: () => {
ejInstance.current = editor;
},
onChange: async () => {
let content = await this.editorjs.saver.save();
// Put your logic here to save this data to your DB
setEditorData(content);
},
autofocus: true,
tools: EDITOR_JS_TOOLS,
defaultBlock: "timeline", // I have made timeline (custom block) as default block.
});
};
return (
<React.Fragment>
<Box id={EDITTOR_HOLDER_ID} sx={{ py: 2 }}></Box>
</React.Fragment>
);
};
export default Editor;
i also faced the same issue so i used react-editor-js instead, however simply rendering the ReactEditor component cannot be done as it will give the same result(duplicated blocks) as the parent component gets rendered twice, i don't know why, i kept the editorjs rendering mecahnism in the useEffect and refer the render container by id.
import React from "react";
import ReactDOM from "react-dom";
import { createReactEditorJS } from "react-editor-js";
import Header from "#editorjs/header";
import textBox from "./tools/textBox";
const ReactEditor = createReactEditorJS();
const Editor = () => {
React.useEffect(() => {
ReactDOM.render(
<ReactEditor
tools={{
header: Header,
textBox: textBox,
}}
defaultBlock="textBox"
/>,
document.getElementById("react-editor-container")
);
}, []);
return <div id="react-editor-container"></div>;
};
However, i am a noob and this may not be the best solution !!

test if a modal disappears after clicking Delete

Beginner with react testing,
I am using jest and react testing library, here I have a component 'A' which is a modal, I'm trying to implement a test to it, when the user clicks a button 'Delete Link' then this modal should disappear(function onDelete). As you can see I'm clicking the button using FireEvent.click() so after it when changing toHaveBeenCalledTimes(0) from 0 to 1, I'm getting Expected number of calls: 1 Received number of calls: 0, shouldn't be expected and received both be 1?
The end component(modal) should not be visible to the user after clicking Delete Link.
Can someone enlighten me with this?
Any suggestions/help is appreciated.
English is not my mother language so there might be mistakes.
my code:
import React from "react";
import { render, screen, cleanup, fireEvent } from "#testing-
library/react";
import { LinkForm } from "../forms/LinkForm";
import { Provider } from "react-redux";
import { store } from "../../redux/store";
import "#testing-library/jest-dom/extend-expect";
describe("Testing component", () => {
const onClickCallback = jest.fn();
test("Testing if link is deleted when button 'Delete Link' is clicked", () => {
const mockDelete = jest.fn();
const props = {
onDelete: mockDelete,
};
render(
<Provider store={store}>
<LinkForm
classes={{ button_basic: "", formControl: "" }}
key={""}
onSubmit={onClickCallback}
onCancel={onClickCallback}
// onClick={onClickCallback()}
{...props}
/>
</Provider>
);
const component = screen.getByTestId("LinkForm");
const deleteLinkButton = screen.getByRole("button", {
name: /Delete Link/i,
});
expect(deleteLinkButton).toBeVisible();
fireEvent.click(deleteLinkButton);
expect(mockDelete).toHaveBeenCalledTimes(0);
expect(component).toBeVisible();
});
});
import React, { useEffect, useState } from "react";
import { connect, RootStateOrAny, useDispatch, useSelector } from "react-redux";
import { Trans } from "react-i18next";
import { editLink, changeLink, removeLink } from "../../redux/actions";
import {Button} from "#material-ui/core/";
import { Done, Delete } from "#material-ui/icons";
interface AFormProps {
key: string;
onSubmit: () => void;
onCancel: () => void;
onClick?: () => void;
classes: {
button_basic: string;
formControl: string;
};
}
const A: React.FC<AFormProps> = (props) => {
const dispatch = useDispatch();
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
dispatch(editLink(linkSettings));
externalOnSubmit();
};
const onDelete = () => {
// Delete selected link from graph
dispatch(removeLink(currentLink.id));
dispatch(changeLink(""));
};
const disabled = currentLink ? false : true;
return (
<form onSubmit={handleSubmit} data-testid="LinkForm">
<Button
id="delete"
type="button"
onClick={onDelete}
disabled={disabled}
variant="outlined"
color="secondary"
className={classes.button_basic}
startIcon={<Delete />}
>
<Trans i18nKey="form.linkForm.delete">Delete Link</Trans>
</Button>
</form>
);
};
export const LinkForm = connect(null, null)(A);
In the test, you are passing a mock function to the onDelete prop of the form but this is not defined in AFormProps nor is an onDelete prop being consumed in the component. The onDelete function is created within the component scope and set as the onClick for the button. The mock function will never be used in this case.

Why is useEffect being triggered without dependency change when working with modals?

I'm having trouble working with useEffect to fetch comments when using a modal. I have a PostMain component that is displayed inside a modal, as seen below. Inside this, there is a CommentsList child component that fetches comments left under the post from the server. I have created a custom hook to handle this, as seen below. The problem I'm facing is whenever I exit the modal, then reopen it, useEffect is triggered even though its dependencies (pageNumber, postId) haven't changed. A server request similar to the initial one is sent, with the same comments being added to the list, as seen in the screenshots below. Obviously, this is not ideal. So, what am I doing wrong? How do I fix this?
Fetch Comments Custom Hook
import { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { fetchComments } from '../store/comments/actions';
function useFetchComments(pageNumber, commentsPerRequest = 5, postId) {
const { error, hasMoreComments, isLoading, commentList } = useSelector(
({ comments }) => ({
error: comments.error,
hasMoreComments: comments.hasMoreComments,
isLoading: comments.isLoading,
commentList: comments.commentList,
})
);
const currentCommentListLength = commentList.length || 0;
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchComments(pageNumber, commentsPerRequest, currentCommentListLength, postId));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [pageNumber, postId]);
return {
error,
hasMoreComments,
isLoading,
commentList,
};
}
export default useFetchComments;
Post Component
import React from 'react';
import { useSelector } from 'react-redux';
import { Image, Modal } from 'semantic-ui-react';
import CommentForm from '../../forms/comment';
import CommentList from '../../shared/comment-list';
function PostMain({ post }) {
const { isLoggedIn } = useSelector(({ auth }) => ({
isLoggedIn: auth.isLoggedIn,
}));
return (
<Modal size="tiny" trigger={<Image src={post.url} />}>
<Modal.Content>
<div>
<Image src={post.url} />
<CommentList postId={post._id} />
{isLoggedIn && (
<CommentForm postId={post._id} />
)}
</div>
</Modal.Content>
</Modal>
);
}
export default PostMain;
Comment List Component
import React, { useState } from 'react';
import { useFetchComments } from '../../../hooks';
function CommentList({ postId }) {
const COMMENTS_PER_REQUEST = 5;
const [pageNumber, setPageNumber] = useState(1);
const { error, isLoading, commentList, hasMoreComments } = useFetchComments(
pageNumber,
COMMENTS_PER_REQUEST,
postId
);
const handleFetchMoreComments = () => {
setPageNumber((previousNumber) => previousNumber + 1);
};
return (
<div>
<div>
{commentList.map((comment) => (
<div key={comment._id}>{comment.body}</div>
))}
{hasMoreComments && (
<p onClick={handleFetchMoreComments}>View More</p>
)}
</div>
{isLoading && <p>Loading...</p>}
{error && <p>{JSON.stringify(error)}</p>}
</div>
);
}
export default CommentList;
First instance of opening modal
Second instance of opening modal

Issue with loading a button based on the loggedin state

I am trying different ways to display a conditional button based on the athtentication state, but i keep getting into trouble. I have an app.js that defines the stacknavigator, which adds a button to the header giving the option to log out if authenticated.
I wrote a handleLogout function that should perform this.
import React from 'react';
import { Button, Image, View, Text } from 'react-native';
import firebase from 'react-native-firebase';
import Loading from './Loading';
import SignUp from './SignUp';
import Login from './Login';
import Main from './Main';
import {createAppContainer} from 'react-navigation';
import {createStackNavigator} from 'react-navigation-stack';
import { useNavigation } from '#react-navigation/native';
// eslint-disable-next-line no-undef
handleLogOut = () => {
const navigation = useNavigation();
firebase
.auth()
.signOut()
.then(() => this.props.navigation.navigate('Login'))
.catch(error => this.setState({errorMessage: error.message}));
};
const AppNavigator = createStackNavigator(
{
Loading: Loading,
SignUp: SignUp,
Login: Login,
Main: Main,
},
{
initialRouteName: 'Loading',
defaultNavigationOptions: {
headerLeft: null,
headerRight: () => {
let button = this.loggedIn? (
<Button
onPress={this.handleLogOut}
title="Log-out"
color="#fff"
/>
)
:
(
<Button
onPress={() => alert('Please log in')}
title="Log-in"
color="#fff"
/>
)
return button;
},
headerStyle: {
backgroundColor: '#c6f1e7',
},
headerTintColor: '#59616e',
headerTitleStyle: {
fontFamily: 'Raleway-Regular',
fontWeight: '400',
},
},
},
);
export default createAppContainer(AppNavigator);
App.js calls on loading.js where the value for loggedin is declared, based on the authentciation state and then loads either main.js or sign-up. in this case the main page is loaded, which means that someone is authenticated:
// Loading.js
import React from 'react';
import {View, ActivityIndicator, StyleSheet} from 'react-native';
import firebase from 'react-native-firebase';
export default class Loading extends React.Component {
componentDidMount() {
firebase.auth().onAuthStateChanged(user => {
if (user) {
this.setState({ loggedIn: true })
this.props.navigation.navigate(user ? 'Main' : 'SignUp');
} else {
this.setState({ loggedIn: false })
}
});
}
render() {
return (
<View style={styles.container}>
<ActivityIndicator size="large" />
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#c6f1e7',
},
});
Now the page redirects to main and shows the welcome message, which indicates that the user is logged in, but the button in the header is saying 'log-in' as well, which means the button is not chosen well. I assume that this is because the loggedin value is not read and it automatically sets it on loggedin: false.
Here is the code for main.js
// Main.js
import React from 'react';
import { View, Text, StyleSheet, Button } from 'react-native';
import firebase from 'react-native-firebase';
import { createAppContainer } from 'react-navigation';
import { createBottomTabNavigator } from 'react-navigation-tabs';
import Kowops from './Kowops';
import Scan from './Scan';
import Wallet from './Wallet';
export class Main extends React.Component {
state = { currentUser: null }
componentDidMount() {
const { currentUser } = firebase.auth()
this.setState({ currentUser })
}
render() {
const { currentUser } = this.state
return (
<View style={styles.container}>
<Text>
Hidiho {currentUser && currentUser.email}!
</Text>
</View>
)
}
}
const bottomTabNavigator = createBottomTabNavigator(
{
Main: {screen: Main},
Kowops: {screen:Kowops},
Scan: {screen:Scan},
Wallet: {screen:Wallet},
},
{
//initialRouteName: 'Main',
tabBarOptions: {
initialRouteName: 'Main',
activeTintColor: '#59616e',
inactiveTintColor: '#a9a9a9',
style: {
backgroundColor: '#c6f1e7',
}
},
});
export default createAppContainer(bottomTabNavigator);
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
}
})
So I need to figure out how to ensure that the value of isloggedin is read properly and the script loads the right button.
Does anyone have a clue?
Thanks in advance!!
Tim
The key here is that you can't use state across different components without passing them as props or through navigation params in this case. You can't use the useNavigation hook outside of a functional component so you should pass the navigation object around when you need it outside of a component (handleLogout is not a component).
Here are some alterations I would make, however I would suggest that you will need to make further changes based on the idea that you can use navigation params to pass information between screens. See more here https://reactnavigation.org/docs/en/params.html.
App.js
DefaultNavigationOptions can be a function which has a navigation prop, this is the navigation object you can use to get params or navigate in the context of the router.
remove that eslint exception because you don't need it, just properly declare the variable. Remove the "this" from you handleLogout function call because it is not a class attribute. Use navigation.getParam to get the isLoggedIn variable which you can pass in the navigate function call.
const handleLogout = navigation => {
firebase
.auth()
.signOut()
.then(() => navigation.navigate('Login'))
.catch(error => this.setState({errorMessage: error.message}));
}
...
defaultNavigationOptions: ({navigation}) => ({
headerRight: () => {
const isLoggedIn = navigation.getParam('isLoggedIn', false);
let button = isLoggedIn ? (
<Button
onPress={() => handleLogOut(navigation)}
title="Log-out"
color="#fff"
/>
) : ...
} ...
Now Loading.js
Here you need to add a navigation param to your navigate call which can then be used in the header
...
firebase.auth().onAuthStateChanged(user => {
if (user) {
this.props.navigation.navigate('Main', {isLoggedIn: true});
} else {
this.props.navigation.navigate('SignUp', {isLoggedIn: false});
}
});
here is a modified version of your code in snack that you can see will get the logged in param and show the logout button https://snack.expo.io/#dannyhw/intrigued-graham-crackers
You will need to make further changes to fix other functionality because I only changed the minimum to show that you can use the navigation param.

Export function from React functional component and use it elsewhere

I want to export function from one of my functional component that is using hooks to another one. I want to prevent redundant code appearing in my components.
I have tried to create separate function.js file where I wanted to place some of my functions but useDispatch hook makes it impossible as it throws hell a lot of errors in every attempt to make it work.
I was searching for solution and trying some export statements in different combinations.
What I want to do is to export my toggleDrawer function from Layout component to other components and here's my code. I'm sure it's very easy and I'm missing something.
import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
import Header from '../Header/header'
import DrawerItems from '../DrawerItems/drawerItems'
import { REDUCERS } from '../../Config/config'
import Container from '#material-ui/core/Container'
import Drawer from '#material-ui/core/Drawer'
import { makeStyles } from '#material-ui/core/styles'
const useDrawerStyles = makeStyles({
paper: {
width: '175px',
padding: '10px'
}
})
const Layout = props => {
const { isDrawerOpened } = useSelector(state => {
return {
...state.interface_reducer
}
})
const dispatch = useDispatch()
const drawerClasses = useDrawerStyles()
const toggleDrawer = (side, open) => event => {
if (event.type === 'keydown' && (event.key === 'Tab' || event.key === 'Shift')) {
return null
}
dispatch({
type: REDUCERS.TOGGLE_DRAWER,
payload: open
})
}
return (
<Container>
<React.Fragment>
<Header/>
<Drawer classes={{paper: drawerClasses.paper}} open={isDrawerOpened} onClose={toggleDrawer('left', false)} >
<DrawerItems/>
</Drawer>
{ props.children }
</React.Fragment>
</Container>
)
}
export default Layout
Define the function in another file. Or define it in that file and export it. Then you can import it in other files for other components.

Categories

Resources