I am using react-router, and I am trying to combine some routes/sub-routes.
I have a left menu bar with 2 options: main and secondary, if you click on main, then a menu with tabs should be open with 4 options, if you click on secondary, the same should happen but with 3 different options.
The left menu bar component
export default class LeftNavMenu extends React.Component {
static propTypes = {
getActivePage : React.PropTypes.func,
leftMenuItems : React.PropTypes.arrayOf(React.PropTypes.object),
}
static contextTypes = {
router : React.PropTypes.func,
}
render () {
return (
<LeftNav
ref="appNavbar"
docked={false}
menuItems={this.props.menuItems}
selectedIndex={this.props.getActivePage()}
onChange={this.props._onLeftNavChange} />
);
}
// Navigate to route when clicking on a Side Bar element.
_onLeftNavChange = (e, key, payload) => {
if (payload.route === 'main') {
console.log(payload.route);
}
this.context.router.transitionTo(payload.route);
}
}
here is the Tabs Component
export default class TabsMainMenu extends React.Component {
static propTypes = {
getActivePage : React.PropTypes.func,
menuItems : React.PropTypes.arrayOf(React.PropTypes.object),
}
static contextTypes = {
router : React.PropTypes.func,
}
render () {
const tabs = this.props.menuItems.map((item) => {
return (
<Tab
key={item.route}
label={item.text}
route={item.route}
onActive={this._onActive} />
);
});
return <Tabs initialSelectedIndex={this.props.getActivePage()}>{tabs}</Tabs>;
}
_onActive = tab => {
this.context.router.transitionTo(tab.props.route);
}
**and here the main component where I am calling those 2 components above
const menuItems = [
{ route : 'universal-search', text : 'Universal Search' },
{ route : 'game-info', text : 'Game Info' },
{ route : 'player-info', text : 'Players Info' },
{ route : 'money', text : 'Money' },
{ route : 'refunds', text : 'Refunds' },
{ route : 'videos', text : 'Videos' },
{ route : 'tips', text : 'Tips' },
], leftMenuItems = [
{ route : 'main', text : 'Main - Management' },
{ route : 'secondary', text : 'Secondary - Operations' },
];
export default class App extends React.Component {
static contextTypes = {
router : React.PropTypes.func,
}
render () {
return (
<LeftNavMenu ref="appNavbar" menuItems={leftMenuItems} getActivePage={this._getActivePage} />
<TabsMainMenu menuItems={menuItems} getActivePage={this._getActivePage} />
<RouteHandler />
);
}
// Toggle Side Bar.
_onLeftIconButtonTouchTap = () => {
this.refs.appNavbar.refs.appNavbar.toggle();
}
// Get the active page.
_getActivePage = () => {
for (const i in menuItems) {
if (this.context.router.isActive(menuItems[i].route)) return parseInt(i, 10);
}
}
_onChange = (event) => {
this.setState({value: event.target.value});
}
}
here are the routes
const Routes = (
<Route handler={Root}>
<Route name="app" path="/" handler={App}>
<Route name="main">
<Route name="game-info" path="game-info" handler={GameInfo} />
<Route name="player-info" path="player-info" handler={PlayerInfo} />
<Route name="money" path="money" handler={Money} />
<Route name="refunds" path="refunds" handler={Refunds} />
</Route>
<Route name="secondary">
<Route name="videos2" path="videos" handler={Videos} />
<Route name="tips2" path="tips" handler={Tips} />
<Route name="universal-search2" handler={UniversalSearch} />
</Route>
<DefaultRoute handler={UniversalSearch} />
</Route>
<Route name="login" handler={Login} />
</Route>
);
look at this in the first component I wrote above
// Navigate to route when clicking on a Side Bar element.
_onLeftNavChange = (e, key, payload) => {
if (payload.route === 'main') {
console.log(payload.route);
}
this.context.router.transitionTo(payload.route);
}
there is where I need to tell the app if payload.route === 'main' then display the tabs I need, but, in that component, what should I do to get the tabs from the tabs component?
so, what should I do in order to call the routes I need depending whether the user choose main or secondary on the left menu bar component?
It's hard for me to follow, so I can't write some sample code, but it sounds to me like you need a store to sit behind the tab components.
The store would definitely hold data useful to determine "current tab" (like tab index or route name).
It may also hold the full list of tabs. This is where I'm not sure, because the sample code seems complex, while obviously also just snippets of a bigger application...
Related
I am trying to share my props (data, saveWorkButtonClicked, updateFBRDB) from <ProjectPage /> component route to <Indent /> component route.
But getting the following error:
Uncaught DOMException: Failed to execute 'pushState' on 'History': async (data, setSpinner, updateFBRDB) => {
setSpinner && setSpinner(true);
let rawRoomData = String.raw`${J...<omitted>...
} could not be cloned.
App.js
<Router>
<Switch>
<Route path="/ProjectPage/:projectId" exact component={ProjectPage} />
<Route path="/Indent/" render={(props) => <Indent {...props} />} />
</Switch>
</Router>
ProjectPage.js
history.push("/Indent/",
{
data: { ...project, rooms: project.rooms, ProjectId: project.ProjectId, ClientName: project.ClientName, Address: project.Address, AmountRecieved: project.AmountReceived, SiteEngineerId: project.SiteEngineersId },
saveWorkButtonClicked,
updateFBRDB,
}
)
// saveWorkButtonClicked & updateFBRDB are API calls which will be called in <Indent />
Indent.js
export default function Indent({ data, saveWorkButtonClicked, updateFBRDB }) {
console.log('data in indent', data)
}
NOTE: Please give solutions where this can be implemented without Context/ Redux/ Mobx. Also, I am using react-router-dom v5.2.0
I would suggest an workaround. Have a state which keeps track of when you want to move to next page, so that we can use Redirect component conditionally with your desired data as props.
App.js
<Router>
<Switch>
<Route path="/ProjectPage/:projectId" exact component={ProjectPage} />
</Switch>
</Router>
ProjectPage.js
const [isDone, setIsDone] = useState(false);
const handleClick = () => {
// Do all your works, when you want to `push` to next page, set the state.
setIsDone(true);
}
if(isDone) {
return (
<>
<Route path="/Indent"
render={ props =>
<Indent
{...props}
data={...}
saveWorkButtonClicked={saveWorkButtonClicked}
updateFBRDB={updateFBRDB}
/>
}
/>
<Redirect to="/Indent" />
</>
);
}
return (
<div>Your Normal Profile Page goes here</div>
)
If you want to "share" props, you need to do one of two things. Either have the receiving component be a child of the propsharing component - in which case you can pass them as props directly. Else, you would need to pass them as state via a common ancestor component, which you would need to update by sending a callback down to the component that will update the state.
You can pass state to location with this format
const location = {
pathname: '/Indent/',
state: {
data: { ...project, rooms: project.rooms, ProjectId: project.ProjectId, ClientName: project.ClientName, Address: project.Address, AmountRecieved: project.AmountReceived, SiteEngineerId: project.SiteEngineersId },
saveWorkButtonClicked,
updateFBRDB,
}
}
history.push(location)
And then using withRouter to receive location values
import { withRouter } from 'react-router'
function Indent({ location }) {
const { state } = location
const { data, saveWorkButtonClicked, updateFBRDB } = state || {}
return <></>
}
export default withRouter(Indent)
I m new to reactJs and i m creating user Authentication functionality. I have two components one is header which has navbar and it contains react-router routers and the other is login component which has two input fields ... The problem with login component is when i start typing in input field it loses focus after each character typed i know it is rerendering the whole component but i don't know how to solve this problem
header.js
changeName = (e) => {
this.setState({name : e.target.value})
}
changePass = (e) => {
this.setState({password:e.target.value})
}
login = () => {
var name = this.state.name;
var password = this.state.password
var mysession;
$.ajax({
url : 'http://localhost:4000/login',
type : "POST",
data : {username:name,password:password},
success : function(data){
if(data == true){
this.setState({sessionFlag:true})
$('#home')[0].click();
}
else {
this.setState({sessionFlag:false})
}
}.bind(this)
})
}
render(){
const {name,password} = this.state;
return (
<Router>
<div>
<Route path="/login" exact component={()=><Login
onClickHandle={this.login.bind(this)}
onChangeName={this.changeName.bind(this)}
onChangePass={this.changePass.bind(this)}
name={name}
password = {password} />} />
</div>
</Router>
)
}
login.js
render(){
return (
<form className="form-horizontal" method ="post">
<input
type="text"
onChange={this.props.onChangeName}
value={this.props.name}/>
<input type="text"
onChange={this.props.onChangePass}
value={this.props.password} />
<input type="button"
value="Login"
onClick={this.props.onClickHandle} />
</form>
)
}
The main issue is the manner in which you are specifying your Login component:
<Route
path="/login"
exact
component={() => (
<Login
onChangeName={this.changeName.bind(this)}
onChangePass={this.changePass.bind(this)}
name={this.state.name}
password={this.state.password}
/>
)}
/>
Using this syntax causes the child of the Route to look like a brand-new type of component with each rendering (since it will be a new arrow function instance each time) so the previous Login component will be completely unmounted and the new one mounted.
From https://reactrouter.com/web/api/Route/component:
When you use component (instead of render or children, below) the router uses React.createElement to create a new React element from the given component. That means if you provide an inline function to the component prop, you would create a new component every render. This results in the existing component unmounting and the new component mounting instead of just updating the existing component. When using an inline function for inline rendering, use the render or the children prop (below).
Here is an example using the render-func approach:
Header.js
import React from "react";
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
import Login from "./Login";
class Header extends React.Component {
constructor(props) {
super(props);
this.state = { name: "", password: "" };
this.changeName = this.changeName.bind(this);
this.changePass = this.changePass.bind(this);
}
changeName = (e) => {
this.setState({ name: e.target.value });
};
changePass = (e) => {
this.setState({ password: e.target.value });
};
render() {
return (
<Router>
<div>
<div>
<Link to="/login">Login</Link>
</div>
<Route
path="/login"
exact
render={() => (
<Login
onChangeName={this.changeName}
onChangePass={this.changePass}
name={this.state.name}
password={this.state.password}
/>
)}
/>
</div>
</Router>
);
}
}
export default Header;
I'm trying to learn how to lift the state from <Child/> to <Parent/> and have the parent control user interactions done the child, (which receives state down as a prop) i.e show color and text.
I was able to lift the state up. However, when I switch back and forth between routes the <Parent/> component is not re-mounting and its state remains exactly how it was previously set by setState({})
const cars = [
{ name: "Ferrari", cost: "$9.000", color: "red", id: 1 },
{ name: "Porsche", cost: "$8.000", color: "black", id: 2 },
***
];
class Dealership extends Component {
state = {
cars,
isShow: {},
correctIndex: Math.floor(Math.random() * (cars.length - 1))
};
handleShuffle = () => {
this.setState({
cars: [...this.state.cars.sort(() => Math.random() - 0.5)],
isShow: {}
});
};
handleShow = car => {
const { isShow } = this.state;
console.log("isShow=", isShow);
this.setState(currentState => ({
isShow: { ...currentState.isShow, [car]: true }
}));
};
render() {
return (
<>
<Navigation />
<Routes
state={this.state}
shuffle={this.handleShuffle}
handleShow={this.handleShow}
// isShow={this.state.isShow}
/>
</>
);
}
}
export default withRouter(Dealership);
As mentioned above, the child <Car/>is receiving state down as props so that its user interaction can be controlled by one source of truth the parent <Dealership />
export default class Car extends Component {
render() {
const { cars, shuffle, isShow, handleShow, correctIndex } = this.props;
const correctCar = cars[correctIndex];
const car = cars.map(car => (
<CarList
// {...this.state}
isShow={isShow[car.name]}
key={car.id}
car={car.name}
guess={car.cost}
isCorrect={correctCar.cost === car.cost}
handleShow={handleShow}
/>
));
return (
<>
<Question key={correctCar.id} guess={correctCar.cost} />
<button
onClick={() => {
shuffle();
}}
>
go again
</button>
<ul className="car-list">{car}</ul>
</>
);
}
}
The <CarList/> is abstracted here:
// CarList.js
export const CarList = ({ isShow, isCorrect, car, handleShow, guess }) => {
function getColor() {
if (isShow) {
const showColor = isCorrect ? "green" : "red";
return showColor;
}
return "";
}
return (
<li onClick={() => handleShow(car)} className={getColor()}>
{car}
<span className={isShow ? "show" : "hide"}>{guess}</span>
</li>
);
};
Oddly (to me), when I switch to a route that holds its own local state i.e <Bike/>, everything works as expected (the state is back to original)
import React, { useState } from "react";
export const Bike = () => {
const [color, setColor] = useState(false);
function ChangeColor() {
setColor(true);
}
return (
<p onClick={ChangeColor}>
Click on the <span className={color ? "red" : " "}>Bike</span>
</p>
);
};
This is how I have my Routes setup:
// Navigation.JS
export const Navigation = () => (
<nav>
<ul>
<li>
<Link to="/">home</Link>
</li>
<li>
<Link to="/car-cost">car</Link>
</li>
<li>
<Link to="/bike">bike</Link>
</li>
</ul>
</nav>
);
// Routes.js
export const Routes = ({ state, shuffle, handleShow, isShow }) => (
<Switch>
<Route
path="/car-cost"
render={() => (
<Car
{...state}
shuffle={shuffle}
handleShow={handleShow}
// isShow={isShow}
/>
)}
/>
<Route path="/bike" render={() => <Bike />} />
<Route path="/" component={Home} />
</Switch>
);
I then wrapped my main app with <BrowserRouter /> as you see in totality plus the current misbehavior happening on this code sandbox
How can I switch between routes having <Car/> behave such as <Bike/>? i.e return to its original state. Also, am I lifting and controlling state correctly here?
Here the state are being saved in parent component. When the route changes then only child components are being remounted. So the state of parent component remains there throughout that routing.
You can keep the state in child component, which would reset the state after every unmount. However if you want to lift the state up and still reset the state, then you would have to do that in parent component.
A better way would be to monitor the route change in the parent component. If the route has changed then parent component should reset its state. In componentDidUpdate method of parent component, you can track the route change and reset the state like this
componentDidUpdate(prevProps) {
if (this.props.location.pathname !== prevProps.location.pathname) {
console.log('Route change! Reset the state');
this.setState({ isShow: {}})
}
}
I am having an issue with my application. My user component only loads UserCard when I start the application from the homepage then click users link there... if I just refresh the users URL... UserCard doesn't get loaded which means something is wrong with my this.props.users. I do see that in chrome it says: Value below was evaluated just now when I refresh but when I go through the flow it doesn't say that. Any help will be appreciated.
App.js
class App extends Component {
constructor(props) {
super(props);
this.state = {
users: []
};
}
componentDidMount() {
users = []
axios.get('/getall').then((res) => {
for(var d in res.data) {
users.push(new User(res.data[d]));
}
});
this.setState({ users });
}
render() {
const { users } = this.state;
return (
<Router history={history}>
<Switch>
<PrivateRoute exact path="/" component={Home} />
<Route exact path='/users' render={(props) => <Users {...props} users={users} />}/>
</Switch>
</Router>
)
}
}
PrivateRoute:
export const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={props => (
<Component {...props} /> )} />
)
User.js
export default class Users extends Component {
render() {
console.log(this.props.users);
return (
<Row>
{this.props.users.map(u =>
<UserCard key={u.name} user={u}/>
)}
</Row>
);
}
}
export class User {
constructor(obj) {
for (var prop in obj){
this[prop] = obj[prop];
}
}
getURLName() {
return this.name.replace(/\s+/g, '-').toLowerCase();
}
}
class UserCard extends Component {
render() {
return (
<Link to={'/users/' + this.props.user.getURLName()} >
<div>
// Stuff Here
</div>
</Link>
);
}
}
As per the comments:
The issue here is how you're setting state. You should never modify state directly since this will not cause the component to rerender See the react docs
Some additional thoughts unrelated to the question:
As per the comments - use function components whenever possible, especially with hooks on the way
There is probably no need to create a User class, only to new up little user objects. Simply use plain old JS objects and calculate the link url right in the place its used:
render() {
const { user } = this.props
return <Link to={`/users/${user.name.replace(/\s+/g, '-').toLowerCase()}`} />
}
It might be a good idea to start using a linter such as eslint. I see that you're declaring users = [] without using let or const (don't use var). This is bad practice since creating variables in this way pollutes the global name space. Linters like eslint will help you catch issues like this while you're coding.
I have a button nested within a component called "Create" that has to trigger a change in state that changes the state in app.js and renders a fresh view.
I can't seem to pass the method changeHPage from app.js to the Create component. I am using React-Router and normally I would simply write <App changeHPage={this.changePage}> to pass the method to its child component and call it using this.props.changeHpage but I can't pass props via this method when using React Router.
Any help on how to pass a method to a child component using React Router would be much appreciated. My code can be found below.
app.js:
/* STRICT MODE: See `../../server.js` */
'use strict';
/* GLOBAL REACT REQUIRES */
// React.js
const React = require('react');
// React-DOM for HTML rendering
const ReactDOM = require('react-dom');
// React router for dynamic pathing. Has several component features that need to be required to use.
const ReactRouter = require('react-router');
// 4 components pulled from ReactRouter:
const Router = ReactRouter.Router;
const Route = ReactRouter.Route;
const Navigation = ReactRouter.Navigation;
const Link = ReactRouter.Link;
const browserHistory = ReactRouter.browserHistory;
/* Relative paths to external components */
const auth = require('./helpers/auth.js');
const requireAuth = require('./helpers/requireauth.js');
const About = require('./components/about.js');
const Login = require('./components/login.js');
const Logout = require('./components/logout.js');
const Signup = require('./components/signup.js');
const Header = require('./components/header.js');
const Create = require('./components/create.js');
const NotFound = require('./components/notfound.js');
const Veri = require('./components/veri.js');
/* React App Creation */
const App = React.createClass({
// Declares the initial state when app is loaded
getInitialState : function() {
return {
loggedIn: auth.loggedIn(),
change: true,
phoneNumber: {}
}
},
// Updates state when login is trigger
updateAuth : function(loggedIn) {
this.setState({
loggedIn: loggedIn
})
},
changeHPage: function() {
this.state.change = !this.state.change;
this.setState({
change: !this.state.change
});
console.log("changePage On HomePage Pressed");
this.context.router.push('/')
},
// Login even triggered and sent to back-end
componentWillMount : function() {
auth.onChange = this.updateAuth
auth.login()
},
addNumber: function(phonenumber){
this.state.phonenumber = phonenumber
this.setState()
},
// Renders App and all of its children
render : function() {
<div className="Detail">
{this.props.children && React.cloneElement(this.props.children, {
changeHPage: this.changeHPage
})}
</div>
var firstView;
{if(this.state.change) {
firstView = <div>
<div className="row">
<Veri> This is a child of Veri </Veri>
<Header details="Hi, I'm Plantee"/>
<section className="col s12">
<ul>
{this.state.loggedIn ? (
<div>
<li><Link to="/logout">Log out</Link> </li>
<li><Link to="/create">Create Your Plantee</Link></li>
{/*<Create> <Veri/> </Create>*/}
</div>
) : (
<div>
<li><Link to="/login">Log In</Link></li>
<li><Link to="/signup">Sign up</Link></li>
</div>
)}
<li><Link to="/about">About</Link></li>
</ul>
{this.props.children || <p>You are {!this.state.loggedIn && 'not'} logged in.</p>}
</section>
</div> </div>
} else {
firstView= <div>'Hello'</div>
}
return React.cloneElement(
firstView,
{switch: this.changeHPage}
)
}}
})
/* React router initialization */
var routes = (
<Router history={browserHistory}>
<Route path="/" component={App} >
<Route path="header" component={Header} />
<Route path="login" component={Login} />
<Route path="logout" component={Logout} />
<Route path="create" component={Create} change={App.changeHPage} />
<Route path="signup" component={Signup} />
<Route path="about" component={About} />
<Route path="very" component={Veri} />
</Route>
<Route path="*" component={NotFound} />
</Router>
)
ReactDOM.render(routes, document.querySelector('#container'))
create.js:
const React = require('react');
const ReactDOM = require('react-dom');
const auth = require('../helpers/auth')
const Veri = require('./veri.js');
const App = require('../app.js');
const ReactRouter = require('react-router');
// 4 components pulled from ReactRouter:
const Router = ReactRouter.Router;
const Route = ReactRouter.Route;
const Navigation = ReactRouter.Navigation;
const Link = ReactRouter.Link;
const browserHistory = ReactRouter.browserHistory;
const Create = React.createClass({
getInitialState: function(){
return {checked: false}
},
handleClick: function(event) {
event.preventDefault();
this.setState({checked: !this.state.checked})
let phonenumber = {
phonenumber: this.refs.phonenumber.value
}
},
showVerification : function(event) {
event.preventDefault();
},
remove(e) {
e.preventDefault();
console.log(this.props);
},
render : function(){
var msg;
{if(this.state.checked) {
msg = <div><Veri text={'Your verification code is '} code={'code'}/> <form className="gotIt" onSubmit={this.props.changeHpage} >
<input type="Submit" value="Got It" />
</form> </div>
}
else {
msg = <Veri details={''}/>
}}
return (
<div>
<h1>Create Your Plantee</h1>
<h2>Please Enter Your Phone Number</h2>
<p>You will recieve a phone call in order to verify that you are capable of raising a plantee</p>
<form className="telephoneNumber" onSubmit={this.handleClick}>
<input id="phonenumber" ref="phonenumber" type="tel" />
<input type="Submit" />
</form>
<div> {msg} </div>
<h3>{this.props.children}</h3>
</div>
)
}
})
module.exports = Create;
Please see the following github issue:
https://github.com/reactjs/react-router/issues/1857
this is directly taken from: ryanflorence
Usually if you're passing props across route boundaries your parent route knows exactly what it's rendering:
<Route path="/inbox" component={Inbox}>
<Route path=":messageId" component={Message}/>
<IndexRoute component={InboxStats}/>
</Route>
const Inbox = React.createClass({
render() {
return (
<div>
{/* this is only ever `Message`, except the weird case
of `InboxStats` which doesn't need the prop */}
{React.cloneElement(this.props.children, {
onDelete: this.handleMessageDelete
})}
</div>
)
}
})
Instead, use a componentless route and just do "normal" React stuff.
<Route path="/inbox" component={Inbox}>
{/* no more `Message` */}
<Route path=":messageId"/>
</Route>
const Inbox = React.createClass({
render() {
const { messageId } = this.props.params
return (
<div>
{messageId ? (
<Message onDelete={this.handleMessageDelete}/>
) : (
<InboxStats/>
)}
</div>
)
}
})
cloneElement is not bad practice on its own, but it can often be an indicator that there's a bit more straightforward way of doing something.