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
Related
This question may sound silly to some people, but I am really confused on how to do it
I have 3 file: App.js, HomePage.js and Profile.js
App.js :
import React from "react"
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import HomePage from "./components/HomePage";
import Profile from "./components/Profile"
function App() {
return (
<Router>
<Switch>
<Route path="/" exact component={HomePage} />
<Route exact path="/profile/:profileId" component= {Profile} />
</Switch>
</Router>
);
}
export default App;
From here, the default page it will go to is HomePage.js
HomePage.js:
import React, { Component } from "react";
import axios from "axios";
import { Link } from "react-router-dom";
class HomePage extends Component {
constructor() {
super();
this.state = {
userData: [],
}
}
componentDidMount() {
axios.get("XXXXXXXX").then((response) => {
const userDataList = response.data.users;
this.setState({
userData: userDataList
})
})
}
render() {
const userGrid = this.state.userData.map((user, index) => {
return (
<div key={index}>
<Link to={`/profile/${user.id}`}>
<img src={user.profilepicture} />
<p>{user.name}</p>
</Link>
</div>
)
})
return (
<div className="App">
<div className="card">
<div className="card__top">
<span className="card__title">
<p>Select An Account</p>
</span>
</div>
<div className="card__bottom">
<div className="card__table">
{userGrid}
</div>
</div>
</div>
</div>
)
}
}
export default HomePage;
In HomePage.js, I am able to show the profile picture and name of the user from API.
In the next page which is Profile.js , I am able to print the ID of the user.
Profile.js:
import React, { Component } from "react";
class Profile extends Component{
componentDidMount(){
const uid = this.props.match.params.profileId;
}
render() {
console.log(this.props.match);
return(
<h1>{this.props.match.params.profileId}</h1>
)
}
}
export default Profile;
As you can see I am printing the ID of user.
Here I also want to show the Profile Picture of the user which I selected in HomePage.js
This I am not able to do it.
JSON file:
{ - users: [-{id:1, name:"abc", profilepicture: "xxxxx.jpeg"}, ]}
You need to store a global state in your applicattion, which you can access from every connected component. This is a more complex topic. redux is a good framework to handle your global state changes.
Here is a tutorial: https://appdividend.com/2018/06/14/how-to-connect-react-and-redux-with-example/
I found it pretty hard to learn redux, but in the end it takes away a lot of pain. Because this is a problem you gonna have in every app you build with react.
You need use Context API o redux
Example context API: https://ibaslogic.com/react-context-api/
Context's well to little projects, but Redux performs better.
App.js
import React from "react"
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import HomePage from "./components/HomePage";
import Profile from "./components/Profile"
import { UsersProvider } from "./UsersProvider.js";
function App() {
return (
<Router>
<UsersProvider>
<Switch>
<Route path="/" exact component={HomePage} />
<Route exact path="/profile/:profileId" component= {Profile} />
</Switch>
</UsersProvider>
</Router>
);
}
export default App;
UsersContext.js
import React, { Component } from "react"
const UsersContext = React.createContext();
const UsersProvider = UsersContext.Provider;
const UsersConsumer = TodosContext.Consumer;
class MyContext extends Component {
state = {
value: null,
};
setValue = (value) => {
this.setState({ value });
};
render() {
return (
<UsersProvider value={{ setValue, value }}>{this.props.children}
</UsersProvider>
)
}
}
export { UsersContext, UsersProvider, UsersConsumer }
HomePage.js
import React, { Component } from "react";
import axios from 'axios';
class HomePage extends Component {
componentDidMount() {
axios.get("XXXXXXXX").then((response) => {
const userDataList = response.data.users;
// updating your context
this.props.context.setValue(userDataList);
})
}
render() {
const userGrid = this.props.context.value.map((user, index) => {
return (
<div key={index}>
<Link to={`/profile/${user.id}`}>
<img src={user.profilepicture} />
<p>{user.name}</p>
</Link>
</div>
)
})
return (
<div className="App">
<div className="card">
<div className="card__top">
<span className="card__title">
<p>Select An Account</p>
</span>
</div>
<div className="card__bottom">
<div className="card__table">
{userGrid}
</div>
</div>
</div>
</div>
)
}
}
export default HomePage;
Profile.js
import React, { Component } from "react";
import { UsersConsumer } from "./UsersContext.js";
class Profile extends Component{
render() {
return(
<UsersConsumer>
{users => (
<h1>{users.value.find(user => user.id === this.props.match.params.profileId)}</h1>
)}
</UsersConsumer>
)
}
}
export default Profile;
I'm building SPA (single page app) using React and React-Router.
"Employees" is one of the navigation menu items in the header.
According to the docs - Route-based code splitting, I'm trying to make components lazy loading like this:
import React, { Component, lazy, Suspense } from 'react';
import './App.css';
import { BrowserRouter, Switch, Route } from 'react-router-dom';
...
// import { Employees } from './components/Employees/Employees';
const Employees = lazy(() => import('./components/Employees/Employees'));
export default class App extends Component {
state = {
...
employeesData: [
...objects with data...
]
}
render() {
return (
<BrowserRouter>
<div className="App">
...
<Suspense fallback={<div>Loading...</div>}>
<Switch>
{/* other routes here */}
<Route path="/employees/" component={
() =>
<Employees
data={this.state.employeesData}
/>
} />
</Switch>
</Suspense>
...
</div>
</BrowserRouter>
);
}
}
Employees component looks like:
import React from 'react';
import './css/Employees.css';
export const Employees = (props) => {
const { data } = { ...props };
// sort elements by name value
data.sort(
(a,b) => (a.name > b.name) ? 1 : ((b.name > a.name) ? -1 : 0)
);
let items = [];
for (let i = 0; i < data.length; i++) {
items.push(
<div className="container">
<div className="imgbox">
<img className="image"
src={ data[i].image }
alt={ data[i].name }
/>
</div>
<div className="textbox">
<h4 className="name">
{ data[i].name }
</h4>
<p className="text">
{ data[i].title }
<br/>
{ data[i].text }
<br/>
{ data[i].workplace }
</p>
</div>
</div>
);
}
return (
<div className="Employees">
...
<div className="wrapper">
...
{ items }
</div>
</div>
)
};
The thing is - when clicking on the "Employees" nav item, web-page becomes blank. What am I doing wrong?
Warning: lazy: Expected the result of a dynamic import() call. Instead received: [object Module]
Your code should look like:
const MyComponent = lazy(() => import('./MyComponent'))
It's saying it expects the Employees component to be the default export of the file. You'll have to change it to be the default export: https://reactjs.org/docs/code-splitting.html#named-exports
Two things.
Use a render prop instead of component in this situation (Router).
Wrap in Suspense just your Employees instead of the whole Switch (inside your render prop).
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;
I am really new to React, so I am trying to build a Pokemon app. My main goal is to build the list of 20 pokemon, and detail box which when clicked on Pokemon from the list should display chosen pokemon details, pictures etc.
import React, { Component } from 'react';
import './styles/App.css';
class App extends Component{
render(){
return <div className="App">
<h1> Pokedex! </h1>
<PokemonList/>
<PokemonDetail/>
</div>;
}
}
class Pokemon extends Component {
render(){
const {pokemon,id} = this.props;
return <div className="pokemon--species">
<button className="pokemon--species--container">
<div className="pokemon--species--sprite">
<img src={`https://pokeapi.co/media/sprites/pokemon/${id}.png`} />
</div>
<div className="pokemon--species--name">{id} {pokemon.name} {pokemon.url} </div>
</button>
</div>;
}
}
class PokemonDetail extends Component {
render(){
const {pokemon, id} = this.props;
return <div className="pokemon--species">
<button className="pokemon--species--container">
<div className="pokemon--species--sprite">
<img src={`https://pokeapi.co/media/sprites/pokemon/${id}.png`} />
</div>
<div className="pokemon--species--name">{id}</div>
<p>Attack:72</p>
<p>Defense:23</p>
<p>Health:99</p>
</button>
</div>;
}
}
class PokemonList extends Component{
constructor(props){
super(props);
this.state = {
species : [],
fetched : false,
loading : false,
};
}
componentWillMount(){
this.setState({
loading : true
});
fetch('https://pokeapi.co/api/v2/pokemon?limit=20').then(res=>res.json())
.then(response=>{
this.setState({
species : response.results,
loading : true,
fetched : true
});
});
}
render(){
const {fetched, loading, species} = this.state;
let content ;
if(fetched){
content = <div className="pokemon--species--list">{
species.map((pokemon,index) => <Pokemon key={pokemon.name} id={index+1} pokemon={pokemon}/>)
}
</div>;
}else if(loading && !fetched){
content = <p> Loading ...</p>;
}
else{
content = <div/>;
}
return <div className="container">
{content}
</div>;
}
}
export default App;
I know, there is much to do there, but first I want to understand how to pass ID to pokemondetails class.
Have a look at react-router and how to pass parameters to components associated with routes. Basically, you could have two routes that would render following components PokemonList and PokemonDetail. Redirect user from the PokemonList to PokemonDetail and append pokemonId to the url ( e.g "/details/23").
After redirection 'PokemonDetail' component would be rendered and pokemonId would be available in the component.
const App = () => (
<Router>
<div>
...
<Route exact path="/" component={PokemonList}/>
<Route path="/details/:pokemonId" component={PokemonDetail}/>
</div>
</Router>
)
// access pokemonId
class PokemonDetail extends Component{
render() {
return (
<div>
<h2>{this.props.params.pokemonId}</h2>
</div>
)
}
}
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!