EDIT: Added component to view
I am trying to render the correct page view based on a submit button from a search. Currently I have a search bar at the top of the view and a default landing page in the middle. When the user searches I want to change the default landing page to the profile page they are searching for.
I am assuming I will have to remove the component from Main and replace it with {this.props.children}. Then in I will have to add maybe a around the submit button? The problem with this so far is that Profile then doesn't get the necessary props it needs from SearchBar.
My view ideally will show at the top and in the main container. When the user searches will change to containing the correct user information searched for which is passed to from -> ->
Below are my current Routs and Main components
import React, { Component } from 'react';
import { Router, Route, Redirect, IndexRoute, Link, hashHistory } from 'react-router';
import Main from '../components/Main';
import Profile from '../components/Profile';
import Landing from '../components/Landing';
class Routes extends Component {
render() {
return (
<Router history={ hashHistory }>
<Route path="/" component={Main}>
<Route path="Profile" component={Profile}></Route>
<Route path="Landing" component={Landing}></Route>
<IndexRoute component={Landing}></IndexRoute>
</Route>
</Router>
)
}
}
export default Routes;
Main
import React, { Component } from 'react';
import Routes from '../utils/Routes';
import Footer from './Footer';
import Profile from './Profile';
import SearchBar from './SearchBar';
import Landing from './Landing';
class Main extends Component {
constructor(props) {
super(props);
this.state = {
profileName: ''
}
}
handleProfileChange(profileName) {
this.setState( { profileName });
//replace <Profile /> with {this.props.children} maybe
}
render() {
return (
<div className="container-fluid">
<div className="row">
<SearchBar history={this.props.history} handleProfileChange={this.handleProfileChange.bind(this)} />
</div>
<div className="row">
<Profile name={this.state.profileName} />
</div>
<div className="row">
<Footer />
</div>
</div>
)
}
}
export default Main;
SearchBar
import React, { Component, PropTypes } from 'react';
import Profile from './Profile';
import TopNav from './TopNav';
import sass from '../scss/application.scss';
import { Router, Route, Redirect, IndexRoute, Link, hashHistory } from 'react-router';
class SearchBar extends Component {
constructor(props){
super(props)
this.state = {
name: ''
}
}
handleChange(e) {
this.setState({
name: e.target.value
});
}
handleSubmit(e) {
e.preventDefault();
console.log("searching for NAME " + this.state.name);
let profileName = this.state.name;
profileName = profileName.toLowerCase().trim();
//Cap the first letter in the name and add the rest of the name
profileName = profileName.charAt(0).toUpperCase() + profileName.substr(1);
console.log("NEW NAME " + profileName);
this.props.handleProfileChange(profileName);
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit.bind(this)}>
<input type="text" placeholder="Enter Name"
name="name"
value={this.state.name}
onChange={this.handleChange.bind(this)} />
<button className="btn btn-success" type="submit">Search</button>
</form>
</div>
)
}
}
SearchBar.propTypes = {
handleProfileChange: React.PropTypes.func.isRequired,
}
export default SearchBar;
Here's a basic sketch, if I understand you correctly. This would be the search bar and the submit.
class SearchProfiles extends Component {
static contextTypes = {
router: PropTypes.object,
};
state = { search: '' };
onSubmit = e => {
e.preventDefault();
// maybe do your search here, or inject it directly.
this.context.router.push(`/profiles/${this.state.search}`);
};
onChange = e => this.setState({ search: e.target.value });
render() {
return (
<form onSubmit={this.onSubmit}>
<input onChange={this.onChange} value={this.state.search} />
<button type="submit">Search Profiles</button>
</form>
)
}
}
You would have to add a route for the search, though.
<Route path="/profiles/:search" component={Profile} />
Related
I've got a switch in App.js to render different body components. "Landing" is the landing page body component. It's got a text field to enter a zip code, and when you click the submit button, it renders the "Events" page body component that displays some stuff.
When the Events component loads, I need it to be able to access the zip code that the user entered on the Landing page, so I lifted "zip" to App.js, which is the parent of Landing and Events.
I'm using Route and Switch so I can render the different body components. It's not getting that far though:
TypeError: this.props.onZipChange is not a function
No clue why it doesn't recognize onZipChange as a function in App.js. I won't bother showing the Events.js file because it's not even being rendered before I get the TypeError. The second I try to type into the input box in Landing.js, it triggers the input box's onChange attr, which calls this.handleChangeZip, which tries to call App.js' onZipChange function through this.props, which it's not recognizing.
Any thoughts?
App.js:
import React, { PropTypes, Component } from "react";
import "./styles/bootstrap/css/bootstrap.min.css";
import "./styles/App.css";
import "./index.css";
import Header from "./routes/Header";
import Body from "./routes/Body";
import { Switch, Route, NavLink } from "react-router-dom";
import Landing from "./routes/Landing";
import Events from "./routes/Events";
import Help from "./routes/Help";
class App extends Component {
constructor(props) {
super(props);
this.state = { zip: "" };
this.handleZipChange = this.handleZipChange.bind(this);
}
handleZipChange = newZip => {
this.setState({ zip: newZip });
};
render() {
const currZip = this.state.zip;
return (
<div className="App">
<Header zip={currZip} />
<Switch>
<Route
exact
path="/"
render={props => <Landing {...props} zip={currZip} />}
onZipChange={this.handleZipChange}
/>
<Route
exact
path="/Events"
render={props => <Events {...props} zip={currZip} />}
onZipChange={this.handleZipChange}
/>
<Route exact path="/Help" component={Help}></Route>
</Switch>
</div>
);
}
}
export default App;
Landing.js:
import { Redirect } from "react-router-dom";
import React from "react";
import "../styles/App.css";
class Landing extends React.Component {
constructor(props) {
super(props);
this.state = { value: "", toEvents: false };
this.handleSubmit = this.handleSubmit.bind(this);
this.handleChangeZip = this.handleChangeZip.bind(this);
}
handleChangeZip(e) {
this.props.onZipChange(e.target.value);
}
handleSubmit(event) {
this.setState(() => ({
toEvents: true
}));
event.preventDefault();
}
render() {
if (this.state.toEvents === true) {
return <Redirect to="/Events" />;
}
return (
<div>
<div className="main-body">
<div className="main-question" id="thisfontonly">
What city are you looking for?
</div>
<div className="textbar-and-button">
<input
onChange={this.handleChangeZip}
value={this.props.zip}
type="text"
name="city"
id="citylabel"
style={{ fontSize: "24pt" }}
className="rcorners"
/>
<div className="buttons">
<input
onClick={this.handleSubmit}
type="submit"
name="submit"
value="Go!"
id="submit"
className="button"
/>
</div>
</div>
</div>
</div>
);
}
}
export default Landing;
please, what is the best way in React how to achieve:
submit form (and..)
redirect to another page (and..)
have some props from the origin form here?
I have discovered two possibilities how to redirect:
Source article: https://tylermcginnis.com/react-router-programmatically-navigate/
1) with React Router: history.push()
2) with React Router: <Redirect />
1) With history.push(): Redirecting works but i have no idea how to add custom props to redirected page.
2) With <Redirect />: adding custom props works (in this way):
<Redirect to={{ pathname: '/products', state: { id: '123' } }} />
But redirecting does not work to me, I keep receiving errors after submission.
Source code:
import React from 'react';
import './App.css';
import { withRouter, Redirect } from 'react-router-dom'
class App extends React.Component {
state = {
toDashboard: false,
}
handleSubmit = () => {
this.setState(() => ({
toDashboard: true
}));
}
render() {
if (this.state.toDashboard === true) {
return <Redirect to={{
pathname: '/products', state: { id: '123' }
}} />
}
return (
<div>
<h1>Register</h1>
<form onSubmit={this.handleSubmit}>
<button type="submit">Submit</button>
</form>
</div>
);
}
}
export default withRouter(App);
Errors:
Warning: You tried to redirect to the same route you're currently on: /products"
Form submission canceled because the form is not connected
What is the best way how to achieve my target, please?
You need to cancel the default submit action.
so change you handleSubmit method to
handleSubmit = (e) => {
e.preventDefault();
this.setState({
toDashboard: true
});
}
What is finally working fine to me is code below here.
From App.js it is routed to Products.js, then i click on the button and it is redirected to NotFound.js and i can reach props "state: { id: 123 }" and i display it here.
Hope it will help to someone who is looking for some working submission patern.
App.js
import React from 'react';
import './App.css';
import { Route, Switch } from 'react-router-dom';
import Products from './Products';
import NotFound from './NotFound';
import Home from "./Home";
class App extends React.Component {
render() {
return (
<div>
<Switch>
<Route path="/products" component={Products} />
<Route path="/notfound" component={NotFound} />
<Route path="/" exact component={Home} />
</Switch>
</div>
);
}
}
export default App;
Products.js
import React, { Component } from "react";
class Products extends Component {
handleSubmit = (e) => {
e.preventDefault();
this.props.history.push({ pathname: '/notfound', state: { id: 123 } });
}
render() {
console.log(this.props);
return (
<div>
<h1>Products</h1>
<form onSubmit={this.handleSubmit}>
<button type="submit">Submit</button>
</form>
</div>
);
}
}
export default Products;
NotFound.js
import React from "react";
const NotFound = (props) => {
console.log(props);
return (
<div>
<h1>Not Found</h1>
<h2>{props.location.state.id}</h2>
</div>
);
};
export default NotFound;
In my code I have a few checks after a user has entered some data, then I want to load the next route if everything is correct, what is the best way to do so?
This is my current Route page:
<Router history = {browserHistory}>
<Route exact path="/" component={() => <MainMenu userData={this.state.userData}/>}/>
<Route exact path="/login" component = {Login} />
<Route exact path="/pastMeetingsPlay/:meetingCode" component={(props) => <PastMeetingsPlay user={this.state.userData.UserID} {...props}/>} />
<Route exact path="/meetingMode/:meetingCode" component={(props) => <MeetingMode user={this.state.userData.UserID} {...props}/>} />
</Router>
the user submits a form then there inputs are checked and if all the required checks pass then it should load meetingMode page
EDIT:
import React, { Component } from 'react';
import './App.css';
import MeetingMode from'./MeetingMode';
import NavbarMenu from './Navbar';
import Popup from "reactjs-popup";
import axios from 'axios';
import {withRouter, history, Redirect, Route} from "react-router";
class MeetingModeLoad extends Component{
constructor(props)
{
super(props);
this.state ={
meeting:{},
value:0
};
this.handleSubmit = this.handleSubmit.bind(this);
this.handleChange = this.handleChange.bind(this);
}
async handleSubmit(event)
{
event.preventDefault();
let meetingLoadCode = this.state.value
try{
let getter = await axios.get(`https://smartnote1.azurewebsites.net/api/meetings/${meetingLoadCode}`)
let meetingLocal = getter.data
this.setState({meeting:meetingLocal})
if(meetingLocal.Status == 2)
{
console.log("please join meeting that is planned or under going")
}
else
{
console.log("/meetingMode/" + this.state.meeting.MeetingID);
this.props.history.push("/meetingMode/" + this.state.meeting.MeetingID)
}
}
catch(error)
{
console.error(error)
}
}
handleChange(event)
{
this.state.value = event.target.value
console.log(this.state.value)
}
render()
{
return(
<div>
<Popup
trigger={<button className="meetingModeButton" onClick={() => this.handleClick}>Meeting Mode</button>}
modal
closeOnDocumentClick>
<div className="newNote">
<header style={{background: "#F7941D" }}> Meeting Mode</header>
<form onSubmit={this.handleSubmit}>
<label> Enter Meeting Code :
<input type="text" name="type" className="inputBox" onChange={this.handleChange}/>
</label>
<input type="submit" value="Submit" />
</form>
</div>
{console.log(this.state.meeting)}
</Popup>
</div>
)
}
}
export default withRouter (MeetingModeLoad)
Looks like you forgot to wrap your component into withRouter. It is mandatory to access the history prop
Place this in the component from which you try to push:
import { withRouter } from 'react-router'
...
export default withRouter(YourComponent);
And push by using this in your component:
this.props.history.push("/meetingMode/" + meetingCode);
I have a child component:
import * as React from 'react';
import Select from 'react-select';
import { Link } from 'react-router-dom';
import { Button } from '../controls/Button/Button';
import { ISelectedItem } from '../../interfaces/ISelectedItem';
import * as service from "../../helpers/service";
export interface IProps{
onClickRender: (selectedItem: ISelectedItem) => void;
}
export interface IState {
customerData: ISelectedItem[];
selectedItem: ISelectedItem;
}
export class DropDownSearch extends React.Component<{}, IState>{
constructor(props: any) {
super(props);
this.state = ({
customerData: [],
selectedItem: { shortName: '', description: '' }
});
}
componentDidMount() {
service.fetchJson<ISelectedItem[]>("/api/customers")
.then((json) =>{
this.setState({
customerData: json
});
});
}
handleChange = (selectedItem: any) => {
this.setState({
selectedItem
});
}
render() {
const { selectedItem } = this.state;
const value = selectedItem && selectedItem;
return (
<div>
<Select
name="form-field-name"
value={this.state.selectedItem}
onChange={this.handleChange}
options={this.state.customerData}
labelKey="shortName"
/>
<Link to={{
path "/dashboard/" + this.state.selectedItem.shortName,
state: { detail : this.state.selectedItem }
}}>
<Button type="button" className="btn btn-primary" caption="Search" />
</Link>
</div>
);
}
}
I want to pass the this.state.selectedItem to the Dashboard component, which is part of the Route config in the parent component below:
import * as React from 'react';
import { Navbar } from './Navbar/Navbar';
import { ShortNameSelector } from './ShortNameSelector/ShortNameSelector';
import { Dashboard } from './Dashboard/Dashboard';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
export class App extends React.Component<{},{}>{
render(){
return(
<BrowserRouter>
<div className="container">
<Navbar />
<div className="col-lg-12">
<Switch>
<Route exact path="/" component={ShortNameSelector} />
<Route path="/dashboard" component={Dashboard} />
</Switch>
</div>
</div>
</BrowserRouter>
);
}
}
Problem is I'm using Routes to switch components on the button click in my child components. How do I pass the this.state.selectedItem object from child to the Dashboard component (shown in parent component) via Routes?
EDIT:
So I put the state attribute inside Link tag and referenced it in Dashboard component like this.props.location.state.detail and it works. But now I want to persist the data in that route/Dashboard component when I open that link in a new page. How do I go about it?
You can use like this
<Route path="/dashboard/:selectedItem" component={Dashboard} />
So you can dynamically update the selected item in the DOM URL and when you click it, you can use 'this.props.match.params.id' in the 'Dashboard' component to access that value.
Passing object between components via Router in React: I have copied fragment of codes from my project, might be useful to you.
I use NavLink, which supposed pass an object to my InfoComponent
<NavLink to={{
pathname: /menu/${props.data.code},
search: '',
state: { selectedMenu: props.data }
}} color="info" className="btn btn-info btn-success mx-4">Info</NavLink>
In my router, I then received the passed argument in Router as follows, added console log for more clarity
<Route path="/menu/:item" render={(props) => {
console.log("::::::::: " + JSON.stringify(props.location.state.selectedMenu));
return (<InfoComponent selectedMenu={props.location.state.selectedMenu} />);
}} />
I have two components. These components are located on different routes. 'CreateItem' component gives me possibility to create new items. I store new items to array. Array will include new created items. I want send this modified array to component 'Main' where I will iterate those items and display them as list.
Here is my code:
1) index.js file:
import React, { Component } from 'react';
import { render } from 'react-dom';
import { BrowserRouter, Route } from 'react-router-dom'
import {Main} from "./components/Main"
import {CreateItem} from "./components/CreateItem"
import {CurrentItem} from "./components/CurrentItem"
render(
<BrowserRouter>
<div>
<Route exact path="/" component={Main}/>
<Route path="/create_item" component={CreateItem}/>
<Route path="/item" component={CurrentItem}/>
</div>
</BrowserRouter>,
document.getElementById('app')
);
2) Main.js
import React from 'react';
import { withRouter } from 'react-router-dom';
import { Route, browserHistory } from 'react-router-dom';
export class Main extends React.Component {
render(){
const ToCreateItemPageButton = () => (
<Route render={({ history}) => (
<button type='button' onClick={() => { history.push('/create_item') }}>Move to create item page!</button>
)}
/>
)
return (
<div>
<h1>Main Page</h1>
<ToCreateItemPageButton/>
</div>
);
}
}
3) CreateItem.js
import React from 'react';
import { Route, browserHistory } from 'react-router-dom';
export class CreateItem extends React.Component {
constructor(props) {
super(props);
this.state = {
mainArray: [],
item: {},
item_id: 0,
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({item: {item_id: this.state.item_id,
name:event.target.value}});
}
handleSubmit(event) {
if (this.state.item.name.length > 0) {
this.state.mainArray.push(this.state.item);
this.state.item_id = this.state.item_id + 1;
let data = JSON.stringify(this.state.mainArray);
localStorage.setItem('mainObject', data);
this.setState(
{mainArray : this.state.mainArray,
item_id : this.state.item_id,}
);
event.preventDefault();
}
}
render(){
const ToMainPageButton = () => (
<Route render={({ history}) => (
<button type='button' onClick={() => { history.push('/') }}>Move to main page!</button>
)}
/>
)
return (
<div>
<h1>Create new item</h1>
<ToMainPageButton/>
<form onSubmit={this.handleSubmit}>
<label>
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
</div>
);
}
}
So all I want is to have possibility to transfer my mainArray from 'CreateItem' component to 'Main' component.
You could redirect and send data like that:
this.props.history.push({
pathname: '/target-path',
data: [/*your data*/]
});
and receive it on the target component so:
const { data } = this.props.location;
Short answer - Yes it's possible using container component like in fiddle example.
So the idea is to keep you array of items in a container state and pass it to "iterated" component as well as a callback for handling incoming item.
// container component
class Container extends React.Component {
constructor(props){
super(props);
this.state = {
array: ['Hello', 'Stack', 'Overflow']
}
this.handleOnAdd = this.handleOnAdd.bind(this)
}
handleOnAdd(item){
this.setState({
array: [...this.state.array, item]
})
}
render() {
return (
<div>
// pass shared props to "display" component
<ChildOneDisplay items={this.state.array} />
// pass a callback to CreateItem component
<ChildTwoAdd onAdd={this.handleOnAdd} />
</div>
);
}
}
// display component
class ChildTwoAdd extends React.Component{
constructor(props){
...
this.handleAdd = this.handleAdd.bind(this)
}
handleAdd(){
this.props.onAdd(this.state.item);
...
}
render(){
return(
<div>
<input
name="item"
type="text"
onChange={this.handleChange}
value={this.state.item}
/>
<button onClick={this.handleAdd}>Add Me</button>
</div>
)
}
}
So all you need is to wrap your two routes with a container component and pass props to both of them as i did in this example.
// So your container should look like the following one
render(){
return (
<div>
<Route exact path="/" render={() => <Main items={this.state.array}}/>
<Route path="/create_item" render={() => <CreateItem onAdd={this.handleAdd}/>}/>
</div>
)
}
// And render it as the following
<BrowserRouter>
<Container />
<Route path="/item" component={CurrentItem}/>
</BrowserRouter>
Moreover i suggest looking at redux - this is the library for managing your app state.
Thanks!