Persist state in localStorage and retrieving state in componentDidMount - reactjs

I am trying to persist the state of Button option value in localStorage when the user selects it and retrieve the state in componentDidMount method but I am getting a null value. When the user navigates back to the page then state value should be there. Can anyone tell me what's wrong with my code?
code::
import React, { Component } from "react";
import { Button } from "semantic-ui-react";
import { withRouter } from "react-router";
import Answers from "../Answers/Answers";
class Section extends Component {
state = {
que1: "",
que2: "",
que3: ""
};
handleClick = event => {
this.setState(
{
que1: event.target.attributes.getNamedItem("data-key").value
}
),
() => {
localStorage.setItem("que1", que1);
console.log(this.state.que1);
}
};
handleClick2 = event => {
this.setState(
{
que2: event.target.attributes.getNamedItem("data-key").value
}
),
() => {
localStorage.setItem("que2", que2);
console.log(this.state.que2);
}
};
handleClick3 = event => {
this.setState(
{
que3: event.target.attributes.getNamedItem("data-key").value
}
),
() => {
localStorage.setItem("que3", que3);
console.log(this.state.que3);
}
};
componentDidMount() {
this.setState({
que1: localStorage.getItem("que1"),
que2: localStorage.getItem("que2"),
que3: localStorage.getItem("que3")
});
}
render() {
console.log(this.state);
let styles = {
width: '50%',
margin: '0 auto',
marginBottom: '15px'
}
const { history } = this.props;
const { que1, que2, que3 } = this.state;
return (
<>
<p>1. I was stressed with my nerves on edge.</p>
<Button.Group widths="5" onClick={this.handleClick} style={styles}>
<Answers selected={this.state.que1} style={{ backgroundColor: 'red' }} />
</Button.Group>
{` `}
<p>2. I lost hope and wanted to give up when something went wrong.</p>
<Button.Group widths="5" onClick={this.handleClick2} style={styles}>
<Answers selected={this.state.que2} style={{ backgroundColor: 'red' }} />
</Button.Group>
{` `}
<p>3. I feel very satisfied with the way I look and act</p>
<Button.Group widths="5" onClick={this.handleClick3} style={styles}>
<Answers selected={this.state.que3} style={{ backgroundColor: 'red' }} />
</Button.Group>
<p />
{` `}
<Button
disabled={!que1 || !que2 || !que3}
onClick={() => history.push("/section2", [this.state])}
>
NEXT
</Button>
</>
);
}
}
export default withRouter(Section);
output ::

In your onclick handlers, you have:
handleClick2 = event => {
this.setState(
{
que2: event.target.attributes.getNamedItem("data-key").value
}
),
() => {
localStorage.setItem("que2", que2);
console.log(this.state.que2);
}
};
In the setState callback, you need to change it to(you logged the correct value, but didn't store it properly):
localStorage.setItem("que2", this.state.que2);

This is not supposed to be the React way of doing it. You should implement a redux workflow here which will help you persist data when navigating between pages. Refer https://redux.js.org/basics/usage-with-react. And when you click on the back button, you can retrieve the values back from the redux store.
Now, in your case change it to
handleClick(e) => {
const value = event.target.attributes.getNamedItem('data-key').value;
this.setState({que1 : value});
localStorage.setItem('que1', value);
};
And in the constructor
constructor() {
this.state = {
que1 : localStorage.getItem('que1') ? localStorage.getItem('que1') : ''
};
};
But this above solution is not at all recommended. Go ahead with the redux approach.

Related

I am not able to change state and pass props

I have the stake component that is rendered 4 times in the parent class component. I am trying to pass valueNewStake as prop to its parent component and group all the inputs in one common array (see allStakes). For a reason I am not able to change the state and also the dom does not render the button next to the component. Can anyone explain me why it is happening as I am new in react. Thanks
import React, { Component } from 'react';
import Stake from './stake';
class FetchRandomBet extends Component {
constructor(props) {
super(props);
this.state = {
loading: true,
bet: null,
value: this.props.value,
allStakes: ['']
};
}
async componentDidMount() {
const url = "http://localhost:4000/";
const response = await fetch(url);
const data = await response.json();
this.setState({
loading: false,
bet: data.bets,
});
}
render() {
const { valueProp: value } = this.props;
const { bet, loading } = this.state;
if (loading) {
return <div>loading..</div>;
}
if (!bet) {
return <div>did not get data</div>;
}
return (
< div >
{
loading || !bet ? (
<div>loading..</div>
) : value === 0 ? (
<div className="bet-list">
<ol>
<p>NAME</p>
{
bet.map(post => (
<li key={post.id}>
{post.name}
</li>
))
}
</ol>
<ul>
<p>ODDS</p>
{
bet.map(post => (
<li key={post.id}>
{post.odds[4].oddsDecimal}
<div className="stake-margin">
<Stake
allStakes={this.props.valueNewStake}
onChange={() => { this.setState({ allStakes: [...this.props.valueNewStake] }) }}
>
<button>ok</button>
</Stake>
</div>
</li>
))
}
</ul>
</div>
import React, { useState } from 'react';
import CurrencyInput from 'react-currency-input-field';
function Stake() {
const [newStake, setStake] = useState(['']);
const changeStake = (e) => {
setStake(e.target.value)
}
return (
<>
<CurrencyInput
onChange={changeStake}
valueNewStake={newStake}
style={{
marginLeft: "40px",
width: "50px"
}}
placeholder="Stake"
decimalScale={2}
prefix="£"
/>
{newStake}
</>
);
}
export default Stake;
You're not passing your props to your Stake component
function Stake({ allStakes, onChange }) {
// do something with your props here
const [newStake, setStake] = useState(['']);
const changeStake = (e) => {
onChange()
setStake(e.target.value)
}
return (
<>
<CurrencyInput
onChange={changeStake}
valueNewStake={newStake}
style={{
marginLeft: "40px",
width: "50px"
}}
placeholder="Stake"
decimalScale={2}
prefix="£"
/>
{newStake}
</>
);
}

React API call in componentDidMount, and componentWillReceiveProps (order confusion)

I'm fairly new to React and Redux. I'm having this issue where the ratings sometimes don't show up when I refresh the page (please see screenshot). I think it's because sometimes the user from Redux comes into componentWillReceiveProps before loadTeamsData executes, but I don't know why that would make ratings not show up. (Also, I feel like my code is crap... Any critic is appreciated!)
Home.js
export class Home extends React.Component {
state = {
upVote: null,
downVote: null,
clickedTeam: "",
teams: teams
};
// When component mounted, add in thumbUp & thumbDown properties to each team
componentDidMount() {
const { user } = this.props.auth;
console.log("didmount");
this.loadTeamsData();
// Stores user voting info to state when coming from a different page
if (user) {
if (!(Object.entries(user).length === 0)) {
console.log("user", user, user.upVote, user.downVote);
this.setState({
upVote: user.upVote,
downVote: user.downVote
});
}
}
}
// Loads teams thumbUp, thumbDown data to state
loadTeamsData = () => {
axios.get("/api/teams/").then(res => {
console.log("data", res.data);
this.setState({
teams: this.state.teams.map(team => {
res.data.map(vote => {
if (vote.id === team.id) {
team.thumbUp = vote.thumbUp;
team.thumbDown = vote.thumbDown;
}
return vote;
});
return team;
})
});
});
};
// When props from Redux come in, set the state
UNSAFE_componentWillReceiveProps(nextProps) {
const { user } = nextProps.auth;
if (user !== this.props.auth.user && user) {
console.log("willreceiveprops", `\n`, this.props.auth.user, user);
this.setState({
upVote: user.upVote,
downVote: user.downVote
});
}
}
// Handle click on thumbs
onClickHandler = (id, e) => {
const { alert } = this.props;
const up = e.target.classList.contains("up");
if (this.props.auth.isAuthenticated) {
if (up && this.state.upVote === "") {
if (id === this.state.downVote) {
alert.error("You cannot up vote and down vote the same team!");
} else {
this.props.update_up(id);
this.setState(prevState => {
return {
teams: prevState.teams.map(team => {
if (id === team.id) {
team.thumbUp = team.thumbUp + 1;
team.votedUpColor = { color: "#1E95E0" };
}
return team;
}),
clickedTeam: id,
upVote: id
};
});
alert.show(`You Up Voted ${id}`);
}
} else if (!up && this.state.downVote === "") {
if (id === this.state.upVote) {
alert.error("You cannot up vote and down vote the same team!");
} else {
this.props.update_down(id);
this.setState(prevState => {
return {
teams: prevState.teams.map(team => {
if (id === team.id) {
team.thumbDown = team.thumbDown + 1;
team.votedDownColor = { color: "#F8004C" };
}
return team;
}),
clickedTeam: id,
downVote: id
};
});
alert.show(`You Down Voted ${id}`);
}
} else {
alert.show("You have already voted.");
}
} else {
alert.show("Please log in first!");
this.props.history.push(`/login`);
}
};
// When user votes, update the db before updating the state
UNSAFE_componentWillUpdate(newProps, newState) {
newState.teams.map(team => {
if (team.id === newState.clickedTeam) {
axios.put(`/api/teams/${newState.clickedTeam}/`, {
id: team.id,
thumbUp: team.thumbUp,
thumbDown: team.thumbDown
});
}
});
}
render() {
// Welcome header message when user logs in
console.log("render", this.state.teams[0].thumbUp);
const { isAuthenticated, user } = this.props.auth;
const { upVote, downVote } = this.state;
const welcome_header = (
<div className="welcome-header">
<h4 style={{ textAlign: "left" }} className="welcome-header-line">
Welcome, {user && user.username}!
</h4>
<h4 style={{ textAlign: "left" }} className="welcome-header-line">
<span>
Your Vote:{" "}
<i className="far fa-thumbs-up up" style={{ color: "#1E95E0" }}></i>
<span style={{ textTransform: "capitalize" }}>{upVote}</span>
</span>{" "}
<span>
<i
className="far fa-thumbs-down down"
style={{ color: "#F8004C" }}
></i>
<span style={{ textTransform: "capitalize" }}>{downVote}</span>
</span>
</h4>
</div>
);
return (
<div className="home">
<div className="home-container">
{isAuthenticated && welcome_header}
<h2>Who Is Your NBA Champion This Year?</h2>
<Teams
upVote={this.state.upVote}
downVote={this.state.downVote}
teams={this.state.teams}
onClickHandler={this.onClickHandler}
/>
</div>
</div>
);
}
}
Home.propTypes = {
update_up: PropTypes.func.isRequired,
update_down: PropTypes.func.isRequired,
auth: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
auth: state.auth
});
export default connect(mapStateToProps, { update_up, update_down })(
withAlert()(withRouter(Home))
);
When ratings show up
When ratings don't show up
I'm console logging this.state.teams[0] and this.state.teams[0].thumbUp in the render method.
Even during the first render, both thumbUp and thumbDown show in this.state.teams[0], but this.state.teams[0].thumbUp appears to be undefined.
I happened to fix the issue. The issue was actually in the Rating.js file, sorry that I didn't post that file cuz I thought the issue had to be in Home.js.
Before, in Rating.js file, I originally brought in user from redux which caused the issue (I believe it didn't make Rating re-render when in Home.js the axios get call happened after componentWillReceiveProps).
After, instead of bringing in user from redux, I passed user to Rating.js as a prop from Home.js.
Even though it works fine now, I still don't know what exactly the issue was... I'd much appreciate it if someone could enlighten me! Also, please critique my code (i.e. where I can improve)! THANK YOU!
Rating.js (Before)
import React from "react";
import "./Rating.css";
import PropTypes from "prop-types";
import { connect } from "react-redux";
const Rating = props => {
const { thumbUp, thumbDown, id, votedUpColor, votedDownColor } = props.team;
const { upVote, downVote, onClickHandler } = props;
const { user } = props.auth;
let thumbUpColor =
user && id === upVote ? { color: "#1E95E0" } : votedUpColor;
let thumbDownColor =
user && id === downVote ? { color: "#F8004C" } : votedDownColor;
console.log(id, thumbUp, thumbDown);
return (
<div className="rating" key={id}>
<button
className="thumb-up up"
style={thumbUpColor}
onClick={e => onClickHandler(id, e)}
>
<i className="far fa-thumbs-up up"></i>
<span style={{ userSelect: "none" }} className="up">
{thumbUp}
</span>
</button>
<button
className="thumb-down down"
style={thumbDownColor}
onClick={e => onClickHandler(id, e)}
>
<i className="far fa-thumbs-down down"></i>
<span style={{ userSelect: "none" }} className="down">
{thumbDown}
</span>
</button>
</div>
);
};
Rating.propTypes = {
team: PropTypes.object.isRequired,
onClickHandler: PropTypes.func.isRequired,
auth: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
auth: state.auth
});
export default connect(mapStateToProps)(Rating);
Rating.js (After)
import React from "react";
import "./Rating.css";
import PropTypes from "prop-types";
// import { connect } from "react-redux";
const Rating = props => {
const { thumbUp, thumbDown, id, votedUpColor, votedDownColor } = props.team;
const { upVote, downVote, onClickHandler, user } = props;
// const { user } = props.auth;
let thumbUpColor =
user && id === upVote ? { color: "#1E95E0" } : votedUpColor;
let thumbDownColor =
user && id === downVote ? { color: "#F8004C" } : votedDownColor;
console.log(id, thumbUp, thumbDown);
return (
<div className="rating" key={id}>
<button
className="thumb-up up"
style={thumbUpColor}
onClick={e => onClickHandler(id, e)}
>
<i className="far fa-thumbs-up up"></i>
<span style={{ userSelect: "none" }} className="up">
{thumbUp}
</span>
</button>
<button
className="thumb-down down"
style={thumbDownColor}
onClick={e => onClickHandler(id, e)}
>
<i className="far fa-thumbs-down down"></i>
<span style={{ userSelect: "none" }} className="down">
{thumbDown}
</span>
</button>
</div>
);
};
Rating.propTypes = {
team: PropTypes.object.isRequired,
onClickHandler: PropTypes.func.isRequired
// auth: PropTypes.object.isRequired
};
// const mapStateToProps = state => ({
// auth: state.auth
// });
export default Rating;

Connected component doesn't re-render after store changed from another connected component

I am having a problem related to redux.
I have 2 connected components which are:
avatar situated in the navbar which is always visible
profile which is responsible for changing the avatar image in the store
if I am right, when the store change, any connected component will re-render if needed.
In my case, when the action UPDATE_CURRENT_USER update the avatar image, the navbar avatar doesn't get the new image only after I change route or reload page.
I found a solution but many people say it's a hack,
I have put a listener on store changes in the main component and did forceUpdate()
componentDidMount() {
store.subscribe(res => this.forceUpdate());
}
and I don't want to use it since connected components are supposed to re-render on store changes.
user actions:
export const getCurrentUser = () => dispatch => {
axios.get("user").then(user => {
dispatch({
type: GET_CURRENT_USER,
payload: user.data
});
});
};
export const updateCurrentUser = user => dispatch => {
dispatch({
type: UPDATE_CURRENT_USER,
payload: user
})
}
user reducer
const initialState = {
user: {}
}
export default function (state = initialState, action) {
switch (action.type) {
case GET_CURRENT_USER:
return { ...state, user: action.payload };
case UPDATE_CURRENT_USER:
return { ...state, user: action.payload }
default:
return state;
}
}
profile component
class Profile extends Component {
render() {
const { currentUser, updateCurrentUser } = this.props;
return (
<div id="profile-container">
<ProfileSider
currentUser={currentUser}
updateCurrentUser={updateCurrentUser}
/>
<ProfileContent
currentUser={currentUser}
updateCurrentUser={updateCurrentUser}
/>
</div>
);
}
}
const mapStateToProps = state => ({
currentUser: state.userReducer.user
});
export default connect(
mapStateToProps,
{ updateCurrentUser }
)(Profile);
profile sidebar child of profile
class ProfileSider extends Component {
state = { uploading: false };
triggerAvatarInput() {
$("#avatarInput").click();
}
handleChange = async event => {
this.setState({ ...this.state, uploading: true });
const avatarFormData = new FormData();
avatarFormData.append("file", event.target.files[0]);
axios
.post("uploadFile", avatarFormData)
.then(res => {
const avatarURIFormData = new FormData();
avatarURIFormData.append("avatar", res.data.fileDownloadUri);
axios
.put("user/update", avatarURIFormData)
.then(res => {
const { currentUser } = this.props;
currentUser.avatar = res.data.avatar;
this.props.updateCurrentUser(currentUser);
this.setState({
...this.state,
uploading: false,
avatar: currentUser.avatar
});
message.success("Avatar updated successfully", 3);
})
.catch(error => {
this.setState({ ...this.state, uploading: false });
message.error("Updating avatar failed!", 3);
});
})
.catch(error => {
this.setState({ ...this.state, uploading: false });
message.error("Uploading avatar failed!", 3);
});
};
render() {
const { uploading } = this.state;
const { currentUser } = this.props;
return (
<div id="profile-sider">
<div id="profile-sider-info">
<div id="profile-sider-info-avatar">
<div className="container">
<div
className="overlay-uploading"
className={
uploading ? "overlay-uploading" : "overlay-uploading hidden"
}
>
<Icon type="loading" style={{ fontSize: 50, color: "#FFF" }} />
</div>
<div className="overlay" />
<div className="overlay-text" onClick={this.triggerAvatarInput}>
<Icon type="camera" style={{ fontSize: 20 }} />
<span>Update</span>
</div>
<div
className="avatar"
style={{
backgroundImage: "url(" + currentUser.avatar + ")"
}}
></div>
<input
onChange={this.handleChange}
type="file"
accept="image/png, image/jpeg, image/jpg"
id="avatarInput"
/>
</div>
</div>
<h2 style={{ marginTop: 20, textAlign: "center" }}>
{currentUser.fullName}
</h2>
<h4 style={{ textAlign: "center" }}>{currentUser.email}</h4>
</div>
<div id="profile-sider-actions">
<div className="profile-sider-actions-item">
<Link to="/profile/courses" style={{ transition: 0 }}>
<Button type="primary" id="courses-btn">
<Icon type="read" style={{ marginRight: 15 }} />
My Courses
</Button>
</Link>
</div>
<div className="profile-sider-actions-item">
<Link to="/profile/update">
<Button type="primary" id="update-infos-btn">
<Icon type="sync" style={{ marginRight: 15 }} />
Update Infos
</Button>
</Link>
</div>
</div>
</div>
);
}
}
export default ProfileSider;
avatar component situated in navbar
class ProfileAvatar extends Component {
constructor() {
super();
this.handleClick = this.handleClick.bind(this);
this.handleOutsideClick = this.handleOutsideClick.bind(this);
this.state = {
showProfileDropdown: false
};
}
componentDidMount() {
this.props.getCurrentUser();
}
handleLogout = async () => {
try {
await auth.logout();
this.props.onLogout();
notification["success"]({
message: "You have been successfully logged out!"
});
} catch (ex) {}
};
handleClick() {
if (!this.state.showProfileDropdown) {
// attach/remove event handler
document.addEventListener("click", this.handleOutsideClick, false);
} else {
document.removeEventListener("click", this.handleOutsideClick, false);
}
this.setState(prevState => ({
showProfileDropdown: !prevState.showProfileDropdown
}));
}
handleOutsideClick(e) {
// ignore clicks on the component itself
if (this.element && this.element.contains(e.target)) {
return;
}
this.handleClick();
}
render() {
const { currentUser } = this.props;
return (
<div
className="profile-avatar"
ref={element => {
this.element = element;
}}
>
<Avatar
onClick={this.handleClick}
size="large"
style={{ color: "#f56a00", backgroundColor: "#fde3cf" }}
src={currentUser.avatar}
>
{currentUser.fullName ? currentUser.fullName.charAt(0) : null}
</Avatar>
{this.state.showProfileDropdown && (
<div className="profile-dropdown-list">
<List
className="dropdown_list dropdown-shadow "
size="small"
style={{ width: "150px" }}
bordered
itemLayout="vertical"
dataSource={[
<Link to="/profile/update" className="profile-list-item">
<List.Item className="list-item">
<Icon className="profile-icons" type="user" /> My Profile
</List.Item>
</Link>,
<Link to="/profile/courses" className="profile-list-item">
<List.Item className="list-item">
<Icon className="profile-icons" type="container" /> My
Courses
</List.Item>
</Link>,
<List.Item className="list-item">
<Icon className="profile-icons" type="question-circle" /> Ask
for Help
</List.Item>,
<List.Item className="list-item" onClick={this.handleLogout}>
<Icon className="profile-icons" type="logout" /> Log out
</List.Item>
]}
renderItem={item => item}
/>
</div>
)}
</div>
);
}
}
const mapStateToProps = state => ({
currentUser: state.userReducer.user
});
export default connect(
mapStateToProps,
{ getCurrentUser }
)(ProfileAvatar);
image: https://imge.to/i/vywTNj
There are two problems here:
You are mutating the existing object from the store
You are sending that exact same user object back into the store when you dispatch the action.
Specifically, these lines are the cause:
const { currentUser } = this.props;
currentUser.avatar = res.data.avatar;
this.props.updateCurrentUser(currentUser);
currentUser is the user object that's already in the Redux store. This code mutates the object, and inserts it back into the store.
That results in the connected component thinking nothing has actually changed.
The shortest way to fix this is to create a new user object, and insert that:
const {currentUser} = this.props;
const updatedUser = {...currentUser, avatar: res.data.avatar};
this.props.updateCurrentUser(updatedUser);
To avoid this in the future, I strongly encourage you to use the configureStore function from our Redux Starter Kit package, which detects mutations and will throw errors if you mutate.

How to send props and methods between components?

Hi guys, I'm trying to code little app. I am trainee programmer. I need help to understand how to use props on my app. I'm currently using one component from CodePen and I fetched the data from jsonplaceholder. But I don't know how to pass props between this component and App.js. It was no problem with easier components but here is lot of methods and events. With this tagged input I want to add or delete items.
import React from "react";
import StateFullComponent from "./components/StateFullComponent";
import StatelessComponent from "./components/StatelessComponent";
import TagInput from "./components/TagInput";
function App() {
return (
<div>
<StatelessComponent
props1={"String"}
props2={1}
props3={true}
props4={JSON.stringify({ value: "value", key: 1 })}
/>
<StateFullComponent items={["apple", "orrange", "pear", "male"]} />
<TagInput />
</div>
);
}
export default App;
import React, { Component } from "react";
export default class TagInput extends Component {
constructor(props) {
super(props);
this.state = {
users: [],
focused: false,
input: ""
};
this.handleInputChange = this.handleInputChange.bind(this);
this.handleInputKeyDown = this.handleInputKeyDown.bind(this);
this.handleRemoveItem = this.handleRemoveItem.bind(this);
}
componentDidMount() {
fetch("https://jsonplaceholder.typicode.com/users")
.then(response => {
return response.json();
})
.then(result => {
this.setState({
users: result
});
});
}
add() {
let value = Math.floor(Math.random() * 10 + 1);
let users = this.state.users;
users.push(value);
this.setState({ users: users });
}
handleInputChange(evt) {
this.setState({ input: evt.target.value });
}
handleInputKeyDown(evt) {
if (evt.keyCode === 13) {
const { value } = evt.target;
this.setState(state => ({
users: [...state.users, value],
input: ""
}));
}
if (
this.state.users.length &&
evt.keyCode === 8 &&
!this.state.input.length
) {
this.setState(state => ({
users: state.users.slice(0, state.users.length - 1)
}));
}
}
handleRemoveItem(index) {
return () => {
this.setState(state => ({
users: state.users.filter((user, i) => i !== index)
}));
};
}
render() {
console.log(this.props, ":::::::::::");
const { users } = this.state;
const userId = users.map((user, id) => <li key={id}>{user.name}</li>);
const styles = {
container: {
border: "1px solid #ddd",
padding: "5px",
borderRadius: "5px"
},
items: {
display: "inline-block",
padding: "2px",
border: "1px solid blue",
fontFamily: "Helvetica, sans-serif",
borderRadius: "5px",
marginRight: "5px",
cursor: "pointer"
},
input: {
outline: "none",
border: "none",
fontSize: "14px",
fontFamily: "Helvetica, sans-serif"
}
};
return (
/* <div>
<ul>{userId}</ul>
<button onClick={this.handleRemoveItem().bind(this)}>add</button>
</div> */
<label>
<ul style={styles.container}>
{this.state.users.map((user, i) => (
<li
key={i}
style={styles.users}
onClick={this.handleRemoveItem(i).bind(this)}
>
{user}
<span>(x)</span>
</li>
))}
<input
style={styles.input}
value={this.state.input}
onChange={this.handleInputChange.bind(this)}
onKeyDown={this.handleInputKeyDown.bind(this)}
/>
</ul>
</label>
);
}
}
In your componentDidMount you are fetching data, and getting back an array of objects, and setting the state value users to the array of objects. That's all good and exactly what you should be doing.
The problem is in the render method when you are looping through the array of users. Remember that each user in the array is an object. Look at the jsx you have within the li element. You are rendering the user object, and an object is an invalid react child. Instead, you need to render the particular fields from the object.
Example, if the object contains a name field, and an email field, render {user.name} or {user.email}. That way you can render the particular fields of data from the user object.
<li
key={i}
style={styles.users}
onClick={this.handleRemoveItem(i).bind(this)}
>
Name: {user.name}
Email: {user.email}
Id: {user.id}
<span>(x)</span>
</li>
It seems you may still have some questions about passing props to a component. This only addresses the particular error you are seeing. If you still have questions let me know.

Async issue with React render happening before set.state happens

I am having some trouble dealing with an async issue. The render is happening before the state is set at getStepContent(0) causing me to lose access to the state's values when I pass it down a component (CartInfo). Any ideas?
class Cart extends PureComponent {
constructor(props) {
super(props);
this.state = {
order: [],
error: null,
finished: false,
stepIndex: 0
};
}
componentWillMount() {
Meteor.call("orders.getLastOrder", (error, response) => {
if (error) {
this.setState(() => ({ error: error }));
console.log(error);
} else {
this.setState(() => ({ order: response }));
console.log(this.state.order);
}
});
}
goBack = () => this.props.history.push("/shop");
goCart = () => this.props.history.push("/cart");
handleNext = () => {
const { stepIndex } = this.state;
this.setState({
stepIndex: stepIndex + 1,
finished: stepIndex >= 2
});
};
handlePrev = () => {
const { stepIndex } = this.state;
if (stepIndex > 0) {
this.setState({ stepIndex: stepIndex - 1 });
}
};
getStepContent(stepIndex) {
let { order } = this.state;
switch (stepIndex) {
case 0:
while (!order) {
return getStepContent(0);
}
return <CartInfo CartInfo={order} />;
case 1:
return "What is an ad group anyways?";
case 2:
return "This is the bit I really care about!";
default:
return "You're a long way from home sonny jim!";
}
}
render() {
const { finished, stepIndex, order } = this.state;
const contentStyle = { margin: "0 16px" };
return (
<CartPage pageTitle="Cart" history goBack={this.goBack}>
<div className="CartHomePage">
<div style={{ width: "100%", maxWidth: 700, margin: "auto" }}>
<Stepper activeStep={stepIndex}>
<Step>
<StepLabel>Confirm your order</StepLabel>
</Step>
<Step>
<StepLabel>Where should we send it to?</StepLabel>
</Step>
<Step>
<StepLabel>Enjoy!</StepLabel>
</Step>
</Stepper>
<div style={contentStyle}>
{finished
? <p>
<a
href="#"
onClick={event => {
event.preventDefault();
this.setState({ stepIndex: 0, finished: false });
}}
>
Click here
</a>{" "}
to reset the example.
</p>
: <div>
{this.getStepContent(stepIndex)}
<div style={{ marginTop: 12 }}>
<FlatButton
label="Back"
disabled={stepIndex === 0}
onClick={this.handlePrev}
style={{ marginRight: 12 }}
/>
<RaisedButton
label={stepIndex === 2 ? "Finish" : "Next"}
primary={true}
onClick={this.handleNext}
/>
</div>
</div>}
</div>
</div>
<div>
{/* {order.map((item, i) => <div key={i}> {item.name}
{item.price} {item.quantity}</div>)} */}
{/* {this.state.order[0]} */}
</div>
</div>
</CartPage>
);
}
}
export default Cart;
This is the component I am passing it on to
import React from "react";
import { withRouter } from "react-router";
const CartInfo = ({ CartInfo }) =>
<div>
{CartInfo[0].name}
</div>;
export default withRouter(CartInfo);
This is the error code I am currently getting "CartInfo.jsx:6 Uncaught TypeError: Cannot read property 'name' of undefined at CartInfo"
It looks like you are trying to access CartInfo[0].name before the data has been fetched, which throws an error. You can change CartInfo component to something like this:
const CartInfo = ({ CartInfo }) => {
if (CartInfo[0]) {
return(
<div>
{CartInfo[0].name}
</div>;
);
}
}
This way the component will return null, then when the order data is fetched it will rerender and CartInfo[0] will not be undefined.
Another way to do this would be to use lodash _.get which returns undefined instead of throwing an error when you try to access properties of undefined.

Resources