i have to check the current Pathname of my website to set the Steps for my React-Joyride guided Tour.
i have a Parent Component where the routes are defined and in which the Joyride tour is implemented.
the Tutorial Component implements step Objects to set the steps for the tour like this:
import Tutorial from './tutorial/tutorial'
class App extends Component {
constructor (props) {
super(props)
this.state= {
// state things
}
}
render () {
return (
<BrowserRouter>
<Tutorial
run={this.state.run}
stepIndex={this.state.stepIndex}
firstPartClicked={this.state.firstPartClicked}
secondPartClicked={this.state.secondPartClicked}
handleRestart={this.handleRestart}
handleEnd={this.handleEnd}
handleSteps={this.handleSteps}
handleFirstPart={this.handleFirstPart}
handleSecondPart={this.handleSecondPart}
handleClickedFalse={this.handleClickedFalse}
handleSetVisited={this.handleSetVisited}
handleCheckTourDone={this.handleCheckTourDone}
/>
// many other Routes
<Route
exact path='/matches/:matchId/' render={(props) => {
this.removeGlobalTimeRef()
const matchId = this.props.matchId
return this.checkLoginThen(this.gotoMatchDetails(props))
}}
/>
</BrowserRouter>
)
}
}
export default App
import matchSteps from '../tutorial/steps/matchSteps'
class Tutorial extends React.Component {
constructor (props) {
super(props)
this.state = {
isUpdatet: false,
steps: []
}
}
callback = (tour) => {
const { action, index, type, status } = tour
if ([STATUS.FINISHED].includes(status)) {
this.props.handleEnd()
this.props.handleClickedFalse()
if (this.props.location.pathname.startsWith('/matches/') && this.props.location.pathname.includes('sequence')) {
this.props.handleSetVisited()
}
} else if ([STATUS.SKIPPED].includes(status)) {
this.props.handleEnd()
this.props.handleClickedFalse()
this.props.handleSetVisited()
} else if (action === 'close') {
this.props.handleEnd()
this.props.handleClickedFalse()
} else if ([EVENTS.STEP_AFTER, EVENTS.TARGET_NOT_FOUND].includes(type)) {
const step = index + (action === ACTIONS.PREV ? -1 : 1)
this.props.handleSteps(step)
}
}
render () {
let { steps } = this.state
const pathname = this.props.location.pathname
const siteSteps = [matchSteps, matchEditorSteps, matchEditorStepsOne, matchEditorStepsTwo,
matchSequenceSteps, matchSequenceStepsOne, matchSequenceStepsTwo, matchSiteSteps, matchSiteStepsOne, matchSiteStepsTwo]
for (let i = 0; i < siteSteps.length; i++) {
if (pathname === siteSteps[i].onSite && siteSteps[i].part === 'full') {
steps = siteSteps[i].steps
} else if (pathname === siteSteps[i].onSite && siteSteps[i].part === 'one') {
if (this.props.firstPartClicked === true) {
steps = siteSteps[i].steps
}
} else if (pathname === siteSteps[i].onSite && siteSteps[i].part === 'two') {
if (this.props.secondPartClicked === true) {
steps = siteSteps[i].steps
}
}
}
}
return (
<>
<Joyride
callback={this.callback}
run={this.props.run}
stepIndex={this.props.stepIndex}
steps={steps}
continuous
disableOverlayClose
spotlightClicks
showSkipButton
locale={{
back: <span>Zurück</span>,
last: (<span>Beenden</span>),
next: (<span>Weiter</span>)
}}
styles={{
options: {
primaryColor: '#2d98da'
}
}}
/>
</>
)
}
export default withRouter(Tutorial)
const matchSteps = {
name: 'matchSteps',
// onSite: '/matches/:matchId/', <-- HERE
part: 'full',
steps: [{
target: '.row',
title: '',
content: 'bla bla',
placement: 'center',
disableBeacon: true
}]
export default matchSteps
now i have to set the matchId in the step Object at onSite: '/matches/:matchId/' so i can check the pathname in the Tutorial Component.
i dont know how to do it correctly i've tested some ideas but the matchId was always undefined.
You can use react router hooks useParams to get the parameters of the current url
import { useParams } from "react-router-dom";
let { slug } = useParams();
or use react router hooks useLocation to get the current url as location object
import { useLocation } from "react-router-dom";
let location = useLocation();
Your question isn't clear to me. Well the question is clear but the code snippets are not.
There are only two data flows in javascript. Data flows down from parent to child via properties. Data can be passed back to a parent via a callback. You can chain that callback from child -> parent -> grandparent.
class grandparent extends Component {
getDecentData = (data) => { do something with data}
render(){
<Parent CB = {this.getDecentData}/>
}
}
class Parent extends Component {
render(){
<Child CB = {this.props.CB} />
}
}
class Child extends Component {
clickHandler=(e) => {
// do something to get data
this.props.CB(data)
}
render(){
<Control onClick={this.clickHandler} />
}
}
Related
Per documentation, Hooks cannot be used inside class components. But there are ways with higher order components: How can I use React hooks in React classic `class` component?. However this answer provided does not address the case of hooks that get called on function invocation. Take this simple Toast hook from: https://jossmac.github.io/react-toast-notifications/. I'd like to call the hook inside of a class of form:
```
class MyClass extends React.Component {
onTapButton = () => {
if(conditionTrue){
addToast('hello world', {
appearance: 'error',
autoDismiss: true,
})
}
}
render(){ ... }
}
```
There'd be no way of calling addToast without using const { addToast } = useToasts() in the class method, which would throw error.
You can use withToastManager HOC to archive that work
Here is an example
import React, { Component } from 'react';
import { withToastManager } from 'react-toast-notifications';
class ConnectivityListener extends Component {
state = { isOnline: window ? window.navigator.onLine : false };
// NOTE: add/remove event listeners omitted for brevity
onlineCallback = () => {
this.props.toastManager.remove(this.offlineToastId);
this.offlineToastId = null;
};
offlineCallback = id => {
this.offlineToastId = id;
}
getSnapshotBeforeUpdate(prevProps, prevState) {
const { isOnline } = this.state;
if (prevState.isOnline !== isOnline) {
return { isOnline };
}
return null;
}
componentDidUpdate(props, state, snapshot) {
if (!snapshot) return;
const { toastManager } = props;
const { isOnline } = snapshot;
const content = (
<div>
<strong>{isOnline ? 'Online' : "Offline"}</strong>
<div>
{isOnline
? 'Editing is available again'
: 'Changes you make may not be saved'}
</div>
</div>
);
const callback = isOnline
? this.onlineCallback
: this.offlineCallback;
toastManager.add(content, {
appearance: 'info',
autoDismiss: isOnline,
}, callback);
}
render() {
return null;
}
}
export default withToastManager(ConnectivityListener);
For more information you can also find here
I'm newbie in React and trying to build a sample search filter with data from API. Unfortunately I have problem with this code.
It's get me an error ,,Cannot read property 'filter' of undefined".
It seems to me like child component doesn't get props from parent but I declared and imported this in code.
I've tried everything what I found on the internet but nothing helps. Can someone help me out with understanding what I made wrong?
Child
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import Data from './Data';
class App extends Component {
constructor() {
super();
this.state = {
search : " "
};
}
updatedSearch(event) {
this.setState(
{search : event.target.value.substr(0,15)}
)
}
render () {
console.log(this.props.names)
let filterednames = this.props.names.filter(
(name) => {
return name.toLowerCase().indexOf(this.state.
search.toLowerCase()) !== -1;
}
);
return (
<div className = "App">
<h1> Users list </h1>
<Data />
<input type = "text"
placeholder = "Search by user name"
value = {this.state.search}
onChange = {this.updatedSearch.bind(this)}
/>
<ol>
{filterednames.map(name => (
<li key={name}>{name}</li>
))}
</ol>
</div>
)
}
}
ReactDOM.render(<App/>,document.getElementById('root'));
export default App;
Parent
import React, { Component } from 'react';
import App from './index';
class Data extends Component {
constructor(props) {
super(props);
this.state = {
names : [],
}
}
componentDidMount() {
fetch('https://jsonplaceholder.typicode.com/users')
//Response
.then(response => response.json())
.then(output => {
let data = output;
//names in array
let listaimion = [];
for (let index = 0; index < data.length; index++) {
listaimion.push(data[index].name)
}
this.setState({names : listaimion})
})
}
render () {
return (
<div className = "Data">
<App names = {this.state.names} />
</div>
)
}
}
export default Data;
In the parent component, App needs to be declared. Also, App looks like your entry point of your application. Seems like, you might have mixed up Child and Parent here.
Parent -
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import Data from './Data';
class App extends Component() {
constructor() {
this.state = {
names : [],
}
}
componentDidMount() {
fetch('https://jsonplaceholder.typicode.com/users')
//Response
.then(response => response.json())
.then(output => {
let data = output;
let listaimion = [];
for (let index = 0; index < data.length; index++) {
listaimion.push(data[index].name)
}
this.setState({names : listaimion});
});
}
render () {
return (
<div className = "Data">
<Data names = {this.state.names} />
</div>
)
}
}
ReactDOM.render(<App/>,document.getElementById('root'));
export default App;
Child
import React, { Component } from 'react';
class Data extends Component {
constructor(props) {
super(props);
}
render() {
let filterednames = this.props.names.filter((name) => {
return name.toLowerCase().indexOf(this.state.
search.toLowerCase()) !== -1;
}
);
return (<div>{filterednames.join(',')}</div>)
}
}
The <App> component should be the parent - that is where your state should live. You would then pass this.state.names from <App> to <Data> inside the App render method. You should not import App inside Data - App should render Data.
// App.js
class App extends Component {
state = {
names: []
}
componentDidMount(){
// fetch data and when it's done use this.setState({ names: data })
}
render() {
return <Data names={this.state.names}/>
}
}
// Data.js
const Data = (props) => {
return props.names.map(() => {...your map function})
}
I'm a bit new to React and Firestore and already trying to figure out what is happening for a couple of hours. I Try to make my filter function working with data which I receive from Firestore in APP.js. I pass the data {tasks, searchTerm} to DASHBOARD component. The filter worked before when using state and props, but after replacing the hard-coded data in state with firestore data, it doesn't work anymore and I get the following error when filtering the array in the DASHBOARD component:
Cannot read property 'toLowerCase' of undefined
I've tried to send the data without any filtering directly to TASKS.js and this is working correctly (all the tasks are shown). But as soon as I pass newArray to , it doesn't work anymore.
Also, when logging task.title in tasks.filter function in the DASHBOARD component, it shows all the data (with a little delay because the data is coming from Firestore)
APP.JS -
import React, { Component } from 'react';
import './App.css';
import Dashboard from './Components/Dashboard/Dashboard'
import AddTask from './Components/Tasks/Task/AddTask'
import Navbar from './Components/Navbar/Navbar'
import Searchbar from './Components/Searchbar/Searchbar'
import firebase from './Firebase';
class App extends Component {
constructor(props) {
super(props)
this.ref = firebase.firestore().collection('tasks')
this.state = {
tasks: [],
searchTerm: ""
}
this.handleLikeButton = this.handleLikeButton.bind(this)
this.handleRemoveButton = this.handleRemoveButton.bind(this)
this.addTask = this.addTask.bind(this)
this.handleFilter = this.handleFilter.bind(this)
}
componentWillMount() {
const db = firebase.firestore()
const allTasks = []
db.collection('tasks').onSnapshot(collection => {
const tasks = collection .docs.map(doc => doc.data())
this.setState({ tasks: tasks, searchTerm: "" })
})
}
handleLikeButton = (task) => (e) => {
const tasks = [...this.state.tasks]
const index = tasks.indexOf(task)
tasks[index].likes++
this.setState({
tasks: tasks
})
}
addTask = (taskName) => (e) => {
this.ref.add({
id: Math.floor(Math.random() * 100000000000000),
title: taskName,
likes: 0
})
}
handleRemoveButton = (removingTask) => (e) => {
const tasks = [...this.state.tasks]
const newTasks = tasks.filter(task => removingTask.id !== task.id)
this.setState({
tasks: newTasks
})
}
handleFilter = (searchTerm) => {
this.setState({
searchTerm: searchTerm
})
}
render() {
return (
<div className="App">
<Navbar />
<Searchbar handleFilter={this.handleFilter} />
<AddTask addTask={this.addTask} />
<Dashboard tasks={this.state.tasks} searchTerm={this.state.searchTerm} handleLikeButton={this.handleLikeButton} handleRemoveButton={this.handleRemoveButton}/>
</div>
);
}
}
export default App;
DASHBOARD.JS -
import React, { Component } from 'react'
import Tasks from '../Tasks/Tasks'
class Dashboard extends Component {
constructor(props) {
super(props)
this.filterTasks = this.filterTasks.bind(this)
}
filterTasks = () => {
const tasks = [...this.props.tasks]
const newArray = tasks.filter(task =>
task.title.toLowerCase().indexOf(this.props.searchTerm.toLowerCase()) > -1)
return (
<Tasks tasks={newArray} handleLikeButton={this.props.handleLikeButton} handleRemoveButton={this.props.handleRemoveButton} />
)
}
render() {
return (
<div>
<h2>Dashboard</h2>
{this.filterTasks()}
</div>
)
}
}
export default Dashboard
ADDTASK.JS
import React, { Component } from 'react'
class AddTask extends Component {
constructor(props) {
super(props)
this.state = {
addNewTaskFieldEmpty: true,
taskName: ""
}
this.onChangeHandler = this.onChangeHandler.bind(this)
this.disableButton = this.disableButton.bind(this)
}
onChangeHandler(e) {
this.setState({
taskName: e.target.value,
})
this.disableButton(e.target.value)
}
disableButton(taskName) {
if(taskName.length == 0) {
this.setState({addNewTaskFieldEmpty: true})
} else {
this.setState({addNewTaskFieldEmpty: false})
}
}
render() {
return (
<div>
<div className="mdc-text-field half-size">
<input className="mdc-text-field__input " onChange={this.onChangeHandler} />
<div className="mdc-line-ripple"></div>
<label className="mdc-floating-label">Task Name</label>
</div>
<a className={"btn-floating btn-large waves-effect waves-light red " + (this.state.addNewTaskFieldEmpty ? 'disabled' : '')} onClick={this.props.addTask(this.state.taskName)}><i className="material-icons">add</i></a>
</div>
)
}
}
export default AddTask
Lint your App.css for any errors.
I encountered this message. I traced it to a CSS include:
.box-table { border-color:; border: 1px solid #dbdad8; }
The missing value of border-color: caused npm run build to fail.
Interestingly, the same file contained
.submenu-button.submenu-opened:after { background:; }
which caused no problems at all.
This is a reoccurring problem for me… Trying to figure out why an update to a single item in a component results in the entire component re-rendering. If I have a CSS fade in transition on the component, it fades in again when changing a child of the component.
I have a list of items, each with a link. Clicking the link adds the item to the cart. I have it set up to put that item in a “loading” state until the cart action is successful.
This used to work perfectly, but now it just re-renders the entire page, making it disappear for a second then reappear. I’m not entirely sure why.
This is the code stripped down to its basic bits:
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import autobind from 'class-autobind';
import Responsive from 'components/Responsive';
// Selectors
import { createStructuredSelector } from 'reselect';
import { selectCartLoading, selectCartMap, selectFavorites } from 'containers/App/selectors';
import { selectPackages } from 'store/fonts/selectors';
// Actions
import { addToCart } from 'containers/App/actions';
export class Packages extends React.Component {
constructor() {
super();
autobind(this);
}
state = {
loadingID: 0
}
componentWillReceiveProps(nextProps) {
if (this.props.cartLoading === true && nextProps.cartLoading === false) {
this.setState({ loadingID: 0 });
}
}
onAddToCart(e) {
e.preventDefault();
const { onAddToCart } = this.props;
const id = e.currentTarget.dataset.package;
const packageData = {
type: 'package',
id,
quantity: 1
};
onAddToCart(packageData);
this.setState({ loadingID: id });
}
render() {
const { cartMapping, packages } = this.props;
if (!packages) { return null; }
return (
<Responsive>
<div>
<ul>
{ packages.map((pack) => {
const inCart = !!cartMapping[parseInt(pack.id, 10)];
const isFavorited = !favorites ? false : !!find(favorites.favorites, (favorite) => parseInt(pack.id, 10) === favorite.items.id);
return (
<li key={ pack.id }>
<Icon iconName="heart" onClick={ (e) => this.onAddFavorite(e, pack) } />
<span>{ pack.name }</span>
{ inCart && <span>In Cart</span> }
{ !inCart && <a data-package={ pack.id } href="/" onClick={ this.onAddToCart }>Add to Cart</a> }
</li>
);
})}
</ul>
</div>
</Responsive>
);
}
}
Packages.propTypes = {
cartLoading: PropTypes.bool,
cartMapping: PropTypes.object,
onAddToCart: PropTypes.func.isRequired,
packages: PropTypes.array
};
Packages.defaultProps = {
cartLoading: null,
cartMapping: null,
packages: null
};
const mapStateToProps = createStructuredSelector({
cartLoading: selectCartLoading(),
cartMapping: selectCartMap(),
packages: selectPackages()
});
function mapDispatchToProps(dispatch) {
return {
onAddToCart: (data) => dispatch(addToCart(data)),
dispatch
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Packages);
So why does clicking on <a data-package={ pack.id } href="/" onClick={ this.onAddToCart }>Add to Cart</a> result in a complete component re-render?
In your onAddToCart function you are setting the state of the component which will by default trigger a re-render of the component. If you need to set the state but not cause a re-render you can add a shouldComponentUpdate() function and check the changes before issuing a re-render to the component.
Find out more about shouldComponentUpdate() and the rest of the component lifecycle here
I've got a basic admin app and I basically want to protect certain routes against the roles sent by the API when a user logs in via the Oauth2 protocol.
I have a route like...
<Route name="app" handler={App}>
<Route name="admin" path="/admin" roles={["admin", "superadmin"]} />
</Route>
Then I have an authentication component...
import React from 'react';
import SessionStore from '../stores/auth/SessionStore';
export default (ComposedComponent) => {
return class AuthenticatedComponent extends React.Component {
static willTransitionTo(transition) {
// If user isn't logged in, send 'em back to the login page
if (!SessionStore.isLoggedIn()) {
transition.redirect('/login', {}, {'nextPath' : transition.path});
} else if (this.rolesRequired) {
// Get all current users roles from session store.
let userRoles = SessionStore.roles;
// Iterate through user roles, if none match the required roles, send 'em away ta.
if (!this.rolesRequired.every(role => userRoles.indexOf(role) >= 0)) {
transition.redirect('/login', {}, { 'nextPath' : transition.path });
}
}
}
constructor(props) {
super(props);
this.state = this._getLoginState();
}
_getLoginState() {
return {
userLoggedIn: SessionStore.isLoggedIn(),
user: SessionStore.user,
token: SessionStore.token,
roles: SessionStore.roles
};
}
componentDidMount() {
this.changeListener = this._onChange.bind(this);
SessionStore.addChangeListener(this.changeListener);
}
_onChange() {
this.setState(this._getLoginState());
}
componentsWillUnmount() {
SessionStore.removeChangeListener(this.changeListener);
}
render() {
return (
<ComposedComponent
{...this.props}
user={this.state.user}
token={this.state.token}
roles={this.state.roles}
userLoggedIn={this.state.userLoggedIn} />
);
}
}
}
Then any components which need authenticating are passed into an instance of the AuthenticatedComponent, for example...
import React from 'react';
import RoleStore from '../../stores/user/RoleStore';
i
mport AdminActions from '../../actions/admin/AdminActions';
import AuthenticatedComponent from '../../components/AuthenticatedComponent';
import AdminMenu from '../../components/admin/AdminMenu';
import Template from '../template/BaseTemplate.react';
import RoleActions from '../../actions/user/RoleActions';
/**
* Admin
*
* #author Ewan Valentine
* #copyright 65twenty 2015
*/
export default AuthenticatedComponent(class Admin extends React.Component {
constructor() {
super();
this.state = {
users: [],
roles: []
};
this.onChange = this.onChange.bind(this);
}
onChange() {
this.setState({
roles: RoleStore.data,
users: UserListStore.data
});
}
render() {
return(
<Template>
<main>
<AdminMenu />
<h2>Admin Home</h2>
</main>
</Template>
);
}
});
I basically can't figure out the best approach for defining the required roles and there doesn't seem to be any way of accessing props on the Route component.
I had a similar issue, where I wanted to only show "Billing" link on top navbar if user belonged to 'admin' group.
I also had an Authentication component like you did, then I created an Authorization component, and several authorization policies depending on the required roles. Here is the code:
var Authorized = require('../auth/Authorized');
var AdminComponentPolicy = require('../auth/policies/AdminComponentPolicy');
<Authorized policy={AdminComponentPolicy} action="show" user= {this.props.user}>
...protected stuff
</Authorized>
Here is the code for the Authorized component:
//Authorized.jsx
import React from 'react';
var Authorized = React.createClass({
render: function() {
//checks if the informed policy returns true for given action and user.
if (this.props.policy.authorized(this.props.action, this.props.user)) {
return this.props.children;
} else {
return null;
}
}
});
module.exports = Authorized;
Here is the code for AdminComponentPolicy
//AdminComponentPolicy.js
class AdminComponentPolicy {
authorized(action, user) {
//return user.role === 'admin';
let _policies = {
//the 'show' action in this policy returns true for 'admin' users
show: function(record) {
return record.role === 'admin';
},
destroy: function(record) {
return this.show(record) || record.role === 'check if owner here';
},
};
return _policies[action](user);
}
}
export default new AdminComponentPolicy()