How to pass variable to Component received in props? - reactjs

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;

Related

How to set state in parent from one child component and access the state in another child component using react and typescript?

i want to set the state in Parent component on clicking a button in child component. Also i want to access this state in other child component.
what i am trying to do?
On clicking upload button (UploadButton component) i want the state isDialogOpen to be set to true. and i want to access isDialogOpen state in UserButton component.
below is the snippet,
function Main() {
return (
<Wrapper>
<React.Suspense fallback={null}>
<Switch>
<Route
exact
path="/page1"
render={routeProps => (
<Layout>
<React.Suspense fallback={<PlaceHolder></>}>
<child1 {...routeProps} />
</React.Suspense>
</Layout>
)}
/>
<Route
exact
path="/page2"
render={routeProps => (
<Layout>
<Child2 {...routeProps} />
</Layout>
)}
/>
</Switch>
</React>
</Wrapper>
)
}
function Child1() {
return (
<UploadButton/>
);
}
type Props = RouteComponentProps<{ itemId: string; productId: string }>;
function UploadButton({ match }: Props) { //here i set the state isDialogOpen
const [isDialogOpen, setDialogOpen] = React.useState(false);
const handle_click = () => {
setDialogOpen(!isDialogOpen);
};
return (
<>
<Button onClick={handle_click}/>
{isDialogOpen && (
<UploadForm/>
)}
</>
);
}
function Child2() {
return (
<UserButton/>
);
}
function UserButton() {
return (
<Icon/>
);
}
In the above snippet, isDialogOpen state is set in UploadButton component.
Now i want to modify above snippet such that the Icon component in UserButton component is hidden if isDialogOpen is true.
i want to access this isDialogOpen state in UserButton component.
what i have tried?
I can define a function in main component that sets isDialogOpen to true when Upload button is clicked in UploadButton component. but this needs passing the function as prop from main component to Upload Button and similarly passing the state to UserButton from main component.
Is there some neat way to do this? i am new to typescript and react. could someone help me solve this. thanks.
You should define state value and function which update state as props respectively to child components as props. You can take example of the code which I provide bellow
const Child1 = (props) => {
return <div>This is the counter value {props.counter}</div>
}
const Child2 = (props) => {
return <div>
<h2>Here the button to update the counter</h2>
<button onClick={props.update}>
Update counter state in the parent
</button>
</div>
}
class MainComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
counter: 0
}
}
updateCounter = () => {
this.setState({counter: this.state.counter + 1});
}
render() {
return <div>
<Child1 counter={this.state.counter} />
<Child2 update={this.updateCounter} />
</div>
}
}
ReactDOM.render(<MainComponent />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
You can use the same component with context API and React HOOKS like this
import React, { useContext, useState} from 'react';
const CounterContext = React.createContext({
counter: 0
});
const MainComponent = (props) => {
const [counter, setCounter] = useState(0);
const updateCounter = () => {
setCounter(counter + 1);
}
return <CounterContext.Provider value={
counter,
update: updateCounter
}>
<div>
<Child1 />
<Child2 />
</div>
</CounterContext.Provider>;
}
const Child1 = (props) => {
const counter = useContext(CounterContext);
return <div>This is the counter value {counter.counter}</div>
}
const Child2 = (props) => {
const counter = useContext(CounterContext);
return <div>
<h2>Here the button to update the counter</h2>
<button onClick={counter.update}>
Update counter state in the parent
</button>
</div>
}

Why is this.props.history undefined despite having used withRouter?

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

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

Change background of parent after Match

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

React - send function to child via stateless component

I can send a function down to a class component with no problem. But when I want to pass down a function to a stateless component, and from there to a class component, something goes wrong.
It says "this.props.getProduct is not a function" , so I guess some binding thing has to be added, but I already did that in the top component.
The top component is like this:
class App extends Component {
constructor(){
super()
this.state = {
product: {}
}
this.getProduct = this.getProduct.bind(this)
}
getProduct(productId){
const result = productlist.products.filter(obj => {
return obj.id === productId;
})
this.setState({
product: result[0]
})
}
render() {
const {product} = this.state
return (
<div className="App">
<BrowserRouter>
<div>
<Route path="/" render={props =>
<Main
getProduct = {this.getProduct}
product = {this.state.product}
{...props}
/>}
/>
</div>
</BrowserRouter>
</div>
)
}
}
Here is "Main", the intermediate stateless component
const Main = (props) =>
<Route path="/products/:product" render={ props =>
<Product
getProduct={props.getProduct}
product={props.product}
{...props}
/>}
/>
"Product" component where the error occurs
class Product extends Component {
constructor(props) {
super(props)
this.state = {
}
}
componentDidMount() {
this.props.getProduct(this.props.match.params.product) // "not a function"
}
I suspect there is some kind of extra binding that needs to be done, but how?
Here is a sandbox where you can see the problem:
codesandbox
You have a collision here:
const Main = (props) =>
<Route path="/products/:product" render={ props =>
<Product
getProduct={props.getProduct}
product={props.product}
{...props}
/>}
/>
props from Main and props in render, so when you are passing in props from it takes props from the render function you passed instead of props from Main. You have to rename one, or replace const Main = (props) with const Main = ({ getProduct, product}) and then pass it to
The route render function props argument is shadowing the Main component's props variable. Better change variable name of render argument
const Main = (props) =>
<Route path="/products/:product" render={ p =>
<Product
getProduct={props.getProduct}
product={props.product}
{...props}
/>}
/>
This should do the job:
class App extends Component {
constructor() {
super()
this.state = {
product: {},
}
this.getProduct = this.getProduct.bind(this)
}
getProduct(productId) {
const result = productlist.products.filter(obj => {
return obj.id === productId
})
this.setState({
product: result[0],
})
}
render() {
const { product } = this.state
return (
<div className="App">
<BrowserRouter>
<div>
<Route
path="/products/:product" render={props =>
<Product
getProduct={props.getProduct}
product={props.product}
{...props}
/>}
/>
</div>
</BrowserRouter>
</div>
)
}
}

Resources