Is it possible to realize communication between independent components in ReactJS? - reactjs

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!

Related

TypeError: this.props is not a function (react router / switch)

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;

React-Router How to push to next page after checks

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);

React: Passing data from between components via Route

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} />);
}} />

Pass props from wrapper to one children page

Hello and thank you in advance for your help. I have a problem passing props to components loaded with routes. I have a routes file with a wrapper component that loads the pages regarding the path url. On the wrapper component (Layout) I would like to pass to the children components some props. But as the children components are called with this.props.children I don't know how to pass the props. I tried many things and nothing has worked.
I have the following rotes file:
import React from 'react';
import { Route, IndexRoute } from 'react-router';
import Layout from '../components/pages/Layout.js';
import Search from '../components/pages/Search.js';
import Queue from '../components/pages/Queue.js';
import About from '../components/pages/About.js';
const routes = () =>
<Route path="/" component={Layout}>
<IndexRoute component={Search}></IndexRoute>
<Route path="queue" component={Queue}></Route>
<Route path="about" component={About}></Route>
</Route>
export default routes;
In Layout I have:
import React from "react";
import Footer from "../common/Footer.js";
import Nav from "../common/Nav.js";
import Header from "../common/Header.js";
export default class Layout extends React.Component {
constructor(props) {
super(props);
this.state = {
isSongPlaying: false,
playingTrackId: "",
playingList: []
}
}
handleClickTrack(track) {
this.setState({
isSongPlaying: !this.state.isSongPlaying
});
}
renderTrack(i) {
return (
<Player audio_id={id} />
);
}
render() {
const { location } = this.props;
const { history } = this.props;
const { children } = this.props;
return (
<div>
<Header />
<Nav location={location} history={history}/>
<div className="container">
<div className="row">
<div className="col-lg-12">
{this.props.children}
</div>
</div>
<div className="row">
<div className="col-lg-12">
<div className="song-player">
{this.state.isSongPlaying ? this.renderTrack(this.state.playingTrackId) : null}
</div>
</div>
</div>
<Footer/>
</div>
</div>
);
}
}
on {this.props.children} the component is loading my pages components Search, Queue, and About, but i would like add callback props to my Search and Queue components.
On my wrapper Layout component I want to achieve the following:
import React from "react";
import Footer from "../common/Footer.js";
import Nav from "../common/Nav.js";
import Header from "../common/Header.js";
export default class Layout extends React.Component {
constructor(props) {
super(props);
this.state = {
isSongPlaying: false,
playingTrackId: "",
playingList: []
}
}
handleClickTrack(track) {
this.setState({
isSongPlaying: !this.state.isSongPlaying
});
}
renderTrack(i) {
return (
<Player audio_id={id} />
);
}
render() {
const { location } = this.props;
const { history } = this.props;
const { children } = this.props;
return (
<div>
<Header />
<Nav location={location} history={history}/>
<div className="container">
<div className="row">
<div className="col-lg-12">
{RENDER SEARCH WITH onClick prop}
{RENDER QUEUE WITH onClick prop}
</div>
</div>
<div className="row">
<div className="col-lg-12">
<div className="song-player">
{this.state.isSongPlaying ? this.renderTrack(this.state.playingTrackId) : null}
</div>
</div>
</div>
<Footer/>
</div>
</div>
);
}
}
I'm using render={() => <Component/>} in my React apps to give my Routes props. Don't know if it's the perfect way. There might be other ways. But it's working! :)
Here's an example of one of your Routes:
<Route exact path="/queue" render={() => <Queue prop={something}/>} />
You can pass the props to child component using childContextTypes static object.Define below context in parent Layout component.
static childContextTypes={
isSongPlaying: React.PropTypes.bool,
playingTrackId:React.PropTypes.string,
playingList: React.PropTypes.array
}
Then populate the value using getChildContext() in Layout class
getChildContext=()=>{
return {
isSongPlaying: false,
playingTrackId:"Any Value to child component that you are going to pass",
playingList: [] //Array with value
}
}
Now you can get the value in child component (About.jsx or Search.jsx) by defining context types like below
static contextTypes={
isSongPlaying: React.PropTypes.bool,
playingTrackId:React.PropTypes.string,
playingList: React.PropTypes.array
}
Now you can access the property value in child component using the context like below
let isPlaying= this.context.isSongPlaying //or
let playingTrackId=this.context.playingTrackId

React router changing landing view to search results view

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} />

Resources