this.props.history showing undefine in console in React JS - reactjs

In my react app I want to send state to the next path through the history.location.state and history.location.pathname
In my case, it has to push successfully and also showing in history but when I console.log(this.props.history) in the child page showing undefined.
MyComponent Code
import React, { Component } from 'react';
import { withRouter } from 'react-router-dom';
class MyComponent extends Component {
state = {
cart: {
1:{
icon: "URL"
id: 1
quantity: 1
title: "item1"
}
2:{
icon: "URL"
id: 2
quantity: 1
title: "item2"
}
}
}
submitHandler = (event) => {
event.preventDefault();
let data = {};
for (let key in this.state.cart) {
data[key] = this.state.cart[key]
}
console.log("data=",data);
this.props.history.push({
pathname: "/result",
state: { data: data }
});
}
render(){
return(
<div >
<button onClick={this.submitHandler}>CONTINUE</button>
</div>
)
}
}
export default withRouter(MyComponent);
Result Component
import React, { Component } from 'react';
class Result extends Component {
render() {
console.log("ss=",this.props.history);
return(<div>Result</div>)
}
export default Result;
In Console
Route
<Route path="/result" component={Result} />
As shown in the above img in history->location->state is push fine.
But when I console log to this.props showing undefined.
Also I already use withRouter hoc of react-router-dom in export
Suggest me a solution to this?

May be you are called wrong path. its working and the state variable available on this.props.location not a this.props.history;
Codesanbox example

Related

Redirect to the another component in class component ReactJS

How can I redirect to another component with following code?
I can not use useHistory() because I don't have functional component. Is there any similar way to fix this?
class Register extends Component {
state : {
userList:object[];
} = {
userList : [],
}
passwordEl= React.createRef<HTMLInputElement>()
confirmpasswordEl= React.createRef<HTMLInputElement>()
emailEl = React.createRef<HTMLInputElement>()
registerUser = () => {
const { userList } = this.state
let newUser = [...userList]
let emailValue = this.emailEl.current
let passwordValue = this.passwordEl.current
let confirmPassword = this.confirmpasswordEl.current
if (emailValue && passwordValue && confirmPassword) {
if (passwordValue.value === confirmPassword.value) {
newUser.push({
username: emailValue.value,
password: passwordValue.value
})
this.setState({userList: newUser})
alert('Succesfull Registration')
} else {
alert('Passwords Must Match')
}
}
}
These are my buttons :
<div id={'buttons'} className="buttons">
<Link to={'/login'} >
<button>Log In</button>
</Link>
<button onClick={() => this.registerUser()}>Register</button>
</div>
My imports :
import React, {Component,createRef} from 'react'
import {Link} from "react-router-dom";
import './Register.css'
import {withRouter} from 'react-router-dom'
You code should be wrapped with the router from the main top level component.
With that you should be able to take advantage of history within your props.
If you can't access it, wrap your export of the class with:
export default withRouter(Register);
Just trying to remember how to access the props in classes, but I believe it would be:
this.props.history
Then when you want to redirect you would just do:
this.props.history.push('/my/new/route');
UPDATE
import { History, withRouter } from 'react-router'; // or 'react-router-dom'
type MyProps = {
history: History
}
class Register extends Component<MyProps> {
}
export default withRouter(Register);

Stripe - how do I save card element in react?

I'm trying to save card details for use later.
I have generated the SetupIntent client secret
I'm trying to use confirm card setup.
I'm following the docs here for react.
The following line:
const cardElement = this.props.elements.getElement('card')
is throwing me this error:
TypeError: Cannot read property 'getElement' of undefined
Where am I going wrong? My code is below:
This is the relevant portion of the main component:
import React from "react";
import { Elements, StripeProvider } from "react-stripe-elements";
import SaveCardForm from "./SaveCardForm";
<StripeProvider
apiKey={process.env.REACT_APP_API_STRIPE_PUBLISH}
>
<Elements>
<SaveCardForm/>
</Elements>
</StripeProvider>
And this is the SaveCardForm component
import React, { Component } from "react";
import { Stripe, CardElement, injectStripe } from "react-stripe-elements";
import axios from "axios";
class SaveCardForm extends Component {
constructor(props) {
super(props);
this.submit = this.submit.bind(this);
}
submit = e => {
e.preventDefault()
const cardElement = this.props.elements.getElement('card');
axios.get(`${process.env.REACT_APP_API}/saveCardDetails`).then(res => {
console.log('res.data', res.data)
this.props.stripe.confirmCardSetup(res.data.client_secret, {
payment_method: {
card: cardElement,
},
}).then( confirmCardSetupRes => {
console.log('confirmCardSetupRes', confirmCardSetupRes)
})
})
}
render() {
return (
<div>
<CardElement />
<button onClick={this.submit}>
Bid For Tickets
</button>
</div>
);
}
}
export default injectStripe(SaveCardForm);
Given your components, there is no prop named elements passed into SaveCardForm. If it's access to CardElement you are after, use a ref which will give you a direct reference to that component e.g.
constructor(props) {
...
this.cardEl = React.createRef();
}
submit = e => {
...
const card = this.cardEl.current.<accessDomHere>;
this.props.stripe.confirmCardSetup(res.data.client_secret, {
payment_method: {
card
},
}).then(...)
}
render() {
...
<div>
<CardElement ref={this.cardEl} />
...
</div>
}
Switch out <accessDomHere> for whatever DOM query you need to perform to get the information you need. There may even be a React property or function you can access (I'm not familiar with the component).
I resolved this by updating to the latest version of react-stripe-elements.
There is an error in the versions before 5.1.0

React Navigation Component on redirect loses data when refreshes

I am using React 16.4.1 and React-Router-Dom 4.3.1. I have built a side bar react component using a web component that you pass the data into the web component and this renders the component.
Here is the side bar component.
import React from 'react';
import PropTypes from 'prop-types';
import ReactDOM from 'react-dom';
import { Redirect } from 'react-router-dom';
class Sidebar extends React.Component {
constructor(props, context) {
super(props, context);
this.mySideBarRef = React.createRef();
this.menuClick = this.menuClick.bind(this);
this.state = {
redirect: false,
redirectUrl: '/',
menuData: [
{
groupName: 'Group 1',
icon: 'Home',
children: [
{
title: 'Home',
icon: 'favorite',
url: '/',
},
{
title: 'Grp 1 Item 2',
children: [
{
title: 'Roles',
icon: 'Person',
url: '/roles',
},
{
title: 'About',
icon: 'gesture',
url: '/about',
},
{
title: 'Google',
icon: 'world',
url: 'http://www.google.co.uk',
},
],
},
],
},
{
groupName: 'Group 2',
icon: 'search',
children: [
{
title: 'Grp 2 Item 1',
icon: 'microsoft',
children: [
{
title: 'Grp 2 Item 1.1 Bing',
icon: '',
url: 'http://www.google.co.uk',
},
{
title: 'Grp 2 Item 1.2 Bing',
icon: '',
url: 'http://www.bing.com',
},
],
},
],
},
],
};
}
componentDidMount() {
let sideMenuContainer = this.mySideBarRef.current;
sideMenuContainer.sideMenuData = this.state.menuData;
sideMenuContainer.overrideCustomUrlEvent = true;
window.addEventListener('customUrlEvent', this.menuClick)
}
menuClick(e) {
console.log(e);
this.setState({
redirect: true,
redirectUrl: e.url
});
}
componentWillUnmount() {
this.mySideBarRef.current.removeEventListener('customUrlEvent', this.menuClick);
}
render() {
if(this.state.redirect) {
this.state.redirect = false;
return <Redirect to={this.state.redirectUrl} />;
}
if(this.state.menuData !== null) {
return(
<side-bar-component
ref={this.mySideBarRef}
collapsable>
</side-bar-component>
);
}
}
}
export default Sidebar;
When the side-bar component loads it renders the data correctly, as soon as you click on one of the links it does redirect the page but the data does not load, because the componentDidMount does not get triggered. Here is the app.js
// This component handles the App template used on every page.
import React from 'react';
import PropTypes from 'prop-types';
import { Route } from 'react-router-dom';
import Header from './common/header';
import {connect} from 'react-redux';
import HomePage from './home/home-page';
import RolesPage from './role/roles-page';
import AboutPage from './about/about-page';
import { ManageRolePage } from "./role/manage-role-page";
import Sidebar from "./common/sidebar"; //eslint-disable-line import/no-named-as-default
class App extends React.Component {
render() {
return (
<div>
<Header
loading={this.props.loading}
/>
<div>
<div className="demo-sidebar-container">
<Sidebar />
</div>
<div className="demo-content">
<Route exact path="/" component={HomePage}/>
<Route path="/roles" component={RolesPage}/>
<Route path="/role/:id" component={ManageRolePage}/>
<Route path="/role" component={ManageRolePage} exact />
<Route path="/about" component={AboutPage}/>
</div>
</div>
</div>
);
}
}
App.propTypes = {
loading: PropTypes.bool.isRequired,
match: PropTypes.object.isRequired
};
function mapStateToProps(state, ownProps) {
return {
loading: state.ajaxCallsInProgress > 0
};
}
export default connect(mapStateToProps)(App);
Being new to react I think I am missing a fundamental step, what I am trying to achieve is for the redirect to happen and also have the choice depending on business logic to either reload the data into the side menu or to retain the data as the clicked item will be expanded and would like to retain the side menu data. My example above has a hard-coded data-set but eventually I would have a service that would be parameterised. Any guidance or examples on what I am trying to achieve would be grateful.
**
After reading Adams comments I have made the following changes: I
lifted all the code into sidebar-container.js and now my sidebar.js
looks like this;
**
import React from 'react';
import PropTypes from 'prop-types';
import ReactDOM from 'react-dom';
import { Redirect } from 'react-router-dom';
const Sidebar = React.forwardRef((props, ref) => (
<dijits-ui-dds-sidebar
ref={ref}
collapsable>
</dijits-ui-dds-sidebar>
));
export default Sidebar;
**
My sidebar-container.js file now consists of all the logic for
controlling the side bar:
**
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as sideMenuActions from '../../actions/side-bar-actions';
import Sidebar from "./sidebar";
import sideMenu from "../../reducers/side-menu-reducer";
import {Redirect} from "react-router-dom";
class SidebarContainer extends React.Component {
constructor(props, context) {
super(props, context);
this.mySideBarRef = React.createRef();
this.menuClick = this.menuClick.bind(this)
this.state = {
redirect: false,
redirectUrl: '/',
sideMenu: Object.assign({}, this.props.sideMenu),
};
}
componentDidMount() {
let sideMenuContainer = this.mySideBarRef.current;
if(this.state.sideMenu.menuData !== undefined) {
sideMenuContainer.sideMenuData = this.state.sideMenu.menuData;
}
sideMenuContainer.overrideCustomUrlEvent = true;
window.addEventListener('customUrlEvent', this.menuClick);
}
componentWillReceiveProps(nextProps) {
if (nextProps.sideMenu !== this.state.sideMenu) {
let sideMenuContainer = this.mySideBarRef.current;
if(nextProps.sideMenu.menuData !== undefined) {
sideMenuContainer.sideMenuData = nextProps.sideMenu.menuData;
}
}
}
componentWillUnmount() {
this.mySideBarRef.current.addEventListener('customUrlEvent', this.menuClick);
}
menuClick(e) {
console.log(e);
this.setState({
redirect: true,
redirectUrl: e.url
});
}
render() {
if(this.state.redirect) {
this.setState({ redirect: false} );
return <Redirect to={this.state.redirectUrl} />;
}
return (
<div>
<Sidebar sideMenu={this.props.sideMenu} ref={this.mySideBarRef}/>
</div>
);
}
}
SidebarContainer.propTypes = {
sideMenu: PropTypes.object.isRequired
};
function mapStateToProps(state, ownProps) {
debugger;
return {
sideMenu: state.sideMenu,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(sideMenuActions, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps) (SidebarContainer);
**
And the sidebar containers sit's in my app.js.
**
import React from 'react';
import PropTypes from 'prop-types';
import { Route } from 'react-router-dom';
import Header from './common/header';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import HomePage from './home/home-page';
import RolesPage from './role/roles-page';
import AboutPage from './about/about-page';
import { ManageRolePage } from "./role/manage-role-page";
import SidebarContainer from "./common/sidebar-container"; //eslint-disable-line import/no-named-as-default
class App extends React.Component {
render() {
return (
<div>
<Header
loading={this.props.loading}
/>
<div>
<div className="sidebar-container">
<SidebarContainer sideMenu={this.props.sideMenu} />
</div>
<div className="content">
<Route exact path="/" component={HomePage}/>
<Route path="/roles" component={RolesPage}/>
<Route path="/role/:id" component={ManageRolePage}/>
<Route path="/role" component={ManageRolePage} exact />
<Route path="/about" component={AboutPage}/>
</div>
</div>
</div>
);
}
}
App.propTypes = {
loading: PropTypes.bool.isRequired,
sideMenu: PropTypes.object.isRequired,
match: PropTypes.object.isRequired
};
function mapStateToProps(state, ownProps) {
debugger;
return {
loading: state.ajaxCallsInProgress > 0,
sideMenu: state.sideMenu,
};
}
export default connect(mapStateToProps)(App);
**
But I am still getting the same issue of when I click on a items on
the side menu bar to route me to a different route the bar loses the
data. I know the reason why, but trying to find a solution to this.
The menuClick(e) function in the container set the redirect property
and the redirectUrl state values which refreshes the state and forces
the parent container to reload, I also in the render need to set the
state of the redirect back to false. Which is causing the parent to
refresh being the container and then forcing the sidebar to refresh. I
feel I am missing something or misunderstanding something within react
way of doing things. Also I have an action that executes to bring the
initial load of the side bar and it looks like the componentDidMount
was not setting the propType for the child component, so I added the
componentWillReceiveProps lifecycle method which picked up propType
changes. So does this mean that I do not need to have the following
line in the componentDidMount method?
**
if(this.state.sideMenu.menuData !== undefined) {
sideMenuContainer.sideMenuData = this.state.sideMenu.menuData;
}
**
Apologise for my slow response, but I look forward for your
assistance. Once again thank you community for your support.
**
Also include my routes.js in case this might be needed to help troubleshoot this issue.
import React from 'react';
import { Route, IndexRoute, Switch } from 'react-router-dom';
// import { Route, IndexRoute } from 'react-router';
import App from './components/app';
import HomePage from './components/home/home-page';
import AboutPage from './components/about/about-page';
import RolesPage from './components/role/roles-page';
import ManageRolePage from './components/role/manage-role-page'; //eslint-disable-line import/no-named-as-default
export default (
<Switch>
<Route path="/" components={App}>
<IndexRoute component={HomePage} />
<Route path="roles" component={RolesPage}/>
<Route path="role" component={ManageRolePage}/>
<Route path="role/:id" component={ManageRolePage}/>
<Route path="about" component={AboutPage}/>
</Route>
</Switch>
);
After a little more playing I changed the menuClick event to use a private variable rather then state variables for redirectUrl or for redirect flag.
menuClick(e) {
console.log(e);
this.redirectUrl = e.url;
this.redirect = true;
}
Which means that state is not being updated but I can retain the menu expand but page does not redirect because I am not hitting the render function event.
Components will re-render whenever their state or props change, meaning their render method will run again, and their relevant lifecycle methods.
The thing I notice the most about your code is that you are using the Route, but you are not using a Switch or BrowserRouter. This may be related. I can't tell for sure, but because your <SideBar> component is upstream from the Routes, it will simply not re-render or update unless its state or props change.
I would think your sidebar would just not update and the data may load initially but would become stale.
But never fear, allow me to link you an article that I think will bring you to the next level: https://medium.com/#pshrmn/a-simple-react-router-v4-tutorial-7f23ff27adf (note to future individuals: if that URL breaks in the future, let me know)
I think you will figure it out after reading that, but it will detail BrowserRouter, Switch, and Route. I think you need to change your code to use all three of those.
Remember that the Sidebar (and all of its children) will only update if it's state or props change.
You could do something like this:
class App extends Component {
constructor(props) {
super(props)
this.state = {
data: {},
}
}
render() {
return (
<div>
<Sidebar data={this.state.data} />
<AnotherComponent onEventOccurred={newData => this.setState({ data: newData })} />
</div>
)
}
}
In React, we call it lifting state, so the parent of your Sidebar would be the component that controls the state that the sidebar relies on. Inside the Sidebar, you could refer to that state as this.props.data (or if it was a stateless functional component, as props.data). The sidebar would re-render itself every time that prop changed.
The second part of that is shown by:
<AnotherComponent onEventOccurred={newData => this.setState({ data: newData })} />
That component has a prop called onEventOccurred which could be called from inside that component as this.props.onEventOccurred(newData) or if functional non-class component as props.onEventOccurred(). It's literally just a callback, so you call it and it fires this.setState() in its parent. It is an extremely powerful pattern because it allows the parent to control what it will do for that event because the action is defined in the parent. The child simply triggers the callback.
I was initially hesitant to post an answer, but I now feel like you might get a lot out of this. Let me know if anything specific is still unclear. I want you to read the whole Medium article I linked first. The pattern shown in it is very common and idiomatic.
I just noticed you have Redux installed. This is a definite increase in complexity overall, but it is good for cross-cutting concerns. Sometimes we call it sideways data loading. Normally, data flows unidirectionally in React, meaning always from parent to child, upstream to downstream.
With Redux, you would call an action creator from any of your components and your Sidebar would be listening to changes on the state Object. Redux is used when you want pieces of state to be shared across multiple components not directly connected to eachother.
With Redux, connected components are listening for changes on the 'basically' global state tree. When the state changes, components that are subscribed will update.
For example in your Sidebar, you could have this:
function mapStateToProps(state, ownProps) {
return {
loading: state.ajaxCallsInProgress > 0,
data: state.sidebar,
};
}
and it would update its this.props.data every time state.sidebar changed. I'm trying to keep this as minimal as possible because I may fantastically confuse you with extra info, but that is the most important part. Components using connect() are subscribed to changes on the global state tree.
Connect is a higher order component that wraps around the component using it, so when the app's state changes, it causes that component's props to change, which triggers a re-render.

React router v4 - How do you access the state that is passed via Redirect?

I am making a login page with the help of react that redirects to another page when the login is successful. Let me call the component that gets rendered when the login is successful 'A'. I want to pass data fetched from the database to component A and I am doing so by passing it in the 'state' attribute of the 'Redirect' component. However, I do not understand how to access this state in the 'Route' component that ultimately renders component A. Can anyone tell me how?
My code is as follows:
Login.js:
import React from 'react'
import Center from 'react-center'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import isEmpty from 'lodash/isEmpty'
import { browserHistory } from 'react-router'
import { Route, Redirect } from 'react-router-dom'
import Courses from '../pages/Courses'
import Logo from './shared/Logo'
import Routes from './Routes'
import Tiles from './Tiles'
export default class LoginForm extends React.Component
{
constructor()
{
super()
this.state =
{
username: '',
password: '',
student: false,
instructor: false,
error_password: '',
error_username: ''
}
this.onChange = this.onChange.bind(this)
this.onSubmit = this.onSubmit.bind(this)
}
onChange(event)
{
this.setState({error_username:'', error_password:'', [event.target.name]: event.target.value})
console.log(this.state)
event.preventDefault()
}
onSubmit(event)
{
event.preventDefault()
if (this.state.username && this.state.password)
{
this.props.loginRequest({username: this.state.username, password: this.state.password})
.then(response =>
{
this.setState( // THE SERVER WILL SEND THE RELEVANT DATA HERE
{
username: '',
password: '',
student: response.data.student,
error_username: response.data.error_username,
error_password: response.data.error_password
})
})
}
else
this.setState({error_username: 'Please enter username', error_password: 'Please enter password.'})
}
render()
{
const styles =
{
buttonStyle:
{
margin: 'auto',
width: '83'
}
}
if (this.state.student) /// REDIRECT IS HAPPENING HERE
{
return (
<Redirect to = {{
pathname: '/loginS/student',
state: { course_information } /// HERE I WILL SEND THE RELEVANT INFORMATION THAT THE SERVER SENDS TO THE COMPONENT A
}}/> )
}
else if (this.state.instructor)
{
return <Redirect to = {'/loginI/instructor'} />
}
else
{
// NOT RELEVANT
}
return(
display
)
}
}
LoginForm.propTypes =
{
loginRequest: PropTypes.func.isRequired
}
Routes.js
import React from 'react'
import { Switch, Route } from 'react-router-dom'
import Courses from '../pages/Courses'
import Login from './Login'
import Dashboard from './Dashboard'
import StudentsHandle from './StudentsHandle'
import CourseItem from './CourseItem'
import SemesterItem from './SemesterItem'
import Logo from './shared/Logo'
import Tiles from './Tiles'
export default class Routes extends React.Component
{
render()
{
return(
<Switch>
<Route exact path = '/' component = { Dashboard }/>
<Route exact path = '/loginS' component = { Login } />
<Route path = '/loginS/student' render = { (props) => < Tiles data = {this.props.coursesData} />} /> // HOW DO I ACCESS THE PROPS REDIRECT SENDS HERE?
<Route path = '/instructor' component = { Courses } />
</Switch>
);
}
}
You can use the location prop to access the state you have passed. Visit React-Router for reference. When you want to access that state, you can do it by this.props.location.state.
If you're using React hooks you can use useLocation hook.
import { useLocation } from 'react-router-dom';
export const MyComponent = () => {
const { state } = useLocation();
...
}
you can access state using this.props.location.state

React Router v4 params breaking inside unit test when mounting

I have a React component that has URL params. When I run the test and mount the component the params are always undefined and as a result, break the test. I have tried to hard code them as constants, props and it still won't work. Any other ideas I can try?
import React, { Component } from 'react';
class BarcodePage extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
const { SKU, ID } = this.props.match.params
}
render() {
return (
<h1>Barcode view {SKU} {ID}</h1>
);
}
}
export default BarcodePage;
import React from 'react';
import { shallow } from 'enzyme';
import BarcodePage from './BarcodePage';
const component = mount(
<BarcodePage params={{SKU: '1111', ID: '2121212' }} />
);
describe('<BarcodePage />', () => {
it('render one header', () => {
expect(component.find('h1').length).toBe(1);
});
})
React Router provides and your code uses this.props.match.params, not this.props.params. You're passing the wrong props to your unit test:
<BarcodePage params={{SKU: '1111', ID: '2121212' }} />
That gives you this.props.params, but it should be this.props.match.params:
<BarcodePage match={{params: {SKU: '1111', ID: '2121212' }}} />

Resources