React - Redux: Store Data is not rendering? - reactjs

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>

Related

render error while i wanted to get data from server

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

React Redux - how to load details if the array is not yet obtained?

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

Why can't I display array data in React Application?

I am quite new to development with React and I am currently trying to get my head around some basic react and redux things. Unfortunately I am experiencing an issue which I cannot fix on my own.
I have written a mock-api which returns players (profileUrl, username, realname, id). I am dispatching an action which successfully gets me one of those players and I can also pass it to my components props using redux' mapStateToPropsfunction. But I cannot render any of that data in my render function. The react devtools even show me that the single player is getting returned as an array.
The component:
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as playerActions from '../../actions/playerActions';
class SinglePlayer extends React.Component {
componentDidMount() {
this.props.actions.loadPlayer(this.props.match.params.playerid);
}
/**
* Render the component.
*/
render() {
return (
<div>
{ this.props.currentPlayer.username }
</div>
)
}
}
/**
* Defines the state which is exposed to this component.
*
* #param { object } reduxStore The entire redux store.
* #param { object } ownProps The properties which belong to the component.
*/
const mapStateToProps = (reduxStore, ownProps) => {
return {
currentPlayer: reduxStore.playerReducer.currentPlayer
}
}
/**
* Defines which actions are exposed to this component.
*
* #param { function } dispatch This function is used to dispatch actions.
*/
const mapDispatchToProps = (dispatch) => {
return {
actions: bindActionCreators(playerActions, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(SinglePlayer);
React DevTools:
Screenshot of React Devtools props
Redux DevTools:
Screenshot of Redux Devtools data
As you can tell from the image above, the currentPlayer props is inside the playerReducer object.
I have also tried looping over the array like so, with no success either. I just get the error-message stating that .map() is not a function.
this.props.currentPlayer.map(function(player, index) {
return <p>{ player.username }</p>
)}
Error when using .map():
TypeError: this.props.currentPlayer.map is not a function
Can someone tell me what I am doing wrong?
You set your current player by params id at componentDidMount . Your render takes place before that currentPlayer is set hence the error. Add a recheck in your render like below.
render() {
return (
<div>
{
this.props.currentPlayer &&
this.props.currentPlayer.map(function(player, index) {
return <p>{ player.username }</>
)}
}
</div>
)
}
Or
render() {
return (
<div>
{
this.props.currentPlayer ?
this.props.currentPlayer.map(function(player, index) {
return <p>{ player.username }</>
)}
:
null
}
</div>
)
}
Either way it should work. That way this.props.currentPlayer will not be rendered or accessed until its available.
Update
Udate your mapStateToProps to
const mapStateToProps = (state) => {
return {
currentPlayer: state.currentPlayer
}
}
I think from your reduxDev tool, currentPlayer is not under any object.
in first render this.props.currentPlayer is empty!
set empty array "currentPlayer" in state and insert insert this.props.currentPlayer in this.state.currentPlayer and render from state
I managed to solve the issue myself now. The posts here kinda inspired me to try some new things. It was my mock-api which returned the data in a strange and unexpected (at least for me it was unexpected) way.
The dataset:
const players = [
{
profileUrl: 'https://profile.url',
username: 'player1',
realname: 'Max Muster',
steamId: 'player1'
},
{
profileUrl: 'https://profile.url',
username: 'player2',
realname: 'Max Mustermann',
steamId: 'player2'
},
{
profileUrl: 'https://profile.url',
username: 'player3',
realname: 'Maxime Musterfrau',
steamId: 'player3'
},
];
The filtermethod used:
var player = players.filter(function(el) {
return el.steamId === 'player1';
});
If you assume an array of multiple players like above, the shown filtermethod extracts the correct object but still keeps it wrapped in an array. That was producing the mistake...
Thanks a lot for the help guys!

Error Retrieving Data from React Redux Store and Mapping to Props

I have a React component which is fetching data via API to retrieve an product Object by using the ID passed in as a prop to the component.
I have a React / Redux app and I am fairly new to Redux flow.
I have the Products (array with one Product object) data loading via my Action / Reducer to the store.
I am trying to pass this from state to props using the mapStateToProps pattern.
I am getting the following error when it is rendering the { this.props.product.title }
Uncaught TypeError: Cannot read property '__reactInternalInstance$z9gkwvwuolc' of null
I think its due to it being data thats asynchronous.
What's the best way to solve for this?
Below is my code --
class ProductListItem extends Component {
componentDidMount() {
this.props.dispatch(fetchProduct(this.props.id));
}
render() {
return (
<div>
<h1>{ this.props.product.title }</h1>
</div>
);
}
}
// Actions required to provide data for this component to render in sever side.
ProductListItem.need = [() => { return fetchProduct(this.props.id); }];
// Retrieve data from store as props
function mapStateToProps(state, props) {
return {
product: state.products.data[0],
};
}
ProductListItem.propTypes = {
id: PropTypes.string.isRequired,
dispatch: PropTypes.func.isRequired,
overlay: PropTypes.string,
};
export default connect(mapStateToProps)(ProductListItem);
You need to check if the product exist, you will access the inner data only if it exist. This a common pattern:
class ProductListItem extends Component {
componentDidMount() {
this.props.dispatch(fetchProduct(this.props.id));
}
render() {
const { product } = this.props;
return (
<div>
{ product &&
<h1>{product.title}</h1>
}
</div>
);
}
}
If the product exist, then the component will render the <h1>.
In your redux reducer you can define the default state, set the default state and then you can do some ternary checking
export default function reducer(state={ title : undefined}, action) {
//ternary checking in render() on component
product.title !== undefined ? product.title : undefined
This means, if product.title isn't undefined, then render product.title else undefined.

Adding Redux to an existing React app

I've been working on a React app and have gotten to a point where I'll need Redux to handle some aspects of it.
After reading a bunch of tutorials, I'm fairly stuck on how to make my "smarter" components "dumber" and move functions into my actions and reducers.
So, for example, one aspect of the app is more of a to-do list style.
One of my classes starts like this:
export default class ItemList extends React.Component {
constructor() {
super();
this.state = { items: [],
completed: [],
};
this.addItem = this.addItem.bind(this);
this.completeItem = this.completeItem.bind(this);
this.deleteItem = this.deleteItem.bind(this);
}
addItem(e) {
var i = this.state.items;
i.push({
text: this._inputElement.value,
paused: false,
key: Date.now()
});
this.setState({ items: i });
e.preventDefault();
this._inputElement.value = '';
this._inputElement.focus();
}
completeItem(e) {
this.deleteItem(e);
var c = this.state.completed;
c.push({
text: e.target.parentNode.parentNode.getElementsByClassName('item-name')[0].innerHTML,
paused: false,
key: Date.now()
});
this.setState({ completed: c });
}
deleteItem(e) {
var i = this.state.items;
var result = i.filter(function(obj) {
return obj.text !== e.target.parentNode.parentNode.getElementsByClassName('item-name')[0].innerHTML;
});
this.setState({ items: result });
}
// ... more irrelevant code here ...
// there's a function called createTasks that renders individual items
render() {
var listItems = this.state.items.map(this.createTasks);
return <div className="item-list">
<form className="form" onSubmit={this.addItem}>
<input ref={(a) => this._inputElement = a}
placeholder="Add new item"
autoFocus />
<button type="submit"></button>
</form>
{listItems}
</div>;
}
}
So, as you can see, it's very logic-heavy. I've started adding Redux by adding a <Provider> in my index file, and made a basic reducers file that is fairly empty so far:
import { combineReducers } from 'redux';
const itemList = (state = {}, action) => {
};
// ... other irrelevant reducers
const rootReducer = combineReducers({
itemList,
// ...
});
export default rootReducer;
...and I've made an actions file that doesn't have much in it yet either.
I've been struggling to figure out:
Most actions I've seen examples of just return some kind of JSON, what do I return in the reducer that uses that JSON that my component can use?
How much of my component logic is reusable, or should I just forget it? What is the best way to go about this to reuse as much code as I've written as possible?
First of all you need to understand the overall picture of how redux works with react.
Before coming to that lets first understand what are smart components and dumb components.
Smart Components
All your code logic needs to be handled here
They are also called containers.
They interact with the store(aka state management) to update your components.
Dumb Components
They just read props from your containers and render you components
This is just the UI view and should not contain any logic.
All styling/html/css comes in your dumb components.
Here is an amazing piece of article which you can go through to understand smart and dumb components if you still have doubts.
Ok, now lets try understanding how redux works:-
Your smart components(aka containers) interact with your redux store
You fire actions from your containers.
Your actions call your apis
The result of your action updates the store through a reducer
You containers read the store through mapStateToProps function and as soon as value in store changes it updates your component.
Now lets consider your todo example
TodoListContainer.js
class TodoListContainer extends Component {
componentWillMount () {
// fire you action action
}
render () {
return (
<Todos todos=={this.props.todos} />
)
}
}
function mapStateToProps(state) {
const {todos} = state;
return {
todos;
}
}
export default connect(mapStateToProps)(TodoListContainer)
TodoList.js
class TodoList extends Component {
renderTodos() {
return this.props.todos.map((todo)=>{
return <Todo todo={todo} key={todo.id} />
})
}
render () {
return () {
if (this.props.todos.length === 0) {
return <div>No todos</div>
}
return (
<div>
{this.renderTodos()}
</div>
)
}
}
}
export default class TodoList
Todo.js
class Todo extends Component {
render () {
return (
<div>
<span>{this.props.todo.id}</span>
<span>{this.props.todo.name}</span>
</div>
)
}
}
Reducer
export default function todos(state={},action) {
switch (action.type) {
case 'RECEIVE_TODOS':
return Object.assign(state,action.todos);
}
}
action
function fetchTodos() {
return(dispatch) => {
axios.get({
//api details
})
.then((res)=>{
dispatch(receiveTodos(res.todos))
})
.catch((err)=>{
console.warn(err)
})
}
}
function receiveTodos(todos) {
return {
type: 'RECEIVE_TODOS',
todos
}
}
Now if you have read redux documentation you would see that actions return objects then how would i call my api there which returns a function instead of an object. For that I used redux thunk about which you can read here.
I gave you an example in which you can fetch todos. If you want to do other operations like deleteTodo, addTodo, modifyTodo then you can do that in appropriate components.
DeleteTodo - you can do in TodoListContainer.
AddingTodo - you can do in TodoListContainer.
Changing State(completed/Pending) - you can do in TodoListContainer.
ModifyingTodo - you can do in TodoContainer.
You can also check out here for a detailed example, but before that I would say just should go through basics of redux which you can find here
P.S: I wrote code on the fly so it might not work properly but it should work with little modification.

Resources