In my ReactHooks/Typescript app, I have a Navigation component, that renders a PatientInfo component. The PatientInfo child is rendered conditionally based on what props it is passed, as decided by a searchbox in another child component - MyPatients.
In this structure, I am getting the following error:
Navigation.tsx:
// code....
<Route exact path="/" component={MyPatients} />
<Route
exact
path="/Pasient"
render={() => (
<PatientInfo
setName={setName}
setSchema={setSchema}
patientID={patientID}
/>
)}
/>
// code....
MyPatients:
const MyPatients = (props: { history: History }) => {
localStorage.clear();
const [patientID, setPatientID] = useState(
localStorage.getItem('myData') || '',
);
useEffect(() => {
localStorage.setItem('myData', patientID);
}, [patientID]);
return (
<>
<div className="search-container"></div>
<Row gutter={[60, 40]} justify={'center'}>
<Col span={1000}>
<p>Søk med personnummer for å finne en pasient</p>
<Search
style={{ width: 400 }}
className="search-bar"
placeholder="Søk etter en pasient!"
onSearch={(value: string) => setPatientID(value)}
/>
</Col>
</Row>
{patientID &&
props.history.push({ pathname: 'Pasient', state: patientID })}
</>
);
};
export default MyPatients;
I am not familliar with this issue, and don't understand what's happening. My educated guess is that React doesn't like the fact that the state of the parent component is being updated by functions passed to the children, which again are dependant on the props passed along with it. Am I on to something? Any ideas as to what is causing this if not?
Any help is appreciated.
You are navigating with history.push on each render.
As #HMR mentioned in the comment, you have to remove navigation from JSX template and add it into a separate effect.
const MyPatients = (props: { history: History }) => {
localStorage.clear();
const [patientID, setPatientID] = useState(
localStorage.getItem("myData") || ""
);
useEffect(() => {
localStorage.setItem("myData", patientID);
}, [patientID]);
// separate effect here
useEffect(() => {
if (patientID) {
props.history.push({ pathname: "Pasient", state: patientID });
}
}, [props, patientID]);
return (
<>
<div className="search-container"></div>
<Row gutter={[60, 40]} justify={"center"}>
<Col span={1000}>
<p>Søk med personnummer for å finne en pasient</p>
<Search
style={{ width: 400 }}
className="search-bar"
placeholder="Søk etter en pasient!"
onSearch={(value: string) => setPatientID(value)}
/>
</Col>
</Row>
</>
);
};
export default MyPatients;
EDIT
This might cause your error:
<PatientInfo
setName={setName}
setSchema={setSchema}
patientID={patientID}
/>
If you call setName or setSchema on render of PatientInfo then Navigation state gets updated before PatientInfo render is finished.
Related
My Router is a simple component containing public and private routes. I have created an AuthRoute referring to the great tutorial from here
So, my Router looks like:
<Router>
<div>
<Navigation />
<Route exact path={ROUTES.LANDING} component={Landing} />
<Route path={ROUTES.SIGN_UP} component={SignUp} />
<Route path={ROUTES.SIGN_UP_SUCCESS} component={SignUpSuccess} />
<AuthenticationRoute path={ROUTES.HOME} component={Home} />
</div>
</Router>
and my AuthenticationRoute looks like this:
export const AuthenticationRoute = ({ component: Component, ...rest }) => {
const [authChecking, setAuthChecking] = useState(true);
const [{ isAuth }, dispatch] = useStateValue();
useEffect(() => {
checkLoggedIn().then(res => {
setAuthChecking(false);
dispatch({
op: 'auth',
type: 'toggleSessionAuth',
toggleSessionAuth: res
});
});
}, [])
if(authChecking)
return null;
if(!isAuth) {
return <Redirect to='/' />;
}
return <Route {...rest} render={(props) => (
<Component {...props} />
)
} />
}
Everything looks fine, however, my console returns such warning:
Warning: Functions are not valid as a React child. This may happen if you return a Component instead of <Component /> from the render. Or maybe you meant to call this function rather than return it.
I have tried different solutions using component/render etc, however, I could not find a solution to this problem and I have no idea what I am doing wrong.
For testing purposes, instead of rendering Component, I tried to render simple <div>test</div> and it worked fine. However, when I am passing a JSX component in props, it returns the warning shown above.
Implementation oh Home Component (Home.js):
export const Home = () => {
const [{ user }, dispatch] = useStateValue();
const { history } = useReactRouter();
const moveTo = path => {
dispatch({
op: 'other',
type: 'setView',
setView: path
});
history.push(path);
}
return (
<div className="pageMenuWrapper">
<h1 className="homeTitle">Hi {() => user ? `, ${user.username}` : ``}.</h1>
<div className="wrapper">
<Tile image={leagueico} alt="text" onClick={() => moveTo(ROUTES.TEST)}/>
<Tile comingSoon />
</div>
</div>
);
}
export default Home;
Could anyone help me solve this little problem?
I can't display users data such as name when he is logged in. I have used props and state user as currentUser but i am unable to access these fields since the error says that it can't read property of undefined.
class UserPanel extends React.Component {
state = { user: this.props.currentUser }
dropdownOptions = () => [
{
key: "user",
text: (
<span>
Sign in as <strong>{this.state.user.displayName}</strong>
</span>
),
disabled: true
},
{
key: "avatar",
text: <span>Change Avatar</span>
},
{
key: "signout",
// Set a signout Function to enable user to sign out of the chat
text: <span onClick={event => this.handleSignOut(event)}>SignOut</span>
}
];
handleSignOut = (event) => {
// You need to prevent form submission. Use event.preventDefault() in your handle submit function.
event.preventDefault();
firebase
.auth()
.signOut()
.then(() => console.log("See you"));
}
render(){
console.log(this.props.currentUser);
return (
<Grid style={{ background: '#4c3c4c' }}>
<Grid.Column>
<Grid.Row style={{ padding: '1.2rem', margin: 0 }}>
<Header inverted floated='left' as='h2'>
<Icon name='code' />
<Header.Content>VirtualChat</Header.Content>
</Header>
</Grid.Row>
{/* User Dropdown Choices */}
<Header style={{ padding: "0.25em" }} as="h4" inverted>
<Dropdown
trigger={<span>{this.state.user.displayName}</span>}
options={this.dropdownOptions()}
/>
</Header>
</Grid.Column>
</Grid>
)
}
}
// index.js
const store = createStore(rootReducer, composeWithDevTools());
// change root component to a statefull component
class Root extends React.Component {
componentDidMount() {
firebase.auth().onAuthStateChanged(user => {
// If firebase has detect a user
if (user) {
// console.log(user);
this.props.setUser(user);
// We will redirect them to the home Route
this.props.history.push("/");
} else {
// In case user signout
this.props.history.push('/login');
this.props.clearUser();
}
});
}
render(){
return this.props.isLoading ? <Spinner /> : (
// All of our indivicuals routes will be nested in switch component which is nested to router component
<Switch>
{/* Root route of the app, we first set the path and then which component we watn */}
{/* We added exact keyword in order to secure that the main route will not match multiple components */}
<Route exact path="/" component={App} />
{/* Create routes for Login and Register */}
<Route path="/login" component={Login} />
<Route path="/register" component={Register} />
</Switch>
);
}
}
// To get loading data from our state object to see when user actions is loaded
const mapStateFromProps = state => ({
isLoading: state.user.isLoading
});
const RootWithAuth = withRouter(
connect(
// Using mapStateFromProps because, since state update are asynchronous and take some amount of time
mapStateFromProps,
{ setUser, clearUser }
)(Root)
);
// We render root because app is now our route
// In order to provide this global state/store to the other components we wrap the router in to a provider
// Provider will provide this global state to any component who want to make use of it
ReactDOM.render(
<Provider store={store}>
<Router>
<RootWithAuth />
</Router>
</Provider>,
document.getElementById('root')
);
registerServiceWorker();
I think that the value is undefined because you are not checking if the props has a value maybe the data that your are trying to render is not ready or is async. To handle this you can set your state in a componentDidMount so if the state.currentUser is null it means that the data isn't ready and you can render a loader or something similar.
class UserPanel extends React.Component {
state = { user: null }
dropdownOptions = () => [
{
key: "user",
text: (
<span>
Sign in as <strong>{this.state.user.displayName}</strong>
</span>
),
disabled: true
},
{
key: "avatar",
text: <span>Change Avatar</span>
},
{
key: "signout",
// Set a signout Function to enable user to sign out of the chat
text: <span onClick={event => this.handleSignOut(event)}>SignOut</span>
}
];
handleSignOut = (event) => {
// You need to prevent form submission. Use event.preventDefault() in your handle submit function.
event.preventDefault();
firebase
.auth()
.signOut()
.then(() => console.log("See you"));
}
componentDidMount(){
this.setState({ user: this.props.currentUser })
}
render(){
if( !this.state.user){
return <div>Curernt User doesnt exist!</div>
}
return (
<Grid style={{ background: '#4c3c4c' }}>
<Grid.Column>
<Grid.Row style={{ padding: '1.2rem', margin: 0 }}>
<Header inverted floated='left' as='h2'>
<Icon name='code' />
<Header.Content>VirtualChat</Header.Content>
</Header>
</Grid.Row>
{/* User Dropdown Choices */}
<Header style={{ padding: "0.25em" }} as="h4" inverted>
<Dropdown
trigger={<span>{this.state.user.displayName}</span>}
options={this.dropdownOptions()}
/>
</Header>
</Grid.Column>
</Grid>
)
}
}
You call this.props.state.user instead of this.state.user
I'm trying to figure out what I'm not doing well.
I have a shops object which is a database with different shop inside.
I want to render each shop's information in their own page thanks to react-router.
I already try many way to render my details.
The error that always come back its that I can read props, or state of undefined in my shopDetails component. When I want to console log my location element, it is shown as undefined but when I go to my react developer tool I can see my shops data right stored in my shopDetails props.location...
I really don't understand how to render the good data. I open all other subject without understanding how to deal with my problem.
If you could help on this, it would be amazing. Thanks for your time.
App.js
render() {
return (
<Router>
<HeaderFilters
wrapperHeaderFunction={this.wrapperHeaderFunction}
zip_code={this.state.zip_code}
handleChanges={this.handleChanges}
isClicked={this.isClicked}
filterClick={this.filterClick}
selectedOption={this.state.selectedOption}
moreFilterClick={this.moreFilterClick}
filteredResults={this.state.filteredResults}
rating={this.state.rating}
startDate={this.state.startDate} // momentPropTypes.momentObj or null,
startDateId="your_unique_start_date_id" // PropTypes.string.isRequired,
endDate={this.state.endDate} // momentPropTypes.momentObj or null,
endDateId="your_unique_end_date_id" // PropTypes.string.isRequired,
onDatesChange={({ startDate, endDate }) =>
this.setState({ startDate, endDate })
} // PropTypes.func.isRequired,
focusedInput={this.state.focusedInput} // PropTypes.oneOf([START_DATE, END_DATE]) or null,
onFocusChange={focusedInput => this.setState({ focusedInput })} // PropTypes.func.isRequired,
/>
{this.state.isMoreFiltersRequired ? (
<MoreFilters
handleChanges={this.handleChanges}
isClicked={this.isClicked}
filterClick={this.filterClick}
moreFilterClick={this.moreFilterClick}
filteredResults={this.state.filteredResults}
rating={this.state.rating}
/>
) : null}
<div>
{this.state.login ? <Spinner animation="border" size="xl" /> : null}
</div>
<Switch>
<Route
exact
path="/"
render={() => (
<ShopPreview
loading={this.state.loading}
shops={this.state.shops}
filteredResults={this.state.filteredResults}
rating={this.state.rating}
/>
)}
/>
<Route
path="/search"
render={() => (
<ShopSearch
loading={this.state.loading}
shops={this.state.shops}
filteredResults={this.state.filteredResults}
rating={this.state.rating}
/>
)}
/>
<Route
path={`/shopDetail/:id`}
render={routeProps => (
<ShopDetails {...routeProps} shops={this.state.shops} />
)}
/>
</Switch>
</Router>
);
}
}
export default App;
Shops.js (the component which renders the shop list)
render() {
return (
<Container>
<ListGroup>
{this.props.shops.map((detail, index) => (
<ListGroup.Item key="index">
<Row>
<Col>
<Image
alt=""
src={detail.imgURL}
width={150}
height={150}
rounded
/>
</Col>
<Col>
<h3 className="shop_title">{detail.nom}</h3>
<StarRatings
rating={this.props.rating}
starRatedColor="#DAA520"
changeRating={this.changeRating}
numberOfStars={5}
starDimension="15px"
name="rating"
starSpacing="2px"
/>
<p id="resume">{detail.resume}</p>
</Col>
<Col>
<Row>
{detail.startPrice === ""
? "Sur devis"
: "A partir de " + detail.startPrice + " €"}
</Row>
<Row>
{/* Make route with id, with key= detail.id */}
<Link
to={{
pathname: "/shopDetail/" + detail.id,
state: {shops : this.props.shops}
}}
>
<Button
className="detailButton"
key={detail.id}
variant="primary"
onClick={this.props.filterClick}
>
Détails
</Button>
</Link>
</Row>
</Col>
</Row>
</ListGroup.Item>
))}
</ListGroup>
</Container>
);
}
}
export default Shops;
ShopDetails.js (the component which renders the shop details according to its URL id)
import React, { Component } from 'react'
class ShopDetails extends Component {
constructor(props){
super(props)
this.setState({
})
}
render() {
console.log("Props shops: " ,this.props.shops)
const id = window.location.pathname.replace("/shopDetail/", "");
const data = this.props.shops
const location = this.props.location
console.log("Location:", location)
const shop = data.find(s => s.id === id)
return (
<div>
<h1>{shop.id}</h1>
<h3>{shop.nom}</h3>
<p>{shop.website}</p>
</div>
)
}}
export default ShopDetails
For now, I'm just allow to render the id, but I can't access to my elements "shops" in my state which stock my shop data to map on each component.
edit:
SCREENSHOT CONSOLE.LOG
edit2:
import React, { Component } from 'react'
class ShopDetails extends Component {
constructor(props){
super(props)
this.setState({
shop:{}
})
}
render() {
console.log("Props shops: " ,this.props.shops)
const id = window.location.pathname.replace("/shopDetail/", "");
console.log("id: ", id)
const data = this.props.shops
console.log("data: ", data)
const location = this.props.location.state
console.log("Location:", location)
const shop = data.find(s => s.id === id)
return (
<div>
</div>
)
}}
export default ShopDetails
Edit3
Screen log object developed1
Edit3
Screen log object developed2
Edit3
Screen log object developed3
Edit 4:
const shop, can finally be render something in console.log
Problem was about a triple = in my const shop = data.find(s => s.id == id)
import React, { Component } from 'react'
class ShopDetails extends Component {
constructor(props){
super(props)
this.setState({
shop:{}
})
}
render() {
console.log("Props shops: " ,this.props.shops)
const id = window.location.pathname.replace("/shopDetail/", "");
console.log("id: ", id)
const data = this.props.shops
console.log("data: ", data)
const shop = data.find(s => s.id == id)
console.log("shop: ", shop)
console.log("this.props.match.params.id: ", this.props.match.params.id)
return (
<div>
{shop.map((detail, index) => (
<div key={index}>
<h1>{detail.nom}</h1>
</div>
))}
<p>{data.id}</p>
</div>
)
}}
export default ShopDetails
Now I have to return my data stored in my shop const, see below the link of the console.log(shop) since the edit 4
screenshot edit4
You have to set the == instead of === because the id of your shop is a number and the id from your url is a string. If you cast the id from your url to number, it should also work with ===. To render your shop data, after you find it, should not be done with map since you cannot access the object keys with it. You should just render it with shop.nom etc. Hope this helps. Happy coding.
Finally, solve my problem !
So first I had this problem with my === operator as well explain Domino987 previously.
Then if I couldn't render my {shop.nom} element even if I could see my shop element in my props.
click to see my console.log("const shop = data.find(...): ", shop);
It is because at the moment the component renders, there is not value inside shop.
We can know that by looking at the 'i' icon in my dev tools.
So I had 2 options:
You have 2 options:
Put all my information in state, not like shop={}, but like bornePhoto, cabinePhoto, helio, booth… etc, then it first renders with empty information, and then when the information arrives, it updates the state and it will shown.
Conditional render. Example:
if (!shop.nom){
return null
} else {
return <h1>{shop.nom}<h1/>
}
OR :
{Boolean(shop.nom) ? <h1>{shop.nom}<h1/> : null}
I used ternary method with the following code:
class ShopDetails extends Component {
constructor(props) {
super(props);
this.state = { }
}
render() {
console.log("Props shops: ", this.props.shops);
const id = this.props.match.params.id;
console.log("id: ", id);
const data = this.props.shops || {};
console.log("data: ", data);
const shop = data.find(s => s.id == id);
console.log("const shop = data.find(...): ", shop);
return (
<div className="container">
<Row>
<Col>
{shop ? (
<Image
alt=""
src={shop.imgURL}
width={150}
height={150}
rounded
/>
) : null}
</Col>
<Col >
{shop ? <h1>{shop.nom}</h1> : null}
{shop ? <h2>A partir de {shop.startPrice} €</h2> : null}
</Col>
</Row>
<Row >
<Col >
<h3>Site internet:</h3>
{shop ? <p>{shop.website}</p> : null}
</Col>
<Col >
<h3>Services</h3>
<p>Rappel des services</p>
</Col>
</Row>
<Row >
<Col>
<h4>Présentation</h4>
{shop ? <p>{shop.resume}</p> : null}
</Col>
</Row>
</div>
);
}
}
export default ShopDetails;
That solve my problem, and I could finally render my elements.
If that could help someone, I will be glad !
Thanks to all the community for helping me in my project !
const Create = () => {
console.log('rerender !!')
const [parcelType, setParcelType] = useState('paper')
console.log('parcelType =', parcelType)
return (
<Container onClick={() => setParcelType('plastic')}>
<BookingList />
<Card title="Business">
<p>Header</p>
</Card>
</Container>
)
}
export default Create
I want to change parcelType state to 'plastic' when click on Container in Create component. and I want to reset parcelType state to 'paper' when route is change ( Create component re-render ). But when component re-render state is not set to paper
For more details: CreateComponent is re-render when route is change in BookingList component
const BookingList = props => {
const { id } = props.match.params
const containerStyle = useTranslateSpring('-100px', '0')
const itemList = items.map((item, idx) => {
const itemStyle = useTranslateSpring('-100px', '0', '0', 200 + 200 * idx)
const url = `/booking/${item.id}/create`
return (
<ItemContainer
onClick={() => props.history.push(url)}
style={itemStyle}
key={item.id}
isactive={id === item.id}
>
{item.id}
</ItemContainer>
)
})
return <Container style={containerStyle}>{itemList}</Container>
}
export default withRouter(BookingList)
Create Component is render in route by routeTemplate
const Routes = () => (
<Router basename={process.env.REACT_APP_BASE_URL}>
<>
<RouteTemplate
exact
path="/booking/:id/create"
component={Booking.create}
title="Booking"
/>
</>
</Router>
)
and RouteTemplate is render Component wrapped by PageTemplate component
const RouteTemplate = props => {
const {
component: Component,
title,
query,
isAuthenticated,
isLanding,
...rest
} = props
return (
<Route
{...rest}
render={matchProps =>
isAuthenticated ? (
<PageTemplate title={title} isLanding={isLanding}>
<Component {...matchProps} query={query} />
</PageTemplate>
) : (
<Redirect
to={{
pathname: '/',
state: { from: props.location },
}}
/>
)
}
/>
)
}
So I assume you want to reset component's state once route is changed.
This should happen wherever you use functional component + hooks or class-based component with explicit this.state. It's how React works under the hood.
You already have <Create> rendered at the page
Once route is changed <Route> tries to render <Create> element
React sees there is already existing <Create> element and tries to update that instead of re-creating(typically update is much more efficient than re-creating). That's why state is not reset - since it should not reset for updates.
There are different way to handle that.
If such a case happen outside react-router's <Route> I'd suggest use key prop to reset state. But for <Route> it would mean replacing more clear/straightforward <Route path="..." component={Create} /> with more verboose <Route path="..." render={({match}) => <Create match={match} key={match.params.id} />}
So instead let's apply useEffect hook to reset state once props.match.params.id is changed:
const Create = ({ match: {params: {id} } }) => {
useEffect(() => {
setParcelType('paper');
}, [id]);
That should be equal to class-based
state = {
typeOfWhatEver: 'paper'
};
componentDidUpdate(prevProps) {
if(prevProps.match.params.id !== this.props.match.params.id) {
this.setState({
typeOfWhatEver: 'paper'
});
}
}
So, I am rendering different components based on the URL using BrowserRouter and Route. But, There is a lot of markup which is similar while rendering. So, I have created a Wrapper which should take components as props and solve this!!
class Wrapper extends React.Component {
componentDidMount() {
this.props.setActiveTab(this.props.activeTab);
console.log('Wrapper props: ', this.props)
}
render() {
const OtherComponent = this.props.otherComponent
return (
<Row>
<Col md='8' id='content-block'>
<SwitchTab />
</Col>
<Col md='4' id='info-block'>
<InfoComponent info={this.props.info} {...this.props}/>
{
this.otherComponent &&
<OtherComponent {...this.props}/>
}
</Col>
</Row>
)
}
}
These are some of the Routes:
<Route
exact
path='/r/all/'
render={() =>
<Wrapper
setActiveTab={context.toggleTab}
activeTab={'3'}
info='all'
/>
}
/>
<Route
exact
path='/u/:username/'
render={(props) => {
return (
<Wrapper
setActiveTab={context.toggleTab}
activeTab={'4'}
info='user'
user={props.match.params.username}
otherComponent={Profile}
username={props.match.params.username}
/>
// <Profile username={props.match.params.username} />
)
}}
/>
<Route
exact
path='/r/:subreddit/'
render={(props) => {
return (
<Wrapper
setActiveTab={context.toggleTab}
activeTab={'4'}
info='subreddit'
otherComponent={Subreddit}
subreddit={props.match.params.subreddit}
/>
// <Subreddit subreddit={props.match.params.subreddit} />
)
}}
/>
The otherComponent is not getting rendered. I don't know where the problem is. Also if there is any other better method, please do state that.
You are checking if this.otherComponent is truthy before rendering. You just want to check if OtherComponent is truthy.
{OtherComponent && <OtherComponent {...this.props} />}
You could also change Wrapper to render children if you would prefer.
Example
class Wrapper extends React.Component {
componentDidMount() {
this.props.setActiveTab(this.props.activeTab);
console.log("Wrapper props: ", this.props);
}
render() {
const { children, ...restProps } = this.props;
return (
<Row>
<Col md="8" id="content-block">
<SwitchTab />
</Col>
<Col md="4" id="info-block">
<InfoComponent {...restProps} />
{children}
</Col>
</Row>
);
}
}
// Usage
<Wrapper>
<OtherComponent />
</Wrapper>
this.otherComponent &&
<OtherComponent {...this.props}/>
A quick look, this.otherComponent is not defined so the component is not getting rendered, should be this.props.otherComponent??