I am very new to react...
I have a parent component that represents the background for my webapp. I use Match from react-router to handle when the user navigates to different routes.
The new route renders the PageView component, which I want to have a different background image. So to do this I set the background image of it's parent by passing a function from the parent to he child's props and then calling it in the child from componentDidMount()
This does not work and gives me Maximum call stack exceeded error
Here is my parent component:
class App extends Component {
constructor () {
super()
this.state = {
backgroundImage: '../public/img/initial.jpg'
}
this.changeBackground = this.changeBackground.bind(this)
}
changeBackground (backgroundImage) {
this.setState({
backgroundImage
})
}
render () {
return (
<BrowserRouter>
<Provider store={store}>
<div className='app' style={{backgroundImage: `url(${this.state.backgroundImage})`}}>
<div className='overlay'>
<Match exactly pattern='/' component={() => <Landing />}
/>
<Match
pattern='/healthcare'
component={(props) => <PageView descriptionText='Healthcare Solutions'
backgroundImage='../public/img/5.png' changeParentBackground={this.changeBackground} {...props} />}
/>
<Match
pattern='/officeofthefuture'
component={(props) => <PageView descriptionText='Office of the Future'
backgroundImage='../public/img/1.png' changeParentBackground={this.changeBackground} {...props} />}
/>
</div>
</div>
</Provider>
</BrowserRouter>
)
}
}
And here is my child component PageView
class PageView extends Component {
componentDidMount () {
const { backgroundImage, changeParentBackground } = this.props
changeParentBackground(backgroundImage)
}
render () {
const { descriptionText } = this.props
console.log(this.props.changeParentBackground)
return (
<div className='outerDiv'>
<LeftDesign show />
<RightDesign descriptionText={descriptionText} />
</div>
)
}
}
RightDesign:
class RightDesign extends Component {
render () {
const { descriptionText } = this.props
return (
<div className='rightDiv'>
<div id='bigCircle'>
<div className='bigCircleTextDiv'>
<h1 className='bigCircleText'>{descriptionText}</h1>
</div>
</div>
</div>
)
}
}
LeftDesign
class LeftDesign extends Component {
constructor (props) {
super(props)
const { show } = this.props
this.state = {show}
}
render () {
return (
<div className='leftDivContainer'>
<div className='leftDiv'>
{this.state.show && <WelcomeMsg />}
</div>
</div>
)
}
}
I commented earlier about this sort of solution.
It uses CSS background images and transitions to switch the image based on what the string of the location's pathname's second item is, which would correspond to the first-level routes of your app.
function getBackground(array) {
switch(array[1]) {
case 'healthcare':
return 'www.foo.com/image/1.jpg';
case 'officeofthefuture':
return 'www.foo.com/image/2.jpg';
default:
return 'www.foo.com/image/default.jpg'
}
}
export default class Parent extends Component {
render() {
let { location } = this.props;
// Split the pathname string into an array of sub-paths
let locationArray = location ? location.pathname.split('/') : [];
// Determine the Background URL
let backgroundUrl = getBackground(locationArray);
// Define the Parent Style
const parentStyle = {
transition: 'all 0.2s ease-out',
backgroundImage: "url('"+backgroundUrl+"')",
backgroundPosition: 'center',
backgroundSize: 'contain',
backgroundRepeat: 'no-repeat'
};
return <div style={parentStyle}>
{this.props.children}
</div>
}
}
Related
I'm trying to do this.props.history.push... in my component, but even after making sure that I'm exporting it using withRouter I still get this error:
Uncaught TypeError: Cannot read property 'push' of undefined
I also made sure that the parent component that's using this is wrapped inside of a ProtectedRoute as well:
// my component:
import React from 'react';
import { withRouter } from 'react-router-dom';
import { Link } from 'react-router-dom';
class UserIndexItem extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
this.play = this.play.bind(this);
}
handleClick(e) {
if (!e.target.classList.contains("triangle")) {
this.props.history.push(`/playlist/${this.props.playlist.id}`);
}
}
handleTrack(playlist) {
// still going forward one, then back one, and then it plays normally...
if (!playlist.payload.tracks) return;
let tracks = Object.values(playlist.payload.tracks);
let currentTrack = tracks[0];
let nextTrack = tracks[1];
this.props.receiveCurrentTrack(currentTrack);
this.props.receiveNextTrack(nextTrack);
this.props.receiveTitle(currentTrack.title);
this.props.receiveArtist(currentTrack.artist);
this.props.receiveAlbumId(currentTrack.album_id);
}
play() {
const { playlist } = this.props;
this.props.requestSinglePlaylist(this.props.playlist.id).then(playlist => this.handleTrack(playlist));
this.props.receivePlaylistId(playlist.id);
}
render() {
const { playlist } = this.props;
return (
<li>
<div className="playlist-image" onClick={ this.handleClick }>
<div className="play-button" onClick={ this.play }>
<div className="triangle right"></div>
<div className="circle"></div>
</div>
<div className="overlay"></div>
<img src={playlist.photo_url} alt="Playlist thumbnail" onClick={ this.handleClick }/>
</div>
<div className="playlist-name">
<Link to={`/playlist/${playlist.id}`}>{ playlist.title}</Link>
</div>
</li>
);
}
}
export default withRouter(UserIndexItem);
// my parent component:
import React from 'react';
import UserIndexItem from './user_index_item';
import { selectTracksFromPlaylist } from '../../reducers/selectors';
class UserIndex extends React.Component {
constructor(props) {
super(props);
}
render() {
const { user, playlists } = this.props;
return(
<div className="user-index-container">
<div className="header">
<h1>{ user.username }</h1>
<h2>Public Playlists</h2>
</div>
<div className="playlists">
<ul>
{ playlists.map(playlist =>
<UserIndexItem
key={ playlist.id }
playlist={ playlist }
requestSinglePlaylist={ this.props.requestSinglePlaylist }
receiveCurrentTrack={ this.props.receiveCurrentTrack }
receiveNextTrack = { this.props.receiveNextTrack }
receiveTitle={ this.props.receiveTitle }
receiveArtist={ this.props.receiveArtist }
receivePlaylistId={ this.props.receivePlaylistId }
receiveAlbumId={ this.props.receiveAlbumId }
/>)
}
</ul>
</div>
</div>
);
}
}
export default UserIndex;
// my route that's using the parent component:
<ProtectedRoute path="/users/:userId" component={UserIndex} />
// my ProtectedRoute implementation:
const Protected = ({ component: Component, path, loggedIn, exact }) => (
<Route path={ path } exact={ exact } render={ (props) => (
loggedIn ? (
<Component {...props} />
) : (
<Redirect to="/welcome" />
)
) }/>
);
You can try like this:
<ProtectedRoute path="/users/:userId" component={props => <UserIndex {...props} />} />
Please let me know if this is working.
Thanks.
I think that {...props} need to call inside UserIndexItem as well.
According to my understand inside the App.js you need to pass {...props} to child component otherwise it don't have parent properties
// this ProtectedRoute should change according to your requirement. I just put sample code
const ProtectedRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={(props) => (
? <Component {...props} />
: <Redirect to="/Login"/>
)} />
)
<ProtectedRoute path="/users/:userId" component={UserIndex} />
// my parent component:
<UserIndexItem
key={ playlist.id }
playlist={ playlist }
requestSinglePlaylist={ this.props.requestSinglePlaylist }
receiveCurrentTrack={ this.props.receiveCurrentTrack }
receiveNextTrack = { this.props.receiveNextTrack }
receiveTitle={ this.props.receiveTitle }
receiveArtist={ this.props.receiveArtist }
receivePlaylistId={ this.props.receivePlaylistId }
receiveAlbumId={ this.props.receiveAlbumId }
{...this.props}
/>
I have implemented an app which uses react-router to handle the routes in my web-app. I want to trigger the function logintoggle which is on the Header.js component from a function from the Hompage.js component. The App.js has all the routes in one file.
Can anyone explain to me how this can be achieved with small code snippet?
App.js
render() {
const { location } = this.props;
return (
<IntlProvider
locale="a"
messages="s"
>
<Fragment>
<div>
<Headers />
<Switch>
<Route exact path="/women" component={HomePage} />
</Switch>
</div>
</Fragment>
</IntlProvider>
);
}
}
export default App;
Header
class Header extends React.Component {
constructor(props) {
super(props);
}
logintoggle(tab) {
if (this.state.activeTab !== tab) {
this.setState({
activeTab: tab
});
}
}
}
Homepage.js
class CheckOut extends Component {
constructor(props) {
super(props);
}
}
When you need to have a shared state among the components React.Context API is what you need. It allows you to create a separate context provider, which will provide the state and the methods to manipulate this state to all the components you need. In the example below I have a LoginContextProvider with activeTab state variable. I provide activeTab and setActiveTab to all the components inside LoginContextProvider's children. Header changes activeTab to 1, Homepage changes to 2 and LoginContextDebug represents the actual activeTab value.
const LoginContext = React.createContext(null);
const LoginContextProvider = ({ children }) => {
const [activeTab, setActiveTab] = React.useState(0);
return (
<LoginContext.Provider value={{ setActiveTab, activeTab }}>
{children}
</LoginContext.Provider>
);
};
const Header = () => {
// Use setActiveTab here
const { setActiveTab } = React.useContext(LoginContext);
return (
<div>
<h1>I am header</h1>
<button onClick={() => setActiveTab(1)}>Set activeTab to 1</button>
</div>
);
};
const Homepage = () => {
// Use setActiveTab here
const { setActiveTab } = React.useContext(LoginContext);
return (
<div>
<h1>I am homepage</h1>
<button onClick={() => setActiveTab(2)}>Set activeTab to 2</button>
</div>
);
};
const LoginContextDebug = () => {
const { activeTab } = React.useContext(LoginContext);
return (
<pre style={{ padding: 10, background: "lightgray" }}>
activeTab={activeTab}
</pre>
);
};
const App = () => (
<LoginContextProvider value={null}>
<Header />
<Homepage />
<LoginContextDebug />
</LoginContextProvider>
);
ReactDOM.render(<App />, document.getElementById('root'));
<script crossorigin src="https://unpkg.com/react#16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.production.min.js"></script>
<div id="root"></div>
In Layout.jsx I´ve the following structure, where children is a Component.
How can I pass 'name' to 'children' component?
const Home = () => {
return <h1>Hello world!</h1>; {/* here I need the name */}
};
class App extends Component {
render() {
return (
<Layout>
<Switch>
<Route path='/' component={Home}/>
...
</Switch>
</Layout>
);
}
}
class Layout extends Component {
name = "John";
render() {
const { children } = this.props;
return (
<div>
{ children }
</div>
);
}
}
React component has to be uppercase. If the child variable holds a React component, you can just store it inside some uppercase variable and then render as a tag.
const { child: Child } = this.props;
return (
<div>
<Child name={this.name} />
</div>
);
Try const { children: Child } = this.props;
My parent component:
class Main extends Component {
constructor(props) {
super(props);
this.state = {
docked: false,
open: false,
transitions: true,
touch: true,
shadow: true,
pullRight: false,
touchHandleWidth: 20,
dragToggleDistance: 30,
currentUser: {}
};
this.renderPropCheckbox = this.renderPropCheckbox.bind(this);
this.renderPropNumber = this.renderPropNumber.bind(this);
this.onSetOpen = this.onSetOpen.bind(this);
this.menuButtonClick = this.menuButtonClick.bind(this);
this.updateUserData = this.updateUserData.bind(this);
}
updateUserData(user){
this.setState({
currentUser: user
})
}
render() {
return (
<BrowserRouter>
<div style={styles.content}>
<div className="content">
<Switch>
<Route path="/login/:code/:state" component={Login} updateUserData = {this.updateUserData}/>
<Route path="/dashboard" component={Login}/>
</Switch>
</div>
</div>
</BrowserRouter>
)
}
}
My child (login) component:
class Login extends Component{
constructor(props) {
super(props);
this.state = {
linkedInUrl: ''
};
}
componentWillMount(){
const query = new URLSearchParams(this.props.location.search);
if(query.get('code') && query.get('state')){
const code = query.get('code');
axios.post(Globals.API + '/user/saveUser', {
code: code,
})
.then((response) => {
if(response.data.success == true){
this.props.updateUserData(response.data.user);
}
})
.catch((error) => {
console.log(error);
})
}
}
render() {
const { linkedInUrl } = this.state;
return (
<div className="panel center-block" style={styles.panel}>
<div className="text-center">
<img src="/images/logo.png" alt="logo" style={styles.logo}/>
</div>
<div className="panel-body">
<a href={linkedInUrl} className="btn btn-block btn-social btn-linkedin">
<span className="fa fa-linkedin"></span>
Sign in with LinkedIn
</a>
</div>
<div className="panel-footer">
</div>
</div>
)
}
I am trying to update the currentUser object from Main component when I get a response in Login component and to also be able to access currentUser object from within all child components of Main (basically from my entire app). But this.props is empty in Login component and I cannot do this.props.updateUserData(response.data.user); either. Can anyone tell me why please? Thank you all for your time!
Because you don't pass any props to Login component. So to get it working you shouldn't use component prop on Route component. Instead of it you should use render prop, which takes a function which returns a component or jsx doesnt matter. More about Route component you can find here.
So replace this route
<Route
path="/login/:code/:state"
component={Login}
updateUserData = {this.updateUserData}
/>
To something like this, using render prop:
<Route
path="/login/:code/:state"
render={() => <Login updateUserData={this.updateUserData} currentUser= {this.state.currentUser} />}
/>
Worked example
Here is more example how to pass props into Route components using react-router.
Hope it will help
I have three components Routes, Menu and a Page, and i'm trying to update Page (scroll it down to the selected part), when clicked on the corresponding menu item.
This is Routes code, it has a state scrollDestination which is passed to Page as a prop and a setScrollDestination method which passed to Menu also as a prop.
export default class Routes extends React.Component {
constructor(props) {
super(props);
this.state = {
scrollDestination: '#part_1'
};
this.setScrollDestination = this.setScrollDestination.bind(this);
}
setScrollDestination(destination) {
this.setState({
scrollDestination : destination
})
}
render() {
return (
<Router onUpdate = {() => {document.body.scrollTop = 0}}>
<div className="router">
<Menu text = {this.props.text.header}
changeLang = {this.props.changeLang}
setScrollDestination = {this.setScrollDestination} />
<Switch>
<Route exact path = "/"
render = {(props) => (
<HomePage text = {this.props.text}
changeLang = {this.props.changeLang}
{...props} />
)} />
<Route path = "/page"
render = {(props) => (
<Page text = {this.props.text}
scrollDestination = {this.state.scrollDestination}
{...props} />
)} />
</Switch>
</div>
</Router>
);
}
}
Menu component pass on destination as a parameter to setScrollDestination function and it is updating Routes' state and also Page's prop.
export default class Menu extends React.Component {
constructor(props) {
super(props);
this.handleCloseClick = this.handleCloseClick.bind(this);
}
handlePageScroll(destination) { this.props.setScrollDestination(destination); }
render() {
return (
<div>
<ul>
<li onClick = {this.handlePageScroll.bind(this, '#part_1')}><Link to="/">Home</Link></li>
<li><Link to="/page">Page
<ul>
<li onClick = {this.handlePageScroll.bind(this, '#part_1')}>part_1</li>
<li onClick = {this.handlePageScroll.bind(this, '#part_2')}>part_2</li>
<li onClick = {this.handlePageScroll.bind(this, '#part_3')}>part_3</li>
</ul></Link>
</li>
</ul>
</div>
);
}
}
Page component must only scroll the page. The state is updating, but it ignors some clicks. It does not always work.
export default class Page extends React.Component {
constructor(props) {
super(props);
}
componentWillReceiveProps(nextProps) {
let scrollDestination = nextProps.scrollDestination;
setTimeout(function() {
TweenMax.to(window, 1, {scrollTo: {y:scrollDestination, offsetY:100}});
}, 400);
}
}
render() {
return (
<div id="part_1"></div>
<div id="part_2"></div>
<div id="part_3"></div>
);
}
}
I've tried all of the updating methods and all of them are work the same. Where can be the issue?
The second thing is that all methods updates the component when ANY props have changed. Is there any way to run methods if one specific prop changed?
Thanks a lot for any answers.