React-router: Pass props in Link and map match data - javascript

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 !

Related

change Component name with map function

I am trying to change component name.
It is normally like <Dashboard/> and <Table/>. But I want to make like
const names = [ {"name":Dashboard},{"name":Table}]
names.map(c => { <c.name />}
render(){
return(
{names.map(c => {
<Panel>
<Panel.Body>
<Row>
<Col md={4}>
<FormControl
/>
</Col>
</Row>
<hr />
<c.name bla={bla} />
<hr />
</Panel.Body>
</Panel>
)}
According to the doc, you need to declare a capitalized variable first:
import React from 'react';
import { PhotoStory, VideoStory } from './stories';
const components = {
photo: PhotoStory,
video: VideoStory
};
function Story(props) {
// Correct! JSX type can be a capitalized variable.
const SpecificStory = components[props.storyType];
return <SpecificStory story={props.story} />;
}
and then you can call your component name by using your variable.
And in your case, it would be :
{names.map((c) => {
const CapitalizedComponent = c.name;
return <CapitalizedComponent />;
})}
Please also notice that the map function in your code didn't return anything.
working example in sandbox

Warning: Cannot update a component while rendering a different component. ReactJS

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.

React js. Cant' get value of undefined, displayName of user. I have used props to use the user state in this file but it can't access any of user data

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

Redux store not connected

I am developing a Reactjs web application from scratch and encountered a tricky situation which i need help with. Whenever i navigate away from a particular url and navigate back, my redux store does not seem to be connected.
routes.js
const RouteList = () => (
<main>
<Switch>
<Route path="/abc/" exact component={withRouter(HomePage)} />
<Route path="/abc/xyz" exact component={withRouter(XYZPage)} />
<Redirect from="/" to="/abc/" />
<Route component={Error} />
</Switch>
</main>
);
export default RouteList;
App.js
class App extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
render () {
return (
<Router history={browserHistory}>
<div>
<Header />
<RouteList />
<Footer />
</div>
</Router>
);
}
}
export default App;
Header.js
const Header = () => {
return (
<Navbar expand="md">
<NavbarBrand tag={NavLink} to="/">
<img src={brandImage} style={{marginRight: "0", width: "40px", height: "40px"}} /><strong style={{color: "#457B9D"}} >Datum</strong>
</NavbarBrand>
<Nav className="mr-auto" navbar>
<NavItem>
<NavLink className="nav-link" to={"/abc/xyz"} >XYZ</NavLink>
</NavItem>
</Nav>
</Navbar>
);
};
export default withRouter(Header);
When i hit the NavLink which will take me to url: /"abc/xyz", it will take me to XYZPage.js
XYZPage.js
class XYZPage extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
activeTab: "1"
};
this.toggle = this.toggle.bind(this);
}
toggle(tab) {
if (this.state.activeTab !== tab) {
this.setState({
activeTab: tab
});
}
}
render () {
return (
<main>
<div className="container-fluid pt-3">
<Nav tabs>
<NavItem>
<NavLink
className={classnames({active: this.state.activeTab === "1"})}
onClick={() => {this.toggle("1"); }} >
AAA
</NavLink>
</NavItem>
<NavItem>
<NavLink
className={classnames({active: this.state.activeTab === "2"})}
onClick={() => {this.toggle("2"); }} >
BBB
</NavLink>
</NavItem>
<NavItem>
<NavLink
className={classnames({active: this.state.activeTab === "3"})}
onClick={() => {this.toggle("3"); }} >
CCC
</NavLink>
</NavItem>
</Nav>
<TabContent activeTab={this.state.activeTab}>
<TabPane tabId="1">
<Row>
<AAAPAge/>
</Row>
</TabPane>
<TabPane tabId="2">
<Row>
<BBBPage/>
</Row>
</TabPane>
<TabPane tabId="3">
<Row>
<CCCPage/>
</Row>
</TabPane>
</TabContent>
</div>
</main>
);
}
}
export default withRouter(XYZPage);
Each of the AAAPage, BBBPage & CCCPage are components which needs to have some pre-populated dropdowns which i declared in my index.js below:
index.js
const store = configureStore();
store.dispatch(loadAAA());
store.dispatch(loadBBB());
store.dispatch(loadCCC());
render((
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>
), document.getElementById('app'));
loadAAA, loadBBB & loadCCC are all thunks
The configureStore() method is as such:
export default function configureStore(initialState) {
return createStore(
rootReducer,
initialState,
composeWithDevTools(
applyMiddleware(thunk, reduxImmutableStateInvariant()),
)
);
}
To shorten this post i give a sample of my AAAPage as the others are of similar structure:
AAAPage.js:
class AAAPage extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {...};
}
componentWillReceiveProps(nextProps) {...}
render() {
[...]
return (
<Container fluid>
<Row>
<AAAInputForm
// Data from Store is passed here
/>
</Row>
{ChildComponent}
</Container>
);
}
}
AAAPage.propTypes = {
DATA: PropTypes.array
};
function mapStateToProps(state, ownProps) {
let DATA = [];
if (state.AAAReducer.length > 0) {
DATA = state.AAAReducer;
}
return {
DATA: DATA
};
}
export default withRouter(connect(mapStateToProps)(AAAPage));
AAAReducer.js:
export default function AAAReducer(state=initialState.AAAList, action) {
switch(action.type) {
case types.LOAD_AAA_SUCCESS:
return action.AAAList;
default:
return state;
}
}
AAAAction.js:
export function loadAAASuccess(AAAList) {
return {
type: types.LOAD_AAA_SUCCESS,
AAAList: AAAlList
};
}
// thunk
export function loadAAA() {
// A thunk will always return a function that accepts a dispatch
return function(dispatch) {
return apiCall("ALL").then(response => {
dispatch(loadAAASuccess(response.data.AAA));
}).catch(error => {
throw(error);
});
};
}
initialState.js:
export default {
AAAList: [],
BBBList: [],
CCCList: []
};
At this point i believe i provided enough background to my code. I followed tutorials when designing this redux store and I am not sure why when i navigate from "/abc/xyz" to "/abc" and back, or when i navigate to "/abc/xyz" from "/abc", my stores are empty although i called the loadAAA() method at my index.js. All the other pages are affected as well. However, when i hit "/abc/xyz" directly, my stores are connected and my dropdowns are populated. What is happening? Is it because of my lifecycle methods?
I am using react v15.6.2, redux v3.7.2 & redux-thunk v2.3.0.
Thanks for the guidance.
You only call loadAAA at the top level of index.js, which only executes once when your page loads. If you want to dispatch it every time your XYZPage page renders, put in XYZ's componentDidMount
#AKJ - #Andy Ray said it correctly, but I'll like to add that componentDidMount is the best place to load async calls, as it is called after render and about Store redux store keeps data until you refresh the page after refresh redux store is reinitialized, if you need store the data after refresh try redux-persist

How to filter the props for to render with ReactJS?

I have a code that get data of a json-server. And to render seven names in the screen. I want to filter by input and to render only the elements filtereds.
My Apps.js:
class AppRouter extends React.Component {
state = {
employeeCurrent: [],
employee: []
};
componentDidMount() {
axios
.get("http://127.0.0.1:3004/employee")
.
then(response => this.setState({ employee: response.data }));
}
add = name => {
this.setState(prevState => {
const copy = prevState.employeeCurrent.slice();
copy.push(name);
return {
employeeCurrent: copy
};
});
};
render() {
return (
<Router>
<div className="router">
<Route
exact
path="/"
render={props => (
<Home
{...props}
add={this.add}
employee={this.state.employee}
currentEmployee={this.state.currentEmployee}
/>
)}
/>
<Route
path="/user/:id"
component={props => (
<User
{...props}
employee={this.state.employee}
currentEmployee={this.state.currentEmployee}
/>
)}
/>
</div>
</Router>
);
}
}
My body.js (Where has the function for to render)
class Body extends React.Component {
getName = () => {
const { employee, add } = this.props;
return employee.map(name => (
<Link className="link" to={`/user/${name.name}`}>
{" "}
<div onClick={() => add(name)} key={name.id} className="item">
{" "}
<img
className="img"
src={`https://picsum.photos/${name.name}`}
/>{" "}
<h1 className="name"> {name.name} </h1>
</div>{" "}
</Link>
));
};
render() {
return <div className="body">{this.getName()}</div>;
}
}
I tried to pass the state to the App. JS but had no success. I tried everything in the Body. JS but also not succeeded. Could someone help me how to do this?
I'm on the phone so there are some things that are bad to indent. Sorry!
Try this,
class Body extends React.Component {
getName = () => {
const { employee, add } = this.props;
//Filter names in employee array with input
const filterNames = employee.filter(x => x.name === "check with the input value" );
// Then map over the filtered names
return filterNames.map(name => (
<Link className="link" to={`/user/${name.name}`}>
{" "}
<div onClick={() => add(name)} key={name.id} className="item">
{" "}
<img
className="img"
src={`https://picsum.photos/${name.name}`}
/>{" "}
<h1 className="name"> {name.name} </h1>
</div>{" "}
</Link>
));
};
render() {
return <div className="body">{this.getName()}</div>;
}
}

Categories

Resources