1
votes

i am using react redux. In this Profiles component i use this.startSetUsers in componentWillMount to trigger fetching users from the mongodb and store it to state. then i mapStateToProps to get users from state as props, so i get this.props.users. Then in render i try to pass users as props to ProfilesList component and render that or if no users render no users from Profile component.

Now this works if state is empty and i navigate to ProfilesList or if i refresh that particular url with ProfilesList component but if go to ProfilePage component where through startSetUser i get only one user in state and show it in ProfilePage and then from that component where now state has one user i try to go to ProfilesList via < Link> show all users < /Link> i get an error "this.props.users.map is not a function" and i guess that is because mapStateToProps isnt finished before rendering and using this.props.users.map

i hope someone understood me, this was a bit mouthful. :D is there some work around this?

class Profiles extends React.Component {
  componentWillMount() {
    this.props.startSetUsers()
  }

  render() {
    return(
      <div className="content-container content-container--list">
      <div className="list-header">
        <div className="show-for-mobile">Users</div>
        <div className="show-for-desktop">User</div>
        <div className="show-for-desktop">Joined</div>
      </div>
      <div className="list-body">
        {this.props.users.length === 0 ? (
          <div className="list-item list-item--message">
              <span>No users</span>
            </div>
        ) : (
          this.props.users.map((user) => {
            return <ProfilesList key={user._id} {...user} />
          })
        )
      }
      </div>
      </div>
    )
  }
}

const mapStateToProps = (state) => ({
  users: state.user
})

export default connect(mapStateToProps, actions)(Profiles)

ProfilesList

const ProfilesList = ({ username, email, created_at, avatar }) => (
    <Link className="list-item" to={`/user/${username}`}>
      <div className="list-item__avatar-title">
        <div  className="list-item__avatar">
          <img src={avatar || 'https://image.ibb.co/bUv8k7/NoImage.png'} />
        </div>
        <div>
        <h3 className="list-item__title">{username}</h3>
        <span className="list-item__sub-title">{email}</span>
        <br />
        <span className="list-item__sub-title">List of articles: X</span>

        </div>
      </div>
      <div>
        <h4 className="list-item__joined">{moment(created_at).format('MMMM Do, YYYY')}</h4>
      </div>
    </Link>
)

export default ProfilesList;

Profile

class Profile extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      usersLoaded: false
    };
  }

  componentWillMount() {
     this.props.startSetUser(this.props.match.params.id)
  }

  render() {
    return(
      <div>
        {
          this.props.user && this.props.match.params.id ?
          <ProfilePage {...this.props}/> :
          <div>404</div>
        }
      </div>
    )
  }
}

const mapStateToProps = (state, props) => ({
  user: state.user
})

export default connect(mapStateToProps, actions)(Profile)

ProfilePage

const ProfilePage = props => {
  const {user} = props;

  return (
    <div className="section">
      <div className="profile__header">
        <div className="profile__name">
          <h2>{`${user.firstName} ${user.lastName}`}</h2>
          <h3>{user.email}</h3>
        </div>
      </div>
      <div className="profile__content">
            <div className="photo-container">
              <div className="photo">
                <img src={user.avatar || 'https://image.ibb.co/bUv8k7/NoImage.png'} />
              </div>
            </div>
            <div className="profile__info">
              <p>Username: <strong>{user.username}</strong></p>
              <li>Email: <strong>{user.email}</strong></li>
              <li>Location: <strong>{user.location}</strong></li>
              <li>City: <strong>{user.city || renderMessage}</strong></li>
              <li>Birthday: <strong>{`${user.day} ${user.month}, ${user.year}`}</strong></li>
              <li>Gender: <strong>{user.gender}</strong></li>
              <li>Joined: <strong>{moment(user.created_at).format('MMMM Do, YYYY')}</strong></li>
            </div>
            <div className="profile__button">
              <button className="button">Edit</button>
              <Link to="/users" className="button">List of all users</Link>
            </div>
      </div>
    </div>
  );
};

export default ProfilePage;

reducer

const userReducer = (state = [], action) => {
  switch (action.type) {
    case 'SET_USER':
      return action.user;
    case 'SET_USERS':
      return action.users;
    default:
      return state;
  }
};

export default userReducer;

edit: forgot to post action

// SET_USER
export const setUser = (user) => ({
  type: 'SET_USER',
  user
})

export const startSetUser = (username) => {
  return (dispatch) => {
    axios.get(`${ROOT_URL}/user/${username}`)
      .then(response => {
        dispatch(setUser(response.data))
      })
  }
}

// SET_USERS ALL
export const setUsers = (users) => ({
  type: 'SET_USERS',
  users
})

export const startSetUsers = () => {
  return (dispatch) => {
    axios.get(`${ROOT_URL}/users`)
      .then(response => {
        dispatch(setUsers(response.data))
      })
  }
}
2

2 Answers

0
votes

Looks like it's an issue with your reducer:

const userReducer = (state = [], action) => {
  switch (action.type) {
    case 'SET_USER':
      return action.user;
    case 'SET_USERS':
      return action.users;
    default:
      return state;
  }
};

export default userReducer;

You are overwriting state with action.user and action.users. I'm going to guess that action.user is an object and action.users is an array. So when ProfileList renders, it sees that this.state.users exists, BUT you set it to an object instead of an array.

I would make a new property in the userReducer for activeUser or make a new reducer to handle saving the active user.

const initialState = {
    activeUser: null,
    users: []
};

const userReducer = (state = initialState, action) => {
  switch (action.type) {
    case 'SET_USER':
      state.activeUser = action.user;
      return state;
    case 'SET_USERS':
      state.users = action.users
      return state;
    default:
      return state;
  }
};

export default userReducer;
0
votes

Thanks Chase, you got me thinking in right direction, i've managed to find solution, the reducer was the problem indeed

i've changed the reducer to this

const userReducer = (state = {}, action) => {
  switch (action.type) {
    case 'SET_USER':
      return { ...state, user: action.payload }
    case 'SET_USERS':
      return { ...state, users: action.payload }
    default:
      return state;
  }
};

export default userReducer;

and it works:)

alose here is the action that i changed to work with newly changed reducer

// SET_USER
export const setUser = (user) => ({
  type: 'SET_USER',
  payload: user
})

export const startSetUser = (username) => {
  return (dispatch) => {
    axios.get(`${ROOT_URL}/user/${username}`)
      .then(response => {
        dispatch(setUser(response.data))
      })
  }
}

// SET_USERS ALL
export const setUsers = (users) => ({
  type: 'SET_USERS',
  payload: users
})

export const startSetUsers = () => {
  return (dispatch) => {
    axios.get(`${ROOT_URL}/users`)
      .then(response => {
        dispatch(setUsers(response.data))
      })
  }
}