I'm trying to implement Sub-menu (nested menu).
It's worth to mention that I'm using hydra component and don't have previous experience with redux (started learning it a few days ago because of this specific problem).
I've followed the example provided on material-ui for nested list https://material-ui.com/demos/lists/#nested-list. And tutorial from
https://marmelab.com/react-admin/Theming.html#using-a-custom-menu for custom menu implementation.
So I have a few questions.
1) Can I have stateful component (MyMenu) just for handling toggling of menu items?
An example is not related to react-admin but its just example what I mean.
import React, { Component } from "react";
import { connect } from "react-redux";
import { addArticle } from "../actions/index";
const mapDispatchToProps = dispatch => {
return {
addArticle: article => dispatch(addArticle(article))
};
};
class ConnectedForm extends Component {
constructor() {
super();
this.state = {
title: ""
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({ [event.target.id]: event.target.value });
}
handleSubmit(event) {
event.preventDefault();
const { title } = this.state;
const id = uuidv1();
this.props.addArticle({ title, id });
this.setState({ title: "" });
}
render() {
const { title } = this.state;
return (
<form onSubmit={this.handleSubmit}>
<div className="form-group">
<label htmlFor="title">Title</label>
<input
type="text"
className="form-control"
id="title"
value={title}
onChange={this.handleChange}
/>
</div>
<button type="submit" className="btn btn-success btn-lg">
SAVE
</button>
</form>
);
}
}
const Form = connect(null, mapDispatchToProps)(ConnectedForm);
export default Form;
2) If not, can I achieve that by declaring a new state in store, for example, open: false, and then using the custom reducer to handle that.
3(bonus). If it's not a problem I would appreciate if someone can put me in the right direction which things to start learning first so I can less painfully manage to solve issues related to this amazing framework :)
The react-admin demo now shows a way to do so in examples/demo/src/layout/Menu.js:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import compose from 'recompose/compose';
import SettingsIcon from '#material-ui/icons/Settings';
import LabelIcon from '#material-ui/icons/Label';
import { withRouter } from 'react-router-dom';
import {
translate,
DashboardMenuItem,
MenuItemLink,
Responsive,
} from 'react-admin';
import visitors from '../visitors';
import orders from '../orders';
import invoices from '../invoices';
import products from '../products';
import categories from '../categories';
import reviews from '../reviews';
import SubMenu from './SubMenu';
class Menu extends Component {
state = {
menuCatalog: false,
menuSales: false,
menuCustomers: false,
};
static propTypes = {
onMenuClick: PropTypes.func,
logout: PropTypes.object,
};
handleToggle = menu => {
this.setState(state => ({ [menu]: !state[menu] }));
};
render() {
const { onMenuClick, open, logout, translate } = this.props;
return (
<div>
{' '}
<DashboardMenuItem onClick={onMenuClick} />
<SubMenu
handleToggle={() => this.handleToggle('menuSales')}
isOpen={this.state.menuSales}
sidebarIsOpen={open}
name="pos.menu.sales"
icon={<orders.icon />}
>
<MenuItemLink
to={`/commands`}
primaryText={translate(`resources.commands.name`, {
smart_count: 2,
})}
leftIcon={<orders.icon />}
onClick={onMenuClick}
/>
<MenuItemLink
to={`/invoices`}
primaryText={translate(`resources.invoices.name`, {
smart_count: 2,
})}
leftIcon={<invoices.icon />}
onClick={onMenuClick}
/>
</SubMenu>
<SubMenu
handleToggle={() => this.handleToggle('menuCatalog')}
isOpen={this.state.menuCatalog}
sidebarIsOpen={open}
name="pos.menu.catalog"
icon={<products.icon />}
>
<MenuItemLink
to={`/products`}
primaryText={translate(`resources.products.name`, {
smart_count: 2,
})}
leftIcon={<products.icon />}
onClick={onMenuClick}
/>
<MenuItemLink
to={`/categories`}
primaryText={translate(`resources.categories.name`, {
smart_count: 2,
})}
leftIcon={<categories.icon />}
onClick={onMenuClick}
/>
</SubMenu>
<SubMenu
handleToggle={() => this.handleToggle('menuCustomer')}
isOpen={this.state.menuCustomer}
sidebarIsOpen={open}
name="pos.menu.customers"
icon={<visitors.icon />}
>
<MenuItemLink
to={`/customers`}
primaryText={translate(`resources.customers.name`, {
smart_count: 2,
})}
leftIcon={<visitors.icon />}
onClick={onMenuClick}
/>
<MenuItemLink
to={`/segments`}
primaryText={translate(`resources.segments.name`, {
smart_count: 2,
})}
leftIcon={<LabelIcon />}
onClick={onMenuClick}
/>
</SubMenu>
<MenuItemLink
to={`/reviews`}
primaryText={translate(`resources.reviews.name`, {
smart_count: 2,
})}
leftIcon={<reviews.icon />}
onClick={onMenuClick}
/>
<Responsive
xsmall={
<MenuItemLink
to="/configuration"
primaryText={translate('pos.configuration')}
leftIcon={<SettingsIcon />}
onClick={onMenuClick}
/>
}
medium={null}
/>
<Responsive
small={logout}
medium={null} // Pass null to render nothing on larger devices
/>
</div>
);
}
}
const mapStateToProps = state => ({
open: state.admin.ui.sidebarOpen,
theme: state.theme,
locale: state.i18n.locale,
});
const enhance = compose(
withRouter,
connect(
mapStateToProps,
{}
),
translate
);
export default enhance(Menu);
I've searched the same question. But couldn't find a nested menu implementation. So I wrote my own. Check the code below;
import React, { Component, createElement } from "react";
import {
Admin,
Resource,
Layout,
MenuItemLink,
getResources
} from "react-admin";
import jsonServerProvider from "ra-data-json-server";
import { withRouter } from "react-router-dom";
import { connect } from "react-redux";
import {
List,
ListItem,
Collapse,
ListItemText,
ListItemIcon
} from "#material-ui/core";
import { ExpandLess, ExpandMore, StarBorder, LabelIcon } from "#material-ui/icons";
import { withStyles } from "#material-ui/core/styles";
const menuStyles = theme => ({
nested: {
paddingLeft: theme.spacing.unit * 4
}
});
class Menu extends Component {
menuList = [
{ name: "A", label: "Top menu 1", icon: <LabelIcon /> },
{ name: "B", label: "Top menu 2", icon: <LabelIcon /> },
{ name: "c", label: "Top menu 3", icon: <LabelIcon /> }
];
constructor(props) {
super(props);
this.state = { open: "A" };
}
render() {
const { resources, onMenuClick, logout } = this.props;
return (
<div>
<List component="nav">
{this.menuList.map(menu => {
return (
<div key={menu.name}>
<ListItem
button
onClick={() => this.setState(state => ({ open: menu.name }))}
>
<ListItemIcon>{menu.icon}</ListItemIcon>
<ListItemText inset primary={menu.label} />
{this.state.open == menu.name ? (
<ExpandLess />
) : (
<ExpandMore />
)}
</ListItem>
<Collapse
in={this.state.open == menu.name}
timeout="auto"
unmountOnExit
>
<List component="div" disablePadding>
{resources
.filter(x => x.options.menu == menu.name)
.map((resource, i) => (
<MenuItemLink
key={"m" + i}
to={`/${resource.name}`}
primaryText={resource.options.label || resource.name}
leftIcon={
resource.icon
? createElement(resource.icon)
: undefined
}
onClick={onMenuClick}
className={this.props.classes.nested}
/>
))}
</List>
</Collapse>
</div>
);
})}
</List>
</div>
);
}
}
var MenuWithStyles = withStyles(menuStyles)(Menu);
const MyMenu = withRouter(
connect(state => ({
resources: getResources(state)
}))(MenuWithStyles)
);
const MyLayout = props => <Layout {...props} menu={MyMenu} />;
const App = () => (
<Admin
...
appLayout={MyLayout}
>
<Resource
...
options={{ label: 'Page 1' menu: "A" }}
/>
<Resource
...
options={{ label: 'Page 2' menu: "A" }}
/>
<Resource
...
options={{ label: 'Page 3' menu: "B" }}
/>
</Admin>
);
Related
I have a React ecommerce site that is currently integrated with Stripe. When a successful payment is submitted, the cart is emptied (managed in localStorage), however the Cart quantity in the Navbar isn't resetting to 0.
The cart quantity is being managed in state in the <App /> component, setQty. The stripe payment is submitted in the <PaymentForm> component which is nested 4 components deep:
<App /> > <Checkout /> > <PaymentForm /> > <CheckoutForm />
In CheckoutForm, I'm using setQty({quantity: 0}); which I thought would pass "0" up to <App /> and reset the Cart quantity, instead I get an error of "Unhandled Rejection (TypeError): setQty is not a function". Why is this? How can I get this to work? Also, is there an easier way of resetting the Cart without passing props through so many components?
A breakdown of each component so you can see how I'm passing setQty through each component:
App
import React, { useState, useEffect } from 'react';
import './App.css';
import Nav from './Nav';
import Shop from './Components/Shop';
import Info from './Components/Info';
import Cart from './Components/Cart';
import Item from './Components/Item';
import Checkout from './Components/CheckoutForm/Checkout';
import {BrowserRouter as Router, Switch, Route} from 'react-router-dom';
import { getQuantity } from './helpers/helperTools';
function App() {
const storageItems = JSON.parse(localStorage.getItem('product'));
const [qty, setQty] = useState({quantity: getQuantity(storageItems || [])});
console.log("Apppp", qty)
return (
<Router>
<div className="App">
<Nav qty={qty.quantity} />
<Route path="/" exact component={Shop} />
<Route path="/Info" component={Info} />
<Route path="/Cart/" render={(props) => <Cart {...props} setQty={setQty} />} />
<Route path="/Item/:item" component={Item} />
<Route path="/Checkout" component={Checkout} setQty={setQty} />
</div>
</Router>
)
}
export default App;
Checkout
import React, { useState } from 'react';
import { Link } from 'react-router-dom';
import {
Paper,
Stepper,
Step,
StepLabel,
Typography,
CircularProgress,
Divider,
Button,
} from '#material-ui/core';
import useStyles from './styles';
import AddressForm from './AddressForm';
import PaymentForm from './PaymentForm';
const steps = ['Shipping address', 'Payment details'];
function Checkout({setQty}) {
const classes = useStyles();
const [activeStep, setActiveStep] = useState(0);
const [shippingData, setShippingData] = useState({});
const nextStep = () => setActiveStep((prevActiveStep) => prevActiveStep + 1);
const backStep = () => setActiveStep((prevActiveStep) => prevActiveStep - 1);
const next = (data) => {
setShippingData(data);
nextStep();
};
const Form = () =>
activeStep === 0 ? (
<AddressForm next={next} />
) : (
<PaymentForm shippingData={shippingData} backStep={backStep} nextStep={nextStep} setQty={setQty} />
);
const Confirmation = () => <div>Confirmation</div>;
return (
<div>
<div className={classes.toolbar} />
<main className={classes.layout}>
<Paper className={classes.paper}>
<Typography variant='h4' align='center'>
Checkout
</Typography>
<Stepper activeStep={activeStep} className={classes.stepper}>
{steps.map((step) => (
<Step key={step}>
<StepLabel>{step}</StepLabel>
</Step>
))}
</Stepper>
{activeStep === steps.length ? <Confirmation /> : <Form />}
</Paper>
</main>
</div>
);
}
export default Checkout;
PaymentForm AND CheckoutForm, both in the same file
import React, { useState } from 'react';
import { Typography, Button, Divider } from '#material-ui/core';
import {
Elements,
CardElement,
ElementsConsumer,
useStripe,
useElements,
} from '#stripe/react-stripe-js';
import { loadStripe } from '#stripe/stripe-js';
import axios from 'axios';
import { getTotal } from '../../helpers/helperTools';
import Review from './Review';
const stripePromise = loadStripe(
'pk_HIDDEN_FOR_NOW'
);
const CheckoutForm = ({ shippingData, backStep, nextStep, setQty }) => {
const stripe = useStripe();
const elements = useElements();
const handleSubmit = async (event) => {
event.preventDefault();
if (!stripe || !elements) {
return;
}
const storageItems = JSON.parse(localStorage.getItem('product'));
const products = storageItems || [];
const totalPrice = getTotal(products);
let productTitle = '';
products.map((item, index) => {
productTitle = `${productTitle} | ${item.title}`;
});
const cardElement = elements.getElement(CardElement);
const { error, source } = await stripe.createSource(cardElement);
console.log(error, source);
const order = await axios.post('http://localhost:7000/api/stripe/charge', {
amount: totalPrice * 100,
source: source.id,
receipt_email: shippingData.email,
title: productTitle,
customerName: `${shippingData.firstName} ${shippingData.lastName}`,
address: {
city: shippingData.City,
country: shippingData.shippingCountry,
line1: shippingData.address1,
postal_code: shippingData.ZIP,
state: shippingData.shippingState,
},
});
if (error) {
console.log('[error]', error);
} else {
console.log('[PaymentMethod]', order);
localStorage.setItem('product', JSON.stringify([]));
nextStep();
setQty({quantity: 0});
}
};
return (
<form onSubmit={handleSubmit}>
<CardElement
options={{
style: {
base: {
fontSize: '16px',
color: '#424770',
'::placeholder': {
color: '#aab7c4',
},
},
invalid: {
color: '#9e2146',
},
},
}}
/>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<Button variant='outlined' onClick={backStep}>
Back
</Button>
<Button type='submit' variant='contained' disabled={!stripe} color='primary'>
Pay
</Button>
</div>
</form>
);
};
function PaymentForm({ shippingData, backStep, nextStep, setQty }) {
return (
<Elements stripe={stripePromise}>
<Review />
<br />
<br />
<CheckoutForm shippingData={shippingData} nextStep={nextStep} backStep={backStep} setQty={setQty} />
</Elements>
);
}
export default PaymentForm;
Screenshot of my file structure
In the App component, you need to pass setQty as below. Props that are mentioned in the Route component would not be transmitted to the component by itself, and we need to use the render function to pass props.
<Route path="/Checkout" render={(props) => <Checkout setQty={setQty} {...props}/>
I transitioned from a previous React app to a new template. Issue is i am quite confused about how redux is setup and how i can implement authentication.
LoginForm
// validation functions
const required = value => (value === null ? 'Required' : undefined);
const email = value =>
value && !/^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(value) ? 'Invalid email' : undefined;
const LinkBtn = React.forwardRef(function LinkBtn(props, ref) {
// eslint-disable-line
return <NavLink to={props.to} {...props} innerRef={ref} />; // eslint-disable-line
});
// eslint-disable-next-line
class LoginForm extends React.Component {
// state = {
// showPassword: false,
// };
constructor() {
super();
this.state = {
email: '',
password: '',
errors: {},
showPassword: false,
};
}
handleClickShowPassword = () => {
const { showPassword } = this.state;
this.setState({ showPassword: !showPassword });
};
handleMouseDownPassword = event => {
event.preventDefault();
};
onSubmit = e => {
e.preventDefault();
const userData = {
email: this.state.email,
password: this.state.password,
};
loginUser(userData);
};
render() {
console.log(this.props);
const { classes, handleSubmit, pristine, submitting, deco } = this.props;
const { showPassword } = this.state;
return (
<Fragment>
<Hidden mdUp>
<NavLink to="/" className={classNames(classes.brand, classes.outer)}>
<img src={logo} alt={brand.name} />
{brand.name}
</NavLink>
</Hidden>
<Paper className={classNames(classes.paperWrap, deco && classes.petal)}>
<Hidden smDown>
<div className={classes.topBar}>
<NavLink to="/" className={classes.brand}>
<img src={logo} alt={brand.name} />
{brand.name}
</NavLink>
<Button
size="small"
className={classes.buttonLink}
component={LinkBtn}
to="/register"
>
<Icon className={classes.icon}>arrow_forward</Icon>
Create new account
</Button>
</div>
</Hidden>
<Typography variant="h4" className={classes.title} gutterBottom>
Sign In
</Typography>
<Typography variant="caption" className={classes.subtitle} gutterBottom align="center">
Lorem ipsum dolor sit amet
</Typography>
<section className={classes.socmedLogin}>
<div className={classes.btnArea}>
<Button variant="outlined" size="small" className={classes.redBtn} type="button">
<AllInclusive className={classNames(classes.leftIcon, classes.iconSmall)} />
Socmed 1
</Button>
<Button variant="outlined" size="small" className={classes.blueBtn} type="button">
<Brightness5 className={classNames(classes.leftIcon, classes.iconSmall)} />
Socmed 2
</Button>
<Button variant="outlined" size="small" className={classes.cyanBtn} type="button">
<People className={classNames(classes.leftIcon, classes.iconSmall)} />
Socmed 3
</Button>
</div>
<ContentDivider content="Or sign in with email" />
</section>
<section className={classes.formWrap}>
<form onSubmit={handleSubmit}>
<div>
<FormControl className={classes.formControl}>
<Field
name="email"
component={TextFieldRedux}
placeholder="Your Email"
label="Your Email"
required
validate={[required, email]}
className={classes.field}
/>
</FormControl>
</div>
<div>
<FormControl className={classes.formControl}>
<Field
name="password"
component={TextFieldRedux}
type={showPassword ? 'text' : 'password'}
label="Your Password"
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton
aria-label="Toggle password visibility"
onClick={this.handleClickShowPassword}
onMouseDown={this.handleMouseDownPassword}
>
{showPassword ? <VisibilityOff /> : <Visibility />}
</IconButton>
</InputAdornment>
),
}}
required
validate={required}
className={classes.field}
/>
</FormControl>
</div>
<div className={classes.optArea}>
<FormControlLabel
className={classes.label}
control={<Field name="checkbox" component={CheckboxRedux} />}
label="Remember"
/>
<Button
size="small"
component={LinkBtn}
to="/reset-password"
className={classes.buttonLink}
>
Forgot Password
</Button>
</div>
<div className={classes.btnArea}>
<Button variant="contained" color="primary" size="large" type="submit">
Continue
<ArrowForward
className={classNames(classes.rightIcon, classes.iconSmall)}
disabled={submitting || pristine}
/>
</Button>
</div>
</form>
</section>
</Paper>
</Fragment>
);
}
}
const mapDispatchToProps = dispatch => ({
init: bindActionCreators(loginUser, dispatch),
loginUser:
});
LoginForm.propTypes = {
classes: PropTypes.object.isRequired,
handleSubmit: PropTypes.func.isRequired,
pristine: PropTypes.bool.isRequired,
submitting: PropTypes.bool.isRequired,
loginUser: PropTypes.func.isRequired,
deco: PropTypes.bool.isRequired,
};
const LoginFormReduxed = reduxForm({
form: 'immutableExample',
enableReinitialize: true,
})(LoginForm);
const reducerLogin = 'login';
const reducerUi = 'ui';
const FormInit = connect(
state => ({
force: state,
initialValues: state.getIn([reducerLogin, 'usersLogin']),
deco: state.getIn([reducerUi, 'decoration']),
}),
mapDispatchToProps,
)(LoginFormReduxed);
export default withStyles(styles)(FormInit);
login.js
import React from 'react';
import { Helmet } from 'react-helmet';
import brand from 'dan-api/dummy/brand';
import PropTypes from 'prop-types';
import { withStyles } from '#material-ui/core/styles';
import { LoginForm } from 'dan-components';
import styles from 'dan-components/Forms/user-jss';
class Login extends React.Component {
state = {
valueForm: [],
};
submitForm(values) {
const { valueForm } = this.state;
setTimeout(() => {
this.setState({ valueForm: values });
console.log(`You submitted:\n\n${valueForm}`);
window.location.href = '/app';
}, 500); // simulate server latency
}
render() {
const title = brand.name + ' - Login';
const description = brand.desc;
const { classes } = this.props;
return (
<div className={classes.root}>
<Helmet>
<title>{title}</title>
<meta name="description" content={description} />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="twitter:title" content={title} />
<meta property="twitter:description" content={description} />
</Helmet>
<div className={classes.container}>
<div className={classes.userFormWrap}>
<LoginForm onSubmit={values => this.submitForm(values)} />
</div>
</div>
</div>
);
}
}
Login.propTypes = {
classes: PropTypes.object.isRequired,
};
export default withStyles(styles)(Login);
My authactions i am trying to add.
import axios from 'axios';
import jwt_decode from 'jwt-decode';
import setAuthToken from '../../utils/setAuthToken';
import { GET_ERRORS, SET_CURRENT_USER, USER_LOADING } from '../constants/authConstants';
// Login - get user token
export const loginUser = userData => dispatch => {
axios
.post('/api/total/users/login', userData)
.then(res => {
// Save to localStorage
// Set token to localStorage
const { token } = res.data;
localStorage.setItem('jwtToken', JSON.stringify(token));
// Set token to Auth header
setAuthToken(token);
// Decode token to get user data
const decoded = jwt_decode(token);
// Set current user
dispatch(setCurrentUser(decoded));
})
.catch(err =>
dispatch({
type: GET_ERRORS,
payload: err.response.data,
}),
);
};
// Set logged in user
export const setCurrentUser = decoded => {
return {
type: SET_CURRENT_USER,
payload: decoded,
};
};
// User loading
export const setUserLoading = () => {
return {
type: USER_LOADING,
};
};
// Log user out
export const logoutUser = history => dispatch => {
// Remove token from local storage
localStorage.removeItem('jwtTokenTeams');
// Remove auth header for future requests
setAuthToken(false);
// Set current user to empty object {} which will set isAuthenticated to false
dispatch(setCurrentUser({}));
history.push('/dashboard');
};
and the authreducer
import { Map, fromJS } from 'immutable';
import { INIT } from '../constants/reduxFormConstants';
import { SET_CURRENT_USER, USER_LOADING } from '../constants/authConstants';
const isEmpty = require('is-empty');
const initialState = {
usersLogin: Map({
isAuthenticated: false,
user: {},
loading: false,
remember: false,
}),
};
const initialImmutableState = fromJS(initialState);
export default function reducer(state = initialImmutableState, action = {}) {
switch (action.type) {
case INIT:
return state;
case SET_CURRENT_USER:
return {
...state,
isAuthenticated: !isEmpty(action.payload),
user: action.payload,
};
case USER_LOADING:
return {
...state,
loading: true,
};
default:
return state;
}
}
I'm having a really hard time understaning how i can make this work together.
adding app.js
/**
* app.js
*
* This is the entry file for the application, only setup and boilerplate
* code.
*/
// Needed for redux-saga es6 generator support
import '#babel/polyfill';
// Import all the third party stuff
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { ConnectedRouter } from 'connected-react-router/immutable';
import FontFaceObserver from 'fontfaceobserver';
import history from 'utils/history';
import 'sanitize.css/sanitize.css';
// Import root app
import App from 'containers/App';
import './styles/layout/base.scss';
// Import Language Provider
import LanguageProvider from 'containers/LanguageProvider';
// Load the favicon and the .htaccess file
import '!file-loader?name=[name].[ext]!../public/favicons/favicon.ico'; // eslint-disable-line
import 'file-loader?name=.htaccess!./.htaccess'; // eslint-disable-line
import configureStore from './redux/configureStore';
// Import i18n messages
import { translationMessages } from './i18n';
// Observe loading of Open Sans (to remove open sans, remove the <link> tag in
// the index.html file and this observer)
const openSansObserver = new FontFaceObserver('Open Sans', {});
// When Open Sans is loaded, add a font-family using Open Sans to the body
openSansObserver.load().then(() => {
document.body.classList.add('fontLoaded');
});
// Create redux store with history
const initialState = {};
const store = configureStore(initialState, history);
const MOUNT_NODE = document.getElementById('app');
const render = messages => {
ReactDOM.render(
<Provider store={store}>
<LanguageProvider messages={messages}>
<ConnectedRouter history={history}>
<App />
</ConnectedRouter>
</LanguageProvider>
</Provider>,
MOUNT_NODE,
);
};
if (module.hot) {
// Hot reloadable React components and translation json files
// modules.hot.accept does not accept dynamic dependencies,
// have to be constants at compile-time
module.hot.accept(['./i18n', 'containers/App'], () => {
ReactDOM.unmountComponentAtNode(MOUNT_NODE);
render(translationMessages);
});
}
// Chunked polyfill for browsers without Intl support
if (!window.Intl) {
new Promise(resolve => {
resolve(import('intl'));
})
.then(() =>
Promise.all([import('intl/locale-data/jsonp/en.js'), import('intl/locale-data/jsonp/de.js')]),
) // eslint-disable-line prettier/prettier
.then(() => render(translationMessages))
.catch(err => {
throw err;
});
} else {
render(translationMessages);
}
// Install ServiceWorker and AppCache in the end since
// it's not most important operation and if main code fails,
// we do not want it installed
if (process.env.NODE_ENV === 'production') {
require('offline-plugin/runtime').install(); // eslint-disable-line global-require
}
the app component
import React from 'react';
import jwt_decode from 'jwt-decode';
import { Switch, Route } from 'react-router-dom';
import NotFound from 'containers/Pages/Standalone/NotFoundDedicated';
import store from '../../redux/configureStore';
import { setCurrentUser, logoutUser } from '../../redux/actions/authActions';
import setAuthToken from '../../utils/setAuthToken';
import Auth from './Auth';
import Application from './Application';
import ThemeWrapper, { AppContext } from './ThemeWrapper';
window.__MUI_USE_NEXT_TYPOGRAPHY_VARIANTS__ = true;
class App extends React.Component {
render() {
console.log(this.props);
return (
<ThemeWrapper>
<AppContext.Consumer>
{changeMode => (
<Switch>
<Route path="/" exact component={Auth} />
<Route
path="/app"
render={props => <Application {...props} changeMode={changeMode} />}
/>
<Route component={Auth} />
<Route component={NotFound} />
</Switch>
)}
</AppContext.Consumer>
</ThemeWrapper>
);
}
}
export default App;
the auth component
import React from 'react';
import { Switch, Route } from 'react-router-dom';
import Outer from '../Templates/Outer';
import {
Login,
LoginV2,
LoginV3,
Register,
RegisterV2,
RegisterV3,
ResetPassword,
LockScreen,
ComingSoon,
Maintenance,
NotFound,
} from '../pageListAsync';
class Auth extends React.Component {
render() {
return (
<Outer>
<Switch>
<Route path="/login" component={Login} />
{/* <Route path="/login-v2" component={LoginV2} />
<Route path="/login-v3" component={LoginV3} />
<Route path="/register" component={Register} />
<Route path="/register-v2" component={RegisterV2} />
<Route path="/register-v3" component={RegisterV3} /> */}
<Route path="/reset-password" component={ResetPassword} />
<Route path="/lock-screen" component={LockScreen} />
{/* <Route path="/maintenance" component={Maintenance} />
<Route path="/coming-soon" component={ComingSoon} /> */}
<Route component={NotFound} />
</Switch>
</Outer>
);
}
}
export default Auth;
To maintain authentication using PLain redux is not quite possible because when ever you reload, the store get refreshed . However, redux has a functionality called Persisted store
Persisted Store store the data in memory and will not be refreshed with page reload or anything like that.
You can check this Link
Update with out persisted store:
In that case, Get the IsLoggedin state from store.
In App component
const App = () => {
const isLoggedIn = localStorage.getItem("jwtToken") !== null ? true: false
return (
<Router>
<Switch>
<PrivateRoute isLoggedIn={isLoggedIn} path="/dashboard">
<Dashboard />
</PrivateRoute>
<Route path="/login">
<Login authToken={authToken} />
</Route>
</Switch>
</Router>
);
and then in private route component:
const PrivateRoute = (props) => {
const { children, IsLoggedin, ...rest } = props;
return (
<Route
{...rest}
render={({ location }) =>
IsLoggedin ? (
children
) : (
<Redirect
to={{
pathname: '/login',
state: { from: location },
}}
/>
)
}
/>
);
};
UPDATE: After some experimentation, I have determined that the problem is that I have some code (see the useEffect() section below) which updates the accordion when the menu object changes. On. the first render, defaultActiveKey works, but on subsequent re-renders, it does not.
I am using the Accordion control from react-bootstrap and can get the basic example from the docs working, but after customizing the code quite a bit, the default accordion no longer opens by... default. Here's probably the most interesting part of the code:
return (
<>
<MenuMobile menuItems={menuItems} open={open} setOpen={setOpen} />
<Navbar bg="white" variant="light" expand="lg" fixed="left">
<Navbar.Brand href="/home">
<img src={logo} width="113" height="40" alt={siteTitle + " Logo"} />
</Navbar.Brand>
<NavbarToggler open={open} setOpen={setOpen} />
<Accordion
defaultActiveKey={menu.defaultActiveKey}
className="sidebar-menu"
data-active={menu.defaultActiveKey}
>
{menu.cards.map((card, index) => {
return (
<Card key={index}>
<CustomToggle title={card.title} eventKey={card.eventKey} anchors={card.anchors} />
<Accordion.Collapse eventKey={card.eventKey}>
<Card.Body>
{card.anchors.map((anchor) => (
<a href={`#${anchor.href}`} key={anchor.href}>
{anchor.text}
</a>
))}
</Card.Body>
</Accordion.Collapse>
</Card>
);
})}
</Accordion>
</Navbar>
</>
);
I've outputted the menu.defaultActiveKey in a data attribute just to make sure it's getting it right and it is. I suspect the problem has to do with the fact that I'm generating the child <Card> components dynamically but I'm not sure what the fix is?
The entire source code is below if you're interested:
import React, { useState, useContext, useEffect } from "react";
import PropTypes from "prop-types";
import Navbar from "react-bootstrap/Navbar";
import AccordionContext from "react-bootstrap/AccordionContext";
import Accordion from "react-bootstrap/Accordion";
import Card from "react-bootstrap/Card";
import { useAccordionToggle } from "react-bootstrap/AccordionToggle";
import classNames from "classnames";
import queryString from "query-string";
import MenuMobile from "./menuMobile";
import NavbarToggler from "./navbarToggler";
import EduMenus from "../utility/educationMenus";
import logo from "../images/logo-white.svg";
const CustomToggle = ({ title, eventKey, anchors, callback }) => {
const currentEventKey = useContext(AccordionContext);
const onClickToggle = useAccordionToggle(eventKey, () => {
callback(eventKey);
});
const isOpen = currentEventKey === eventKey;
return (
<div className={classNames("card-header", { open: isOpen })} onClick={onClickToggle}>
<h2>{title}</h2>
{!!anchors.length && <i className={classNames("fa", { "fa-angle-down": !isOpen }, { "fa-angle-up": isOpen })} />}
</div>
);
};
CustomToggle.propTypes = {
title: PropTypes.string.isRequired,
eventKey: PropTypes.string.isRequired,
anchors: PropTypes.array,
callback: PropTypes.func,
};
CustomToggle.defaultProps = {
anchors: [],
callback: () => null,
};
const DocsNavbar = ({ siteTitle, location }) => {
const [open, setOpen] = useState(false);
const [menu, setMenu] = useState(EduMenus.default);
const menuItems = [
{
href: "/education/overview",
text: "Education",
},
{
href: "/home",
text: "Business",
},
{
href: "/home",
text: "Travel",
},
{
href: "/home",
text: "Healthcare",
},
];
useEffect(() => {
if (!!location && location.search !== "") {
const params = queryString.parse(location.search);
if (params.menu) {
if (Object.prototype.hasOwnProperty.call(EduMenus, params.menu)) {
setMenu(EduMenus[params.menu]);
} else {
console.error(`Menu named '${params.menu}' does not exist`);
}
}
}
});
return (
<>
<MenuMobile menuItems={menuItems} open={open} setOpen={setOpen} />
<Navbar bg="white" variant="light" expand="lg" fixed="left">
<Navbar.Brand href="/home">
<img src={logo} width="113" height="40" alt={siteTitle + " Logo"} />
</Navbar.Brand>
<NavbarToggler open={open} setOpen={setOpen} />
<Accordion
defaultActiveKey={menu.defaultActiveKey}
className="sidebar-menu"
data-active={menu.defaultActiveKey}
>
{menu.cards.map((card, index) => {
return (
<Card key={index}>
<CustomToggle title={card.title} eventKey={card.eventKey} anchors={card.anchors} />
<Accordion.Collapse eventKey={card.eventKey}>
<Card.Body>
{card.anchors.map((anchor) => (
<a href={`#${anchor.href}`} key={anchor.href}>
{anchor.text}
</a>
))}
</Card.Body>
</Accordion.Collapse>
</Card>
);
})}
</Accordion>
</Navbar>
</>
);
};
DocsNavbar.propTypes = {
siteTitle: PropTypes.string,
location: PropTypes.object,
};
DocsNavbar.defaultProps = {
siteTitle: ``,
};
export default DocsNavbar;
Ok, so I came up with a workaround. Not sure if this is the best way but it works so I'll put it out there but if someone has a better solution I'm all ears.
Basically, I removed defaultActiveKey because it seems like it only works on initial render, and explicitly set the active accordion with activeKey and maintain that in state and set that state whenever the menu changes.
import React, { useState, useContext, useEffect } from "react";
import PropTypes from "prop-types";
import Navbar from "react-bootstrap/Navbar";
import AccordionContext from "react-bootstrap/AccordionContext";
import Accordion from "react-bootstrap/Accordion";
import Card from "react-bootstrap/Card";
import { useAccordionToggle } from "react-bootstrap/AccordionToggle";
import classNames from "classnames";
import queryString from "query-string";
import MenuMobile from "./menuMobile";
import NavbarToggler from "./navbarToggler";
import EduMenus from "../utility/educationMenus";
import logo from "../images/logo-white.svg";
const CustomToggle = ({ title, eventKey, anchors, callback }) => {
const currentEventKey = useContext(AccordionContext);
const onClickToggle = useAccordionToggle(eventKey, () => {
callback(eventKey);
});
const isOpen = currentEventKey === eventKey;
return (
<div className={classNames("card-header", { open: isOpen })} onClick={onClickToggle}>
<h2>{title}</h2>
{!!anchors.length && <i className={classNames("fa", { "fa-angle-down": !isOpen }, { "fa-angle-up": isOpen })} />}
</div>
);
};
CustomToggle.propTypes = {
title: PropTypes.string.isRequired,
eventKey: PropTypes.string.isRequired,
anchors: PropTypes.array,
callback: PropTypes.func,
};
CustomToggle.defaultProps = {
anchors: [],
callback: () => null,
};
const DocsNavbar = ({ siteTitle, location }) => {
const [open, setOpen] = useState(false);
const [menu, setMenu] = useState(EduMenus.default);
const [active, setActive] = useState(EduMenus.default.defaultActiveKey);
const menuItems = [
{
href: "/education/overview",
text: "Education",
},
{
href: "/home",
text: "Business",
},
{
href: "/home",
text: "Travel",
},
{
href: "/home",
text: "Healthcare",
},
];
useEffect(() => {
if (!!location && location.search !== "") {
const params = queryString.parse(location.search);
if (params.menu) {
if (Object.prototype.hasOwnProperty.call(EduMenus, params.menu)) {
setMenu(EduMenus[params.menu]);
setActive(EduMenus[params.menu].defaultActiveKey);
} else {
console.error(`Menu named '${params.menu}' does not exist`);
}
}
}
});
return (
<>
<MenuMobile menuItems={menuItems} open={open} setOpen={setOpen} />
<Navbar bg="white" variant="light" expand="lg" fixed="left">
<Navbar.Brand href="/home">
<img src={logo} width="113" height="40" alt={siteTitle + " Logo"} />
</Navbar.Brand>
<NavbarToggler open={open} setOpen={setOpen} />
<Accordion activeKey={active} className="sidebar-menu" onSelect={(e) => setActive(e)}>
{menu.cards.map((card, index) => {
return (
<Card key={index}>
<CustomToggle title={card.title} eventKey={card.eventKey} anchors={card.anchors} />
<Accordion.Collapse eventKey={card.eventKey}>
<Card.Body>
{card.anchors.map((anchor) => (
<a href={`#${anchor.href}`} key={anchor.href}>
{anchor.text}
</a>
))}
</Card.Body>
</Accordion.Collapse>
</Card>
);
})}
</Accordion>
</Navbar>
</>
);
};
DocsNavbar.propTypes = {
siteTitle: PropTypes.string,
location: PropTypes.object,
};
DocsNavbar.defaultProps = {
siteTitle: ``,
};
export default DocsNavbar;
I am using react-admin framework and I am trying to put the <Edit> component inside <Drawer> component. I need this in order to be able to save changes done in my JsonInput.
This is my custom component:
import React, { Component, Fragment } from 'react';
import { fetchUtils, CardActions, EditButton, Button, List, Datagrid, Edit } from 'react-admin';
import Drawer from '#material-ui/core/Drawer';
import JsonInput from 'react-json-view';
import EditIcon from '#material-ui/icons/Edit';
import IconKeyboardArrowRight from '#material-ui/icons/KeyboardArrowRight';
import { SimpleForm } from 'ra-ui-materialui/lib/form';
const divStyle = {
width: '400px',
margin: '1em'
};
export default class JsonEditButton extends React.Component {
constructor(props, context) {
super(props, context);
this.state = { showPanel: false };
}
handleClick = () => {
this.setState({ showPanel: true });
};
handleCloseClick = () => {
this.setState({ showPanel: false });
};
onChange = (value) => {
const { updated_src, name, namespace, new_value, existing_value } = value;
//this.onChange(updated_src);
}
render() {
const { showPanel } = this.state;
return (
<div>
<Button label="Upravit JSON" onClick={this.handleClick}>
<EditIcon />
</Button>
<Fragment>
<Drawer
anchor="right"
open={showPanel}
onClose={this.handleCloseClick}
>
<div>
<Button label="Zavřít" onClick={this.handleCloseClick}>
<IconKeyboardArrowRight />
</Button>
</div>
<div style={divStyle}>
{props => (
<Edit {...props}>
<SimpleForm>
{this.props.record && <JsonInput src={this.props.record} name={null} displayDataTypes={false} displayObjectSize={false} onEdit={this.onChange} onAdd={this.onChange} onDelete={this.onChange} />}
</SimpleForm>
</Edit>
)}
</div>
</Drawer>
</Fragment>
</div>
);
}
};
However this doesnt return any HTML.
Any idea how to solve this?
Thank you in advance.
Maybe this may help
<div style={divStyle}>
<Edit {...this.props}>
<SimpleForm>
{this.props.record && <JsonInput src={this.props.record} name={null} displayDataTypes={false} displayObjectSize={false} onEdit={this.onChange} onAdd={this.onChange} onDelete={this.onChange} />}
</SimpleForm>
</Edit>
</div>
the previous code where {props => ()} is actually a function or you could try this
<div style={divStyle}>
{props => (
return(
<Edit {...props}>
<SimpleForm>
{this.props.record && <JsonInput src={this.props.record} name={null} displayDataTypes={false} displayObjectSize={false} onEdit={this.onChange} onAdd={this.onChange} onDelete={this.onChange} />}
</SimpleForm>
</Edit>)
)}
</div>
I need to build a drop down menu in a modal popup window with using Material UI
I have drop down menu in my modal window, and as a value I see the last item is "Third" and when I want to select for instance "First" it doesn't work, the menu doesn't select it and still having the last one item as a value in menu
I have 2 files App.js and list.js
App.js code:
import React, { Component } from 'react';
import './App.css';
import {AppButtons} from './components/button'
import {AppList} from './components/list'
import Dialog from 'material-ui/Dialog'
import FlatButton from 'material-ui/FlatButton'
export default class App extends Component {
constructor (props) {
super (props)
this.state = {
isModalOpen: false,
itemsList: [
{name: 'First'},
{name: 'Second'},
{name: 'Third'}
],
}
this.handleChange = this.handleChange.bind(this)
}
handleChange = () => this.setState({this.state.itemsList});
render() {
const actions = [
<FlatButton
label="Save"
primary={true}
onClick={() => this.setState({isModalOpen: false})}
/>,
<FlatButton
label="Cancel"
primary={true}
onClick={() => this.setState({isModalOpen: false})}
/>,
];
return (
<div className="container">
<AppButtons
openModal = {() => this.setState ({isModalOpen: true})}
/>
<Dialog
title="Numbers structure"
actions={actions}
open={this.state.isModalOpen}
onRequestClose={() => this.setState({isModalOpen: true})}
>
<AppList
items = {this.state.itemsList}
/>
</Dialog>
</div>
);
}
}
And list.js code:
import React from 'react'
import DropDownMenu from 'material-ui/DropDownMenu';
import MenuItem from 'material-ui/MenuItem';
const styles = {
customWidth: {
width: 200,
},
};
export const AppList = (props) => {
return (
<div>
<DropDownMenu
style={styles.customWidth}
onChange={this.handleChange}
>
{props.items.map((item, key) => {
return (
<MenuItem
primaryText = {item.name}
openImmediately={true}
autoWidth={false}/>
)
})
}
</DropDownMenu>
<br />
</div>
)
}
Here is the shot
It's because you aren't passing down your handleChange function as a prop to <AppList/>:
<AppList
items = {this.state.itemsList}
/>
Change it to:
<AppList
items = {this.state.itemsList}
handleChange={this.handleChange}
/>
And then in your AppList component, the Dropdown component needs to use this.props.handleChange instead of this.handleChange:
<DropDownMenu
style={styles.customWidth}
onChange={this.props.handleChange}
>
...
</DropDownMenu>