I have a code that is a component in react which is used Redux to get data from server. this is my very simple component:
class Survey extends React.Component {
constructor(props) {
super(props);
}
componentWillMount() {
this.props.getSurvey(this.props.match.params.uuid);
}
render() {
const { survey } = this.props;
const { name } = survey; // << *********** ERROR **********
return (
<Hoc>
<Card title="Name" bordered={false} style={{ width: 300 }}>
<p>Card content</p>
<p>Card content</p>
<p>Card content</p>
</Card>
</Hoc>
);
}
}
const mapStateToProps = state => {
return {
survey: state.survey.currentSurvey
};
};
const mapDispatchToProps = dispatch => {
return {
getSurvey: uuid => dispatch(surveyGetData(uuid))
};
};
export default withRouter(
connect(
mapStateToProps,
mapDispatchToProps
)(Survey)
);
But when i've used this component it gave me an error which said "survey" is null. i tackled about this issue and i've found out when my getSurvey function in componentWillMount run my render method execute before it goes trough redux action to do process of changing the state and pass it to my component to render and there is my problem.
is there any better way to get through this problem without using a big condition?
Your default value for survey probably is null. So when the component renders the first time 'survey' in const { survey } = this.props; is null. The request to get the survey is async so the response might/will come after the initial render.
One solution might be to have a 'loading' flag on your state. While true you can show a loader when rendering the component. You can set the flag true when you fire the request to get the survey and back to false if the request has finished. At that point the survey should be set and you can render your component as you wish. Of course the request response must be succesfull.
Its because your const { survey } = this.props; is null. For handling this you need some kind of check i.e
const { survey } = this.props;
if(!survey) return null;//ensure component does not render if survey not found
const { name } = survey; //It means we find survey here we can move for next logic
Related
I am trying to render a simple profile from my redux store. Actually, the data is successfully provided and mapped to my props. I can log the profile information in the console. But when I render the profile data, it tells me that the data is not defined.
When I console log the profile , it says undefined first and then displays the data (see down). I wonder if react tries to render in the moment the data is undefined.
Console Log
undefined profile.js:28
Object profile.js:28
bio: "Test"
id: 2
image: "http://localhost:8000/media/default.jpg"
Component
export class ProfileDetail extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
this.props.getProfile(this.props.match.params.id);
}
static propTypes = {
profile: PropTypes.array.isRequired,
getProfile: PropTypes.func.isRequired,
};
render() {
const profile = this.props.SingleProfile;
console.log (profile)
return (
<Fragment>
<div className="card card-body mt-4 mb-4">
<h2>{profile.bio}</h2> // if i use here just a string its successfully rendering
</div>
</Fragment>
);
}
}
function mapStateToProps(state, ownProps) {
const { profile} = state.profile
const id = parseInt(ownProps.match.params.id, 10)
const SingleProfile = profile.find(function(e) {
return e.id == id;
});
return { profile, SingleProfile}
}
export default connect(
mapStateToProps,
{ getProfile}
)(ProfileDetail);
Im a little bit lost and happy for any clarification.
You need to check if profile is loaded.
It is normal that request takes some time - it is not instant - to load any data.
Check that profile exists and display its property
<h2>{profile && profile.bio}</h2>
As an alternative, you could display a message:
<h2>{profile ? profile.bio : "Profile not loaded yet"}</h2>
This is a React/Redux app. I have two components. One nested in the other.
<UserReview>
<UserReviewItem>
</UserReview>
I am working with two APIs. I call one API to get a 'movieId', I use the 'movieId' to call a second API to retrieve an image. I am mapping over an array, but it seems like it is only returning the last element's movieId.
The wrapping component:
class UserReview extends Component {
componentDidMount() {
this.props.fetchAllReviews();
}
render() {
const allReviews = this.props.reviews.slice(-2).map((review, i) => {
return (
<UserReviewItem
username={review.username}
text={review.text}
key={review._id}
movieId={review.movieId}
/>
)
});
const mapStateToProps = state => ({
reviews: state.movies.reviews
})
Child Component:
class UserReviewItem extends Component {
componentDidMount() {
**this.props.fetchImage(this.props.movieId)**
}
render() {
return (
<div key={this.props.key}>
<img
src={`https://image.tmdb.org/t/p/original/${this.props.img}`}
/>
<div>
<h4>{this.props.username}</h4>
<p>{this.props.text}</p>
</div>
</div>
);
const mapStateToProps = state => ({
img: state.movies.img
})
I want a different image for every item in the array but I am getting the same image even though the usernames and texts are different.
A solution I tried but got the same result:
class UserReview extends Component {
componentDidMount() {
this.props.fetchAllReviews();
}
render() {
const allReviews = this.props.reviews.slice(-2).map((review, i) => {
return (
<UserReviewItem
username={review.username}
text={review.text}
key={review._id}
-------> movieId={this.props.reviews[i].movieId} <--------
/>
)
});
const mapStateToProps = state => ({
reviews: state.movies.reviews
})
You can try this way:
class UserReview extends Component {
componentDidMount() {
this.props.fetchAllReviews();
}
renderItems(){
const { reviews } = this.props
if (!reviews) return []
return reviews.map(review => <UserReviewItem
username={review.username}
text={review.text}
key={review._id}
movieId={review.movieId}
/>)
}
render() {
return (
this.props.reviews
? <div>{this.renderItems()}</div>
: <p>Loading...</p>
)
};
const mapStateToProps = state => ({
reviews: state.movies.reviews
})
<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>
basically in the renderItems function you destructure the props, get the reviews and map them. In your render function you set a loader if the views are not ready yet (you can use a loading prop if you are setting that up in your store), or call the list if the reviews are already fetched and ready.
I found the answer. Because the second call depends on information from the first call I have to chain the calls using .then()
using a fetch inside another fetch in javascript
In my react JS application, I have a notification icon added in header component. I have created a separate component where I am doing api calls to get the data and display it. what I am trying to achieve here is to change the color of the icon in a Header component if there is some notification alerts.
My Header component-
import React from "react";
import { connect } from "react-redux";
import {
setPoiData,
getNotification,
updateNotification
} from "../../actions/action";
import { Link } from "react-router-dom";
const axios = require("axios");
class Notification extends React.Component {
render() {
const data = this.props.getNotificationStatus;
const highlightBellIcon = Object.keys((data.length === 0))
return (
<div className="notification-parent">
<Link to="/notification-details">
<span className={"glyphicon glyphicon-bell " + (!highlightBellIcon ? 'classA' : 'classB')} />
</Link>
</div>
);
}
}
const mapStateToProps = state => ({
getNotificationStatus: state.root.getNotificationStatus
});
export default connect (mapStateToProps)(Notification)
Here, getNotificationStatus is the state that holds the value in Redux.
Notification-details Component-
import React from "react";
import { connect } from "react-redux";
import {
getNotification
} from "../../actions/action";
import { Spinner } from "../Spinner";
import { setTimeout } from "timers";
import NotificationTile from "../NotificationTile/NotificationTile";
const axios = require("axios");
class NotificationDetails extends React.Component {
componentDidMount = () => {
this.intervalId = setInterval(() => this.handleNotification(), 2000);
setTimeout(
() =>
this.setState({
loading: false
}),
10000
);
};
componentWillUnmount = () => {
clearInterval(this.intervalId);
};
handleNotification = () => {
let postData = {
//inputParams
}
//call to action
this.props.dispatch(getNotification(postData));
};
getNotificationDetails = data => {
const payloadData =
data.payLoad &&
data.payLoad.map(item => {
console.log(this);
return <NotificationTile {...item} history={this.props.history} />;
});
//console.log(payloadData);
return payloadData;
console.log("InitialState" + payloadData);
};
render() {
const { loading } = this.state;
const data = this.props.getNotificationStatus;
return (
<div className="notificationContainer container">
<div className="notification-alert">
{!loading ? (
this.getNotificationDetails(data)
) : (
<h1>
Waiting for notifications..
<Spinner />
</h1>
)}
</div>
</div>
);
}
}
const mapStateToProps = state => ({
getNotificationStatus: state.root.getNotificationStatus
});
export default connect(mapStateToProps)(NotificationDetails);
The problem I am facing is always classB is getting added since the api call happens on click of the bell icon. So when I land to the page first time, api call doesn't happen unless I click on the bell icon. My code is absolutely working fine, It is just that I need to add the class to my Notification component (which is a global component) based on the response received in NotificationDetail Comp which is a sibling comp.Any suggestions where I am going wrong?
When you have to update your REDUX store, based on an Asynchronous call, you should be using something called Action Creators, These action creators will give you the ability
to dispatch an action after your response from you async call.
Use Redux-thunk
In this below code setTimeout is where your async call goes in
const INCREMENT_COUNTER = 'INCREMENT_COUNTER';
function increment() {
return {
type: INCREMENT_COUNTER
};
}
function incrementAsync() {
return dispatch => {
setTimeout(() => {
// Yay! Can invoke sync or async actions with `dispatch`
dispatch(increment());
}, 1000);
};
}
Update you REDUX Store and you call this action creator from componentDidMount()
so that you get your notifications the very first time.
notification-details is a separate component which fetches the data and adds it to store and
there is <Link to="/notification-details"> which loads this component, in this case, your store
initially will not have data until you click on the bell icon, which is correct behavior.
Solution:
Please go one step up in your component tree, I mean go to a component which is superior to
Notification ( its Container component ), and load notification-details in there ( you can use any creational lifecycle hook),
,also have a flag ( something like isNotificationLoaded ) which checks where state.root.getNotificationStatus
is filled with data and load the Notification panel only after that is true.
something like
render() {
const data = this.props.getNotificationStatus;
const noticationPanel = data.length > 0 ? <Notification/> : null;
return({noticationPanel});
}
This will make sure loads only when there is data, until then
you can show some activity indicator.
Hope this helps to solve your problem.
I have an app with redux and router where on the first load, all users are loaded. To this end, I've implemented a main component that loads the user when the component is mounted:
class Content extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
this.props.load();
}
render() {
return this.props.children;
}
}
The afterwards, if the user chooses to load the details of one user, the details are also obtained through the same lifehook:
class Details extends Component {
componentDidMount() {
this.props.getByUrl(this.props.match.params.url);
}
render() {
const { user: userObject } = this.props;
const { user } = userObject;
if (user) {
return (
<>
<Link to="/">Go back</Link>
<h1>{user.name}</h1>
</>
);
}
return (
<>
<Link to="/">Go back</Link>
<div>Fetching...</div>
</>
);
}
Now this works well if the user lands on the main page. However, if you get directly to the link (i.e. https://43r1592l0.codesandbox.io/gal-gadot) it doesn't because the users aren't loaded yet.
I made a simple example to demonstrate my issues. https://codesandbox.io/s/43r1592l0 if you click a link, it works. If you get directly to the link (https://43r1592l0.codesandbox.io/gal-gadot) it doesn't.
How would I solve this issue?
Summary of our chat on reactiflux:
To answer your question: how would you solve this? -> High Order Components
your question comes down to "re-using the fetching all users before loading a component" part.
Let's say you want to show a Component after your users are loaded, otherwise you show the loading div: (Simple version)
import {connect} from 'react-redux'
const withUser = connect(
state => ({
users: state.users // <-- change this to get the users from the state
}),
dispatch => ({
loadUsers: () => dispatch({type: 'LOAD_USERS'}) // <-- change this to the proper dispatch
})
)
now you can re-use withUsers for both your components, which will look like:
class Content extends Component {
componentDidMount() {
if (! this.props.users || ! this.props.users.length) {
this.props.loadUsers()
}
}
// ... etc
}
const ContentWithUsers = withUsers(Content) // <-- you will use that class
class Details extends Component {
componentDidMount() {
if (! this.props.users || ! this.props.users.length) {
this.props.loadUsers()
}
}
}
const DetailsWithUsers = withUsers(Details) // <-- same thing applies
we now created a re-usable HOC from connect. you can wrap your components with withUsers and you can then re-use it but as you can see, you are also re-writing the componentDidMount() part twice
let's take the actual load if we haven't loaded it part out of your Component and put it in a wrapper
const withUsers = WrappedComponent => { // notice the WrappedComponent
class WithUsersHOC extends Component {
componentDidMount () {
if (!this.props.users || !this.props.users.length) {
this.props.loadUsers()
}
}
render () {
if (! this.props.users) { // let's show a simple loading div while we haven't loaded yet
return (<div>Loading...</div>)
}
return (<WrappedComponent {...this.props} />) // We render the actual component here
}
}
// the connect from the "simple version" re-used
return connect(
state => ({
users: state.users
}),
dispatch => ({
loadUsers: () => dispatch({ type: 'LOAD_USERS' })
})
)(WithUsersHOC)
}
Now you can just do:
class Content extends Component {
render() {
// ......
}
}
const ContentWithUsers = withUsers(Content)
No need to implement loading the users anymore, since WithUsersHOC takes care of that
You can now wrap both Content and Details with the same HOC (High Order Component)
Until the Users are loaded, it won't show the actual component yet.
Once the users are loaded, your components render correctly.
Need another page where you need to load the users before displaying? Wrap it in your HOC as well
now, one more thing to inspire a bit more re-usability
What if you don't want your withLoading component to just be able to handle the users?
const withLoading = compareFunction = Component =>
class extends React.Component {
render() {
if (! compareFunction(this.props)) {
return <Component {...this.props} />;
}
else return <div>Loading...</div>;
}
};
now you can re-use it:
const withUsersLoading = withLoading(props => !props.users || ! props.users.length)
const ContentWithUsersAndLoading = withUsers(withUsersLoading(Content)) // sorry for the long name
or, written as a bit more clean compose:
export default compose(
withUsers,
withLoading(props => !props.users || !props.users.length)
)(Content)
now you have both withUsers and withLoading reusable throughout your app
I am fetching data in parent 'wrapper' component and pass it down to two child components. One child component receives it well, another does not.
In container:
const mapStateToProps = createStructuredSelector({
visitedCountriesList: getVisitedCountriesList(),
visitedCountriesPolygons: getVisitedCountriesPolygons()
});
export function mapDispatchToProps(dispatch) {
return {
loadVisitedCountries: () => {
dispatch(loadVisitedCountriesRequest())
},
};
}
in redux-saga I fetch data from API and store them:
function mapPageReducer(state = initialState, action) {
switch (action.type) {
case FETCH_VISITED_COUNTRIES_SUCCESS:
return state
.setIn(['visitedCountriesPolygons', 'features'], action.polygons)
}
Selectors:
const getVisitedCountriesList = () => createSelector(
getMapPage,
(mapState) => {
let countriesList = mapState.getIn(['visitedCountriesPolygons', 'features']).map(c => {
return {
alpha3: c.id,
name: c.properties.name
}
});
return countriesList;
}
)
const getVisitedCountriesPolygons = () => createSelector(
getMapPage,
(mapState) => mapState.get('visitedCountriesPolygons')
)
in a wrapper component I render two components, triggering data fetch and passing props down to child components (visitedCountriesPolygons and visitedCountriesList):
class MapView extends React.Component {
constructor(props) {
super(props)
this.props.loadVisitedCountries();
}
render() {
return (
<div>
<Map visitedCountriesPolygons={this.props.visitedCountriesPolygons} />
<MapActionsTab visitedCountriesList={this.props.visitedCountriesList} />
</div>
);
}
}
Then, in first child component Map I receive props well and can build a map:
componentDidMount() {
this.map.on('load', () => {
this.drawVisitedPolygons(this.props.visitedCountriesPolygons);
});
};
But in the second component MapActionsTab props are not received at initial render, but only after any update:
class MapActionsTab extends React.Component {
constructor(props) {
super(props);
}
render() {
let countriesList = this.props.visitedCountriesList.map(country => {
return <li key={country.alpha3}>{country.name}</li>;
}) || '';
return (
<Wrapper>
<div>{countriesList}</div>
</Wrapper>
);
}
}
UPD:
Saga to fetch data form API:
export function* fetchVisitedCountries() {
const countries = yield request
.get('http://...')
.query()
.then((res, err) => {
return res.body;
});
let polygons = [];
yield countries.map(c => {
request
.get(`https://.../${c.toUpperCase()}.geo.json`)
.then((res, err) => {
polygons.push(res.body.features[0]);
})
});
yield put(fetchVisitedCountriesSuccess(polygons));
}
and a simple piece of reducer to store data:
case FETCH_VISITED_COUNTRIES_SUCCESS:
return state
.setIn(['visitedCountriesPolygons', 'features'], action.polygons)
Why is it different and how to solve it, please?
thanks,
Roman
Apparently, this works correct and it was just a minor issue in another place (not pasted here and not errors reported).
After thorough clean up and refactoring it worked as expected.
Conclusion: always keep your code clean, use linter and follow best practices :)
I think the problem may be in your selectors, in particular this one, whose component parts being executed immediately (with no fetched data values), and hence values will not change as it is memoized. This means that it will not cause an update to the component should the the underlying data change from the fetched data
const mapStateToProps = createStructuredSelector({
visitedCountriesList: getVisitedCountriesList, // should not execute ()
visitedCountriesPolygons: getVisitedCountriesPolygons // should not execute ()
});
By not executing the composed selectors immediately, mapStateToProps will call them each time the state changes and they should select the new values and cause an automatic update of your react component