I'm trying to build an app with React and Redux (DVA). It's using Ant.Design as the main framework. I'm trying to change the URL when the user clicks on a button, and obviously 'bind' that url change to an action, so that if the user goes directly to that URL, he gets what he wants.
At the moment here's what I have, in a function in my component.
const { dispatch, match } = this.props;
dispatch(routerRedux.push('/f/' + record.id));
This is the only thing that I was able to produce. It correctly changes the url, but doesn't bind the url with a specific behaviour, making it completely useless.
How do I link the URL with an action?
If you wish to trigger an action based on a URL, you'll need to use react-router to route a component that then performs the desired action. In such a case it is also a good idea to then visit a different URL, erasing the action-URL from the browser's history.
A typical router definition might look something like this (taken from react-router-redux's docs):
ReactDOM.render(
<Provider store={store}>
<ConnectedRouter history={history}>
<div>
<Route exact path="/" component={Home}/>
<Route path="/about" component={About}/>
<Route path="/success" component={Success}/>
</div>
</ConnectedRouter>
</Provider>,
document.getElementById('root')
)
So you wish to add a path /f/<record-id>. You can do this by adding a line like this:
<Route path="/f/:recordId" component={MyActionComponent}/>
Now you need to define a component, MyActionComponent, that will perform your action.
import { connect } from 'react-redux';
import { replace } from 'react-router-redux';
const mapDispatchToProps = (dispatch: Dispatch) => ({
visitNextLocation: () => dispatch(replace('/success')),
myAction: (recordId) => dispatch(myAction(recordId)),
});
const withDispatch = connect(null, mapDispatchToProps);
class MyActionComponent extends Component {
props: {
match: {
params: {
recordId: string,
}
},
redirectToLogin: () => void,
myAction: string => void,
};
componentWillMount() {
const recordId = this.props.match.params.recordId;
if (recordId) {
this.props.myAction(token);
this.props.visitNextLocation();
}
}
render() {
return null;
}
}
Note the use of replace instead of push. This means, when a user visits this URL their action will get performed and they'll end up on /success. But if they click the Back button, they won't then revisit this URL and run the action again.
I can't put the code on Codepen for privacy reasons. But here's an extract:
router.js
...
},
'/users': {
component: dynamicWrapper(app, ['rule'], () => import('../routes/users')),
},
'/f/:userID': {
component: dynamicWrapper(app, ['rule'], () => import('../routes/users')),
},
...
users.js (the main component that contains LeftPanel and RightPanel)
import React, { PureComponent } from 'react';
import { connect } from 'dva';
import { Row, Col, Card, List, Divider, Badge, Select, Radio, Input, Popover, Button, Table, Spin } from 'antd';
import RightPanel from './RightPanel';
import LeftPanel from './LeftPanel';
import { routerRedux, Route, Switch } from 'dva/router';
import 'font-awesome/css/font-awesome.min.css';
import FadeIn from 'react-fade-in';
#connect(({ rule, loading }) => ({rule, loading: loading.models.rule }))
export default class Users extends React.Component {
constructor(props) {
super(props)
this.state = {
selected_user: [],
defaultView: true,
isLoadingNow: false,
selectedRowKeys: [],
makeEmpty: false,
searchRes: []
}
}
selectRow = (record) => {
const { dispatch, match } = this.props;
dispatch(routerRedux.replace({ pathname: '/f/' + record.id }));
this.setState({isLoadingNow: true, selectedRowKeys: record.key})
setTimeout(() => {
this.setState({
isLoadingNow: false,
defaultView: false,
selected_user: record
})
}, 75)
}
componentDidMount() {
const { dispatch, match } = this.props;
dispatch({
type: 'rule/fetch'
});
if (match.params.userID == undefined) {
// do nothing
} else if (match.params.userID) {
var result = this.props.rule.data.list.filter(function( obj ) {
return obj.id == match.params.userID;
});
this.selectRow.bind(this, result[0])
}
}
render() {
const { selectedRowKeys } = this.state;
const rowSelection = {
selectedRowKeys,
type:"radio"
};
const { rule: { data }, loading } = this.props;
return (<div>
<LeftPanel
rowSelection={rowSelection}
dataSource={this.state.makeEmpty ? this.state.searchRes : this.props.rule.data.list}
selectRow={this.selectRow}
loadingStatus={loading}
/>
<RightPanel
selected_user={this.state.selected_user}
is_default={this.state.defaultView}
loading={this.state.isLoadingNow}
/>
</div>);
}
}
leftPanel.js (responsible for displaying the list of links, on which the user will click on one, which will do 2 things:
- change the url accordingly
- display specific data on RightPanel.js)
import React from 'react';
import { Table, Card } from 'antd';
import styles from './index.less';
// import 'font-awesome/css/font-awesome.min.css';
import { Row, Col, List, Divider, Badge, Select, Radio, Input, Popover, Button } from 'antd';
var moment = require('moment');
class LeftPanel extends React.Component {
constructor(props) {
super(props)
this.state = {
selected_row_index: undefined
}
}
handleChangeStyleOnSelectRow(index) {
this.setState({
selected_row_index: index
}, console.log(this.state.selected_row_index))
}
prettifyForTable(raw_data) {
var prettyRows = [];
raw_data.map((item,index) =>
prettyRows.push(
<div style={{"width": "100%"}}>
<Row style={{ "align-items": "center"}} type="flex" justify="space-between">
<Col span={10}>
<div style={{"font-size": "15px", "text-align": "center"}}>
{item.user_name} <i style={{"color": "rgba(0, 0, 0, 0.25)", "margin": "0 10px", "transform": "rotate(45deg)"}} className="fa fa-plane"> </i> {item.user_age}
<div style={{"font-size": "12px", "color": "grey"}}> {moment(item.user_color).format('HH:MM')} · {moment(item.user_order).format('HH:MM')} </div>
</div>
</Col>
<Col span={3}>
<div style={{"text-align": "right", "text-align": "center"}}>
{item.user_family}
</div>
</Col>
<Col span={6}>
<div style={{"text-align": "right", "text-align": "center"}}>
{moment(item.user_height).format('MMMM D')}
</div>
</Col>
<Col span={3}>
<div style={{"text-align": "center"}}>
{(item.status == "in_progress") ? <div> <Badge style={{"padding-right": "25px"}} status="processing"/></div> : <div style={{"text-align": "center"}}> <Badge style={{"padding-right": "25px"}} status="default"/></div>}
</div>
</Col>
</Row>
</div>
)
);
return prettyRows;
}
render() {
const stylesSelectedRow = { "background": "rgba(155,155,155,0.05)", "box-shadow": "0 0 5px 0 #4A90E2", "transform": "scale(1.01)"};
const { dataSource } = this.props;
return(
<div>
{dataSource &&
<Card bordered={false} loading={this.props.loadingStatus} className={styles.userRows} bodyStyle={{"padding": "0 15px"}}>
<List
size="small"
bordered={false}
dataSource={this.prettifyForTable(dataSource)}
renderItem={(item, index) => (<List.Item onClick={() => {this.state.selected_row_index == index ? null : this.props.selectRow(this.props.dataSource[index]); this.handleChangeStyleOnSelectRow(index)}} style={this.state.selected_row_index == index ? stylesSelectedRow : null} className={styles.userRows}>{item}</List.Item>)}
/>
</Card>
}
</div>
)
}
}
export default LeftPanel;
and finally RightPanel.js, that is reponsible for listening to the URL or a click on LeftPanel, and display data accordingly.
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import infopng from '../../../assets/info.svg';
import TabInformations from './TabInformations.js';
import TabFamily from './TabFamily.js';
import TabProblems from './TabProblems.js';
import { Tabs, Button, Spin, Icon, Table, Pagination, Card, Col, Row, Spinner, Badge } from 'antd';
const TabPane = Tabs.TabPane;
import 'font-awesome/css/font-awesome.min.css';
import WindowSizeListener from 'react-window-size-listener'
import FadeIn from 'react-fade-in';
export default class RightPanel extends Component {
render() {
if (this.props.loading) {
return(
<div>
<Spin
indicator={<Icon type="loading" style={{ fontSize: 24 }} spin />}
src="http://web.gndu.ac.in/DepartmentProfile/images/spinner.gif"
/>
</div>
);
} else if (this.props.is_default) {
return(
<div style={{"margin-top": "64px", "margin-right": "10px", "text-align": "center", "height": "90vh", "display": "flex", "align-items": "center", "justify-content": "center"}}>
<div>
<img src={infopng} style={{"height": "155px"}} />
<p style={{"color": "#8e8e8e"}}> select a user on the <br/> left-hand side... </p>
</div>
</div>
);
} else {
return (
<FadeIn>
<Card bodyStyle={{"padding": "0"}} style={{"background-color": "white", "height":"90vh", "padding": "20px", "box-shadow": "rgba(0, 21, 41, 0.1) 0px 0px 6px", "opacity": "1", "transition": "background 0.6s", "border-radius": "2px", "margin": "10px 10px 0 0px", "margin-top": "64px"}}>
<Tabs defaultActiveKey="1" style={{"text-align": "center", "padding": "0 15px"}}>
<TabPane tab="General" key="1">
<TabInformations
selected_user={this.props.selected_user}
/>
</TabPane>
<TabPane tab="Servicing" key="2">
<TabFamily
selected_user={this.props.selected_user}
/>
</TabPane>
<TabPane tab={<div>Defect(s)<Badge showZero count={0} style={{ backgroundColor: '#fff', color: '#999', boxShadow: '0 0 0 1px #d9d9d9 inset', "margin-left": "10px" }} /></div>} key="3">
<TabProblems />
</TabPane>
</Tabs>
</Card>
</FadeIn>
);
}
}
}
This code does the initial job pretty well: when the user clicks on a link on leftPanel.js, leftPanel.js calls the method selectRow in users.js, which in turn selects a row and display it on RightPanel.js.
My question: how to add to this the fact that it changes the URL whenever the user clicks? And obviously, if the user clicks on "go back" in Chrome, the data in RightPanel.js has to change accordingly.. ?
Related
UPDATE: I easily fixed the first issue by providing unique names for all the 'Rating' components, but the question about 2 x setState fixing props remains open.
I actually have two questions. The second one emerged from trying to solve the first one.
The initial problem was that 'this' in each of the onChange Rating's property in each of the SongScoring components pointed to the first instance of the SongScoring class instead of itself:
Parent code:
import React, { Component } from 'react';
import './Assess.css';
import { Accordion } from 'react-bootstrap';
import SongScoring from './SongScoring';
class Assess extends Component {
constructor(props) {
super(props);
const songs = [
{
track: "song1",
},
{
track: "song2",
},
{
track: "song3",
},
{
track: "song4",
},
]
this.state = {
songs: songs,
};
}
render() {
return (
<div className="root">
<Accordion defaultActiveKey="0">
{
this.state.songs.map((song, index) => (
<SongScoring song={song} key={index.toString()} index={index} />
))
}
</Accordion>
</div>
);
}
}
export default Assess;
Child code:
import React, { Component } from 'react';
import './SongScoring.css';
import { Accordion, Card, Container, Row } from 'react-bootstrap';
import ReactPlayer from "react-player";
import Rating from '#material-ui/lab/Rating';
import Box from '#material-ui/core/Box';
const labels = {
0.5: 'Unpleasant',
1: 'Bearable',
1.5: 'Bearable+',
2: 'Intriguing',
2.5: 'Intriguing+',
3: 'Ok',
3.5: 'Ok+',
4: 'Pleasant',
4.5: 'Pleasant+',
5: 'Excellent',
};
class SongScoring extends Component {
constructor(props) {
super(props);
this.state = {
song: props.song,
key: props.index.toString(),
score: props.song.score || 0,
hover: props.song.score || -1,
onChange: this.props.onChange,
onChangeActive: this.props.onChangeActive
}
}
render() {
return (
<>
<Card>
<Accordion.Toggle as={Card.Header} variant="link" eventKey={this.state.key}>
{this.state.key}
<Rating name="read-only" value={this.state.score} precision={0.5} readOnly />
</Accordion.Toggle>
<Accordion.Collapse eventKey={this.state.key}>
<Card.Body style={{ display: 'flex', alignItems: 'center', lineHeight: '1' }}>
<Container>
<Row className='scoring-row'>
<ReactPlayer
url="https://file-examples-com.github.io/uploads/2017/11/file_example_MP3_700KB.mp3"
width="400px"
height="50px"
playing={false}
controls={true}
style={{ outline: 'none' }}
/>
</Row>
<Row className='scoring-row'>
<Rating
name="song-rating"
value={this.state.score}
precision={0.5}
onChange={(event, newScore) => {
this.setState({
score: newScore
});
}}
onChangeActive={(event, newHover) => {
this.setState({
hover: newHover
});
}}
style={{ marginTop: '40px' }}
/>
</Row>
<Row className='scoring-row'>
{this.state.score !== 0 && <Box>{labels[this.state.hover !== -1 ? this.state.hover : this.state.score]}</Box>}
</Row>
</Container>
</Card.Body>
</Accordion.Collapse>
</Card>
</>
);
}
}
export default SongScoring;
After multiple iterations of trying to solve this problem by changing from arrow functions and binding, putting onChange and onChangeActive into the state etc. I finally decided to try to solve this problem by passing the onChange and onChangeActive functions from the parent to the child.
This is what I came up with (there is console.log instead of setState in onChange and onChangeActive, but what we care about is 'this' reference really):
Parent code:
import React, { Component } from 'react';
import './Assess.css';
import { Accordion } from 'react-bootstrap';
import SongScoring from './SongScoring';
class Assess extends Component {
constructor(props) {
super(props);
const songs = [
{
track: "song1",
},
{
track: "song2",
},
{
track: "song3",
},
{
track: "song4",
},
]
this.songScoring = [];
const onChange = [];
const onChangeActive = [];
for (let i = 0; i < songs.length; i++) {
this.songScoring.push(React.createRef());
onChange.push(function (event, newScore) {
console.log("onChange: ", this);
});
onChangeActive.push(function (event, newHover) {
console.log("onChangeActive: ", this);
});
}
this.state = {
songs: songs,
onChange: onChange,
onChangeActive: onChangeActive
};
}
componentDidMount() {
console.log(this.state.songScoring);
const onChange = [];
const onChangeActive = [];
for (let i = 0; i < this.state.songs.length; i++) {
onChange.push(this.state.onChange[i].bind(this.songScoring[i], 1));
onChangeActive.push(this.state.onChangeActive[i].bind(this.songScoring[i]));
}
this.setState({
onChange: onChange,
onChangeActive: onChangeActive
});//, () => this.setState({unicorn: 1}));
}
render() {
return (
<div className="root">
<Accordion defaultActiveKey="0">
{
this.state.songs.map((song, index) => (
<SongScoring song={song} ref={this.songScoring[index]} key={index.toString()} index={index} onChange={this.state.onChange[index]} onChangeActive={this.state.onChangeActive[index]} />
))
}
</Accordion>
</div>
);
}
}
export default Assess;
Child code:
import React, { Component } from 'react';
import './SongScoring.css';
import { Accordion, Card, Container, Row } from 'react-bootstrap';
import ReactPlayer from "react-player";
import Rating from '#material-ui/lab/Rating';
import Box from '#material-ui/core/Box';
const labels = {
0.5: 'Unpleasant',
1: 'Bearable',
1.5: 'Bearable+',
2: 'Intriguing',
2.5: 'Intriguing+',
3: 'Ok',
3.5: 'Ok+',
4: 'Pleasant',
4.5: 'Pleasant+',
5: 'Excellent',
};
class SongScoring extends Component {
constructor(props) {
super(props);
this.state = {
song: props.song,
key: props.index.toString(),
score: props.song.score || 0,
hover: props.song.score || -1,
onChange: this.props.onChange,
onChangeActive: this.props.onChangeActive
}
}
componentDidUpdate(prevProps, prevState){
if (prevProps.onChange !== this.state.onChange){
this.setState({
onChange: prevProps.onChange,
onChangeActive: prevProps.onChangeActive
})
}
}
render() {
return (
<>
<Card>
<Accordion.Toggle as={Card.Header} variant="link" eventKey={this.state.key}>
{this.state.key}
<Rating name="read-only" value={this.state.score} precision={0.5} readOnly />
</Accordion.Toggle>
<Accordion.Collapse eventKey={this.state.key}>
<Card.Body style={{ display: 'flex', alignItems: 'center', lineHeight: '1' }}>
<Container>
<Row className='scoring-row'>
<ReactPlayer
url="https://file-examples-com.github.io/uploads/2017/11/file_example_MP3_700KB.mp3"
width="400px"
height="50px"
playing={false}
controls={true}
style={{outline: 'none'}}
/>
</Row>
<Row className='scoring-row'>
<Rating
name="song-rating"
value={this.state.score}
precision={0.5}
onChange={this.state.onChange}
onChangeActive={this.state.onChangeActive}
style={{ marginTop: '40px' }}
/>
</Row>
<Row className='scoring-row'>
{this.state.score !== 0 && <Box>{labels[this.state.hover !== -1 ? this.state.hover : this.state.score]}</Box>}
</Row>
</Container>
</Card.Body>
</Accordion.Collapse>
</Card>
</>
);
}
}
export default SongScoring;
In this setting, console.log in onChange and onChangeActive output both 'undefined'.
Now, if I uncomment the fragment: '//, () => this.setState({unicorn: 1}));' (unicorn is a dummy, unused variable) in the parent, console.log of onChange and onChangeActive prints out nicely 'this' as the references for respective SongScoring components.
Therefore my questions are:
what's going on here, in both problems? Is it a bug of the 'Rating' component?
how to solve my initial problem more efficiently than by using the second approach with the uncommented fragment?
UPDATE: I easily fixed the first issue by providing unique names for all the 'Rating' components, but the question about 2 x setState fixing props remains open.
Slide in Material UI transition starts from the edge of the screen, is there any way we can limit the transaction inside card component.
I have posted the code below to emphasize the issue. My objective is to ensure that the transition starts and ends within a component boundary
For example in the code below, I would like the transition to start and end within the card Material UI component
import React from 'react';
import Card from '#material-ui/core/Card';
import { IconButton, CardContent, Slide, Paper } from "#material-ui/core";
import { Settings } from "#material-ui/icons"
import { withStyles } from '#material-ui/core/styles'
const styles = {
card: {
minWidth: 560,
},
cardContent: {
padding: 8,enter code here
display: 'flex',
justifyContent: 'flex-end'
},
smallIcon: {
width: 36,
height: 36,
padding: 0,
},
paper: {
width: "400px",
height: "320px",
zIndex: 1,
position: "absolute",
backgroundColor: "#000000",
top: "20%",
}
}
class SimpleCard extends React.Component {
// eslint-disable-next-line no-useless-constructor
constructor(props) {
super(props)
this.state = {
gearIconClick: ""
}
}
chartFilterSlider = (evt) => {
debugger
var clicked = evt.currentTarget.translate
console.log("clicked", clicked)
this.setState({
gearIconClick: clicked
})
}
render() {
const { classes, } = this.props
const { gearIconClick } = this.state
//console.log("gearIconClick",gearIconClick)
return (
<Card className={classes.card}>
<CardContent className={classes.cardContent} >
<IconButton className={classes.smallIcon} onClick={this.chartFilterSlider}>
<Settings />
</IconButton>
{gearIconClick ?
<Slide direction="left" in={true} mountOnEnter unmountOnExit>
<Paper elevation={4} className={classes.paper}>
hello
</Paper>
</Slide>
:
<div></div>
}
</CardContent>
{this.props.children}
</Card>
);
}
}
export default withStyles(styles)(SimpleCard)
Excepted :
Is there any way we can limit the transaction inside the card component.
Actual output:
Slider in Material UI comes from the edge of the screen
Add overflow: hidden to Card, it should do the job.
Also you can check the container prop of Slide component: https://mui.com/components/transitions/#slide-relative-to-a-container
Dear community I am currently writing a website and its user onboarding using the ProrgressiveMobileStepper using Material UI. You can find my website here:
https://konekto-eeioq6hwh.now.sh/first_use
Apart from the formatting being screwed up (for which I would also appreciate your help), I am mainly concerned that the next button does not work. I have structured that the several screens are conditionally rendered in index.js depending on the state and the handleNext function should be called from ProgressiveMobileStepper when the button is clicked. However, the screen does not update then.
index.js file:
import React from 'react';
import { Grid, Container } from '#material-ui/core';
import { Header } from '../Layout';
import logo from './android-icon-192x192.png';
import { withStyles } from '#material-ui/core/styles';
import ExplanationAboutProcess0 from './Explanation0';
import ExplanationAboutProcess1 from './ExplanationProcess1';
import ExplanationAboutProcess2 from './ExplanationProcess2';
import ExplanationAboutProcess3 from './ExplanationProcess3';
import ProgressiveMobileStepper from './ProgressiveMobileStepper';
const styles = theme => ({
container: {
alignItems: 'center',
border: 'black',
'border-width': 'medium',
'margin-top': '80px',
background: 'rgba(255, 255, 255, 0.8)',
'border-radius': '20px'
},
picture: { display: 'block', margin: '0 auto' },
box: { width: '230px' }
});
class FirstUse extends React.Component {
constructor(props) {
super(props);
this.classes = props.classes;
this.state = {
componentType: 'explanationaboutprocess0'
};
this.handleNext = this.handleNext.bind(this);
this.handleBack = this.handleBack.bind(this);
}
fun() {
//this.props.history.push('/settings');
}
handleNext(e) {
console.log(e);
console.log(this.componentType);
if (this.componentType == 'explanationaboutprocess0')
this.setState({ componentType: 'explanationaboutprocess1' });
}
handleBack(e) {
console.log(e);
if (this.componentType == 'explanationaboutprocess1')
this.setState({ componentType: 'explanationaboutprocess0' });
}
render() {
let component;
if (this.state.componentType == 'explanationaboutprocess0') {
component = (
<ExplanationAboutProcess0
handleNext={this.handleNext}
handleBack={this.handleBack}
/>
);
} else if (this.state.componentType == 'explanationaboutprocess1') {
component = (
<ExplanationAboutProcess1
handleComponentType={this.handleComponentType}
handleBack={this.handleBack}
/>
);
} else if (this.state.componentType == 'explanationaboutprocess2') {
component = (
<ExplanationAboutProcess2
handleComponentType={this.handleComponentType}
handleBack={this.handleBack}
/>
);
} else if (this.state.componentType == 'explanationaboutprocess3') {
component = (
<ExplanationAboutProcess3
handleComponentType={this.handleComponentType}
handleBack={this.handleBack}
/>
);
}
return (
<React.Fragment>
<Header title="Learn how to send SOS" />
<Grid
container
className={this.classes.container}
direction="column"
spacing={2}
>
<Grid item sm={12} className={this.classes.item}>
<img src={logo} alt="Logo" />
</Grid>
<Grid item sm={12} className={this.classes.item} />
<Container component="main" maxWidth="sm">
{component}
</Container>
</Grid>
<Grid item sm={12} className={this.classes.item}>
<ProgressiveMobileStepper />
</Grid>
</React.Fragment>
);
}
}
export default withStyles(styles)(FirstUse);
ProgressiveMobileStepper.js file:
import React from 'react';
import { makeStyles, useTheme } from '#material-ui/core/styles';
import MobileStepper from '#material-ui/core/MobileStepper';
import Button from '#material-ui/core/Button';
import KeyboardArrowLeft from '#material-ui/icons/KeyboardArrowLeft';
import KeyboardArrowRight from '#material-ui/icons/KeyboardArrowRight';
const useStyles = makeStyles({
root: {
maxWidth: 400,
flexGrow: 1
}
});
export default function ProgressMobileStepper(props) {
const classes = useStyles();
const theme = useTheme();
const [activeStep, setActiveStep] = React.useState(0);
return (
<MobileStepper
variant="progress"
steps={6}
position="static"
className={classes.root}
nextButton={
<Button
size="small"
onClick={props.handleNext}
disabled={activeStep === 5}
>
Next
{theme.direction === 'rtl' ? (
<KeyboardArrowLeft />
) : (
<KeyboardArrowRight />
)}
</Button>
}
backButton={
<Button
size="small"
onClick={props.handleBack}
disabled={activeStep === 0}
>
{theme.direction === 'rtl' ? (
<KeyboardArrowRight />
) : (
<KeyboardArrowLeft />
)}
Back }
</Button>
}
/>
);
}
Just as an example, you can see two of the screens, which should be conditionally rendered below:
Explanation0.js file:
import React from 'react';
import { Typography } from '#material-ui/core';
export default class ExplanationAboutProcess1 extends React.Component {
constructor(props) {
super(props);
this.classes = props.classes;
this.state = {};
}
render() {
return (
<React.Fragment>
<Typography>
Konnekto allows you to send an emergency request from you phone
without requiring a celluar connection. Please enter your personal
information.
</Typography>
</React.Fragment>
);
}
}
ExplanationProcess1.js file:
import React from 'react';
export default class ExplanationAboutProcess1 extends React.Component {
constructor(props) {
super(props);
this.classes = props.classes;
this.state = {};
}
render() {
return (
<React.Fragment>
<p>
To explain you how you would use the app in case of emergency, we
guide you through which questions we ask you:
<br />
0.1 Are you affected yourself?
<br />
0.2 How do you want to contact?
</p>
</React.Fragment>
);
}
}
I would really appreciate your help!
I have an application has been written with React + Redux and Antdesign. My application is a dashboard app. So I used the layout in Ant design https://ant.design/components/layout/
When I click on side menus, the active menu gets bold which is fine. But I need when I refresh the page, it checks and detect the route and bold related menu item.
I have a Sidebar component which is stateful. Inside it, in componentDidMount I call a function which will dispatch an action from mapDispatchToProps. The reducer changes the state. But in HTML codes, in defaultSelectedKeys, I can not set the number of active menus.
Sidebar.js component:
import React from 'react';
import { render } from 'react-dom';
import { connect } from 'react-redux'
import { Switch, BrowserRouter, Route, Link } from 'react-router-dom';
// antd
import { Layout, Breadcrumb, Menu, Icon } from 'antd';
const { Header, Content, Footer, Sider } = Layout;
// Helpers
import { Alert } from '../helpers/notifications';
// Components
import Home from '../components/Home';
// import Header from '../components/Header';
import NotFound from '../components/NotFound';
import PostsEditor from '../components/Posts/PostsEditor';
// Actions
import { setRouteActiveFlag } from '../actions/ui.action'
class Sidebar extends React.Component {
componentDidMount () {
const routes = {
'/' : 1,
'/posts' : 2,
'/logout' : 3
}
this.props.detectActiveRoute(setRouteActiveFlag({
routes:routes,
path:window.location.pathname
}))
}
render() {
const { selectedRoute } = this.props;
console.log(selectedRoute);
return (
<div>
<Layout>
<Sider
style={{
overflow: 'auto',
height: '100vh',
position: 'fixed',
left: 0,
}}
breakpoint="lg"
collapsedWidth="0"
onBreakpoint={broken => {
console.log(broken);
}}
onCollapse={(collapsed, type) => {
console.log(collapsed, type);
}}
>
<div className="logo" >
Logo <br/><br/><br/>
</div>
<Menu theme="dark" mode="inline" style={{ lineHeight: '64px' }} defaultSelectedKeys={[selectedRoute.toString() || '1']}>
<Menu.Item key="1">
<Link to="/" style={{ color:'#fff' }}>
<Icon type="user" />
<span className="nav-text">Home</span>
</Link>
</Menu.Item>
<Menu.Item key="2">
<Link to="/posts" style={{ color:'#fff' }}>
<Icon type="user" />
<span className="nav-text">Posts</span>
</Link>
</Menu.Item>
<Menu.Item key="3">
<a href="/logout" style={{ color:'#fff' }}>
<Icon type="user" />
<span className="nav-text">Logout</span>
</a>
</Menu.Item>
</Menu>
</Sider>
<Layout style={{ marginLeft: 200 }}>
<Content style={{ margin: '24px 16px 0', overflow: 'initial'}}>
<Breadcrumb style={{ margin: '0 0 20px 0' }}>
<Breadcrumb.Item>Home</Breadcrumb.Item>
<Breadcrumb.Item>List</Breadcrumb.Item>
<Breadcrumb.Item>App</Breadcrumb.Item>
</Breadcrumb>
<div style={{ padding: 24, background: '#fff', minHeight: 360 }}>
<Switch>
<Route path="/" exact component={Home} />
<Route path="/posts/:id?" component={PostsEditor} />
<Route component={NotFound}/>
</Switch>
<Alert stack={ { limit: 3 } } />
</div>
</Content>
<Footer style={{ textAlign: 'center' }}>Ant Design ©2018 Created by Ant UED</Footer>
</Layout>
</Layout>
</div>
);
}
}
const mapStateToProps = (state, ownProps) => {
return {
state: state,
props: ownProps,
selectedRoute:state.ui.selectedRoute || 1
}
}
const mapDispatchToProps = (dispatch, ownProps) => {
return {
detectActiveRoute: (obj) => dispatch(obj)
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(Sidebar)
ui.action.js
export const setRouteActiveFlag = (payload = 'global') => ({
type: actions.SET_ROUTE_ACTIVE_FLAG,
payload
});
ui.reducer.js
import { handleActions } from 'redux-actions';
import Immutable from 'seamless-immutable';
import * as actions from '../consts/action-types';
const initialState = Immutable({
requests: {},
selectedRoute:{}
});
export default handleActions({
[actions.SET_ROUTE_ACTIVE_FLAG]: (state, action) => {
if (action.payload.routes && action.payload.path && action.payload.routes[ action.payload.path ]) {
return state.set('selectedRoute', action.payload.routes[ action.payload.path ])
}else{
return state.set('selectedRoute', 1)
}
}
}, initialState);
Please help me find the best and simple practices.
There is no need to use redux, just use react-router to get current pathname and pass it to defaultSelectedKeys.
<Menu defaultSelectedKeys={[this.props.location.pathname]}>
...
.....
</Menu>
Look at this answer , if you don't know how to get pathname
The following answer assumes you are using hooks. I know that from your question you are not using hooks, but it could be useful for other people. This answer works not only when refreshing but also when pressing the back and forward buttons:
import React, { useState, useEffect } from 'react'
import { useHistory, useLocation } from 'react-router-dom'
import { Layout, Menu } from 'antd'
const { Sider } = Layout
const items = [
{ key: '1', label: 'Invoices', path: '/admin/invoices' },
{ key: '2', label: 'Service Details', path: '/admin/service-details' },
{ key: '3', label: 'Service Contract Details', path: '/admin/service-contract-details' },
{ key: '4', label: 'Cost Centers', path: '/admin/cost-centers' },
{ key: '5', label: 'Clients', path: '/admin/clients' },
{ key: '6', label: 'Vendors', path: '/admin/vendors' }
]
const Sidebar = () => {
const location = useLocation()
const history = useHistory()
const [selectedKey, setSelectedKey] = useState(items.find(_item => location.pathname.startsWith(_item.path)).key)
const onClickMenu = (item) => {
const clicked = items.find(_item => _item.key === item.key)
history.push(clicked.path)
}
useEffect(() => {
setSelectedKey(items.find(_item => location.pathname.startsWith(_item.path)).key)
}, [location])
return (
<Sider style={{ backgroundColor: 'white' }}>
<h3 style={{ paddingLeft: '1rem', paddingTop: '1rem', fontSize: '1.25rem', fontWeight: 'bold', minHeight: 64, margin: 0 }}>
Costek
</h3>
<Menu selectedKeys={[selectedKey]} mode='inline' onClick={onClickMenu}>
{items.map((item) => (
<Menu.Item key={item.key}>{item.label}</Menu.Item>
))}
</Menu>
</Sider>
)
}
export default Sidebar
Your sidebar will look as follows:
You can add any css in your menu by conditioning and adding a class just in this way.
<MenuItem className={ (this.props.location.pathname==='/yourRoute')? 'active' : '' } >
</MenuItem>
In case if you get any kind of undefined error then you can use the 'withRouter' HOC
in this way.
In your component where you want to get that location prop, you will first import
import {withRouter} from 'react-router-dom';
then you can export it in this way.
export default withRouter(YourComponent);
Final code can look somewhat similar to this
import React, {Fragment, Component} from 'react';
import {withRouter, Link } from 'react-router-dom';
class Menu extends Component {
render(){
const {pathname} = this.props.location;
return (
<Fragment>
<div id="sidebar-menu" className="sidebar-menu">
<ul>
<li className={(pathname==='/dashboard' || pathname==='/')?'active':''}>
<Link to="/dashboard">Dashboard</Link>
</li>
<li className={(pathname==='/properties')?'active':''}>
<Link to="/properties">Properties</Link>
</li>
</ul>
</div>
</Fragment>
);
}
}
export default withRouter(Menu);
I have an array data that rendered with map and wrapped with Material Ui ButtonBase component. I want to get the data name if button is clicked.
import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from 'material-ui/styles';
import Card, { CardContent } from 'material-ui/Card';
import Typography from 'material-ui/Typography';
import ButtonBase from "material-ui/ButtonBase";
const styles = theme => ({
card: {
textAlign: 'center',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
},
cardButton: {
display: "block",
textAlign: "initial",
}
});
class SimpleMediaCard extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [
{
name: 'Javascript',
image: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQGLecy3onT9rChTsw1BawUkuOxKE1r6yx_ViYXjqpBq8zsE_BI'
},
{
name: 'React',
image: 'https://lapraim.com/assets/partners/react-logo.png'
}
]
}
this.handleClick = this.handleClick.bind(this);
}
handleClick(evt) {
alert('Name : ');
}
render() {
const { classes } = this.props;
const { data } = this.state;
const { handleClick } = this;
return (
<div>
{data.map(function(item, i) {
return (
<div key={i}>
<ButtonBase className={classes.cardButton} onClick={handleClick}>
<Card className={classes.card}>
<img src={item.image} height='100px' alt={item.name}/>
<CardContent>
<Typography component="h2">
{item.name}
</Typography>
</CardContent>
</Card>
</ButtonBase>
</div>
)
})}
</div>
)
}
}
SimpleMediaCard.propTypes = {
classes: PropTypes.object.isRequired,
};
export default withStyles(styles)(SimpleMediaCard);
This is the demo code link
At code above if picture is clicked it will show an alert.
I want to show the data name on each clicked picture.
Can anyone help me to show data name every picture is clicked?
You mean something like this?
handleClick(item) {
alert('Name : ' + item.name);
}
<ButtonBase className={classes.cardButton} onClick={() => handleClick(item)}>
Just pass the item to the event handler.