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>
)
}
}
Related
Somebody help me :(
I couldn't find "this.props.Epoint" on the result page.
It's just like "Epoint : " "IPoint : ". Empty, Empty.
I do have to receive "Epoint : 0", "IPoint : ", don't I?
Here is the code. Please save me.
<App.js>
class App extends Component {
state = {
EPoint: 0,
IPoint: 0,
};
upEPoint = async () => {
this.setState({
Epoint: this.state.EPoint ++
})
};
upIPoint = async () => {
this.setState({
Ipoint: this.state.IPoint ++
})
};
render() {
return (
<>
<Router>
<Route exact path="/" component={Home} />
<Route path="/question1" component={() => <Question1 EPoint={this.state.EPoint} IPoint={this.state.IPoint} upEPoint={this.upEPoint} upIPoint={this.upIPoint}/>} />
<Route path="/question2" component={() => <Question2 EPoint={this.state.EPoint} IPoint={this.state.IPoint} upEPoint={this.upEPoint} upIPoint={this.upIPoint}/>} />
<Route path="/question3" component={() => <Question3 EPoint={this.state.EPoint} IPoint={this.state.IPoint} upEPoint={this.upEPoint} upIPoint={this.upIPoint}/>} />
<Route path="/result" component={() => <Result EPoint={this.state.EPoint} IPoint={this.state.IPoint}/>} />
<Router/>
</>
export default App;
<Result.js>
class Result extends Component {
render() {
return (
<div>
<header>
<h1> Result </h1>
<h5> Epoint : {this.props.Epoint}</h5>
<h5> Ipoint : {this.props.Ipoint}</h5>
</header>
</div>)
}
}
export default Result;
I think the first issue here is that you are trying to access Epoint from props, but the variable in state that you are passing down in props is actually EPoint (notice the capital P there). Same goes for IPoint.
Your Result.js should look like this:
import React from "react";
class Result extends React.Component {
render() {
return (
<div>
<header>
<h1> Result </h1>
<h5> Epoint : {this.props.EPoint}</h5>
<h5> Ipoint : {this.props.IPoint}</h5>
</header>
</div>
);
}
}
export default Result;
As the other answers have also mentioned, you cannot set your state as you have.
I am not so good with class components, but I believe you must set it something like the following:
constructor(props) {
super(props);
this.state = { EPoint: 0, IPoint: 0 };
}
u cant use this.state inside setState just get prev state from arrow function then assign it to new object and return it into setState
upIPoint = async () => {
this.setState(prev => ({
Ipoint: prev.IPoint + 1
})
};
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>
}
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}
/>
For some reason after I connect my Routes component with the connect() code - my components are causing re-rendering after clicking them. Can someone help explain what is wrong with this code? After commenting our the connect functions click my buttons cause re-rendering. I removed my import statements to reduce amount of code.
// List of routes that uses the page layout
// listed here to Switch between layouts
// depending on the current pathname
const listofPages = [
/* See full project for reference */
];
class Routes extends React.Component {
constructor(props){
super(props);
this.state = {
hideNavigation: false
};
};
toggleHeader = () => {
const { hideNavigation } = this.state
this.setState({ hideNavigation: !hideNavigation })
};
render(){
const currentKey = this.props.location.pathname.split('/')[1] || '/';
const timeout = { enter: 500, exit: 500 };
// Animations supported
// 'rag-fadeIn'
// 'rag-fadeInRight'
// 'rag-fadeInLeft'
const animationName = 'rag-fadeIn'
return (
// Layout component wrapper
// Use <BaseHorizontal> to change layout
<Base hideNavigation={this.state.hideNavigation}>
<TransitionGroup>
<CSSTransition key={currentKey} timeout={timeout} classNames={animationName} exit={false}>
<div>
<Suspense fallback={<PageLoader/>}>
<Switch location={this.props.location}>
<Route
loggedIn={this.props.isLoggedIn}
path="/login"
render={() => (<Login toggleHeader={this.toggleHeader} />)}
/>
<PrivateRoute
loggedIn={this.props.isLoggedIn}
path="/my-credentials"
component={MyCredentials}
/>
<PrivateRoute
loggedIn={this.props.isLoggedIn}
path="/submenu"
component={SubMenu}
/>
<PrivateRoute
loggedIn={this.props.isLoggedIn}
path="/new-application"
toggleHeader={this.toggleHeader}
component={NewApplication}
/>
{ this.props.isLoggedIn ?
<Redirect to="/submenu"/> :
<Redirect to="/login"/>
}
</Switch>
</Suspense>
</div>
</CSSTransition>
</TransitionGroup>
</Base>
)
}
}
const mapStateToProps = (state) => {
console.log(state)
return {
"isLoggedIn": state.auth.isLoggedIn
}
}
const mapDispatchToProps = dispatch => ({ })
export default connect(
mapStateToProps,
mapDispatchToProps
)(withRouter(Routes));
Change the order of the HOCs. So change
export default connect(mapStateToProps, mapDispatchToProps(withRouter(Routes));
to
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Routes));
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;