简体   繁体   中英

Grab and display single JSON object in React-Redux

I did a tutorial and am able to display all of my Articles objects but I cannot figure out how to modify the code in order to grab one specific object and store it in the state. I have tried a lot of different things but I keep getting 'TypeError: Cannot read property 'name' of undefined'.

One note, the id that I am looking for is stored in 'this.props.match.params.id' but I don't really know what this means or how to use it. Thanks

ArticleShow.js

import React, { Component } from "react";
import { Container } from "reactstrap";
import { connect } from "react-redux";
import { getArticle } from "../actions/articleActions";
import PropTypes from "prop-types";

class articleShow extends Component {
  componentDidMount() {
    this.props.getArticle();
  }

  render() {
    const { article } = this.props.article;


    return (
      <Container>
        {article.name}
        <br />
        {article.author}
        <br />
        {article.body.split("\r").map((c) => {
          return <p> {c} </p>;
        })}
        <br />
      </Container>
    );
  }
}

ArticleShow.propTypes = {
  getArticle: PropTypes.func.isRequired,
  article: PropTypes.object.isRequired,
};

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

export default connect(mapStateToProps, { getArticle })(ArticleShow);

articleActions.js

import axios from "axios";

import {
  GET_ARTICLES,
  GET_ARTICLE,
} from "./types";

export const getArticles = () => (dispatch) => {
  dispatch(setArticlesLoading());
  axios.get("/api/articles").then((res) =>
    dispatch({
      type: GET_ARTICLES,
      payload: res.data,
    })
  );
};

export const getArticle = (id) => (dispatch) => {
  dispatch(setArticlesLoading());
  axios.get(`/api/articles/${id}`).then((res) =>
    dispatch({
      type: GET_ARTICLE,
      payload: res.data,
    })
  );
};

articleReducer.js

import {
  GET_ARTICLES,
  GET_ARTICLE,
} from "../actions/types";

const intialState = {
  articles: [],
  loading: false,
};

export default function (state = intialState, action) {
  switch (action.type) {
    case GET_ARTICLES:
      return {
        ...state,
        articles: action.payload,
        loading: false,
      };
    case GET_ARTICLE:
      return {
        ...state,
        article: action.payload,
        loading: false,
      };
  default:
      return state;
  }
}

routes/api/articles.js

const express = require("express");
const router = express.Router();

// Article Model
const Article = require("../../models/Article");

router.get("/", (req, res) => {
  Article.find()
    .sort({ date: -1 })
    .then((articles) => res.json(articles));
});

router.get("/:id", (req, res) => {
  Article.findById(req.params.id).then((article) => res.json(article));
});

module.exports = router;

models/Article.js

const mongoose = require("mongoose");
const Schema = mongoose.Schema;

// create schema
const ArticleSchema = new Schema({
  name: {
    type: String,
    required: true,
  },
  author: {
    type: String,
    required: true,
  },
  body: {
    type: String,
    required: true,
  },
  date: {
    type: Date,
    default: Date.now,
  },
});

module.exports = Article = mongoose.model("article", ArticleSchema);

store.js

import { createStore, applyMiddleware, compose } from "redux";
import thunk from "redux-thunk";
import rootReducer from "./reducers";

const initialState = {};

const middleware = [thunk];

const store = createStore(
  rootReducer,
  initialState,
  compose(
    applyMiddleware(...middleware),
    window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
  )
);

export default store;

First, if possible I would suggest you to reconsider how you are storing your data in articles node. In articles array, it would save you lots of complications if you store articles as object instead of array with article id as key if we are updating/deleting/accessing these articles Secondly, Article should be child component of Articles which would ensure a particular article would always exists when an Article component loads

articles: {
  5f0b628f172467147fbed0c2: {
    "name":"Article 4",
    "author":"Carol Henderson"
  }
}

In that scenario, your switch block would look like this:

case GET_ARTICLES:
      return {
        ...state,
        articles: action.payload.reduce((accObj, curObj) => {...accObj, [curObj._id]: curObj}, {}),
        loading: false,
      };
case GET_ARTICLE:
      return {
        ...state,
        articles: {
          [action.payload._id]: action.payload,
        },
        loading: false,
      };

But still if you keep it in your current shape due to some use case, you could try this:

case GET_ARTICLE:
      // find the article and merge more details
      const article = state.articles.find((art) => art._id === action.payload._id);
      article = {...article, ...action.payload};
      // since state has reference of article via references, your state has now new values.
      return {
        ...state,
        loading: false,
      };

I haven't tested the code. But it should give you an idea how to go about it

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM