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
Related
I am new to React Redux. I am not sure what is wrong on my code. There is no error on the terminal but when I take a look on the browser there is a TypeError. ItemsProduct was on the props. I was wondering why it returns an error when I am trying to access the properties.
productDescription.js
import React, { Component } from "react";
import { Link } from "react-router-dom";
import { connect } from "react-redux";
import axios from "axios";
import {
fetchProductsRequests,
fetchProductsSuccess,
fetchProductError,
} from "../../actions/productActions";
class ProductDescription extends Component {
componentDidMount() {
this.props.fetchProducts();
}
render() {
return (
<>
<div className="grid grid-cols-3 gap-6 mb-10">
<div className="col-start-2 col-end-4">
<h4>{this.props.itemsProduct[0].name}</h4>
</div>
</div>
</>
);
}
}
const mapStateToProps = (state, ownProps) => {
return {
itemsProduct: state.rootProduct.products.filter(
(prod) => prod.id == ownProps.match.params.id
),
};
};
const mapDispatchToProps = (dispatch) => {
return {
fetchProducts: () => {
dispatch(fetchProductsRequests());
axios
.get("http://localhost:3000/js/products.json")
.then((response) => {
dispatch(fetchProductsSuccess(response.data));
})
.catch((error) => {
dispatch(fetchProductError(error.message));
});
},
};
};
export default connect(mapStateToProps, mapDispatchToProps)(ProductDescription);
productActions.js
export const FETCH_PRODUCTS_REQUESTS = "FETCH_PRODUCTS_REQUESTS";
export const FETCH_PRODUCTS_SUCCESS = "FETCH_PRODUCTS_SUCCESS";
export const FETCH_PRODUCTS_ERROR = "FETCH_PRODUCTS_ERROR";
export const fetchProductsRequests = () => {
return {
type: FETCH_PRODUCTS_REQUESTS,
};
};
export const fetchProductsSuccess = (product) => {
return {
type: FETCH_PRODUCTS_SUCCESS,
payload: product,
};
};
export const fetchProductError = (error) => {
return {
type: FETCH_PRODUCTS_ERROR,
payload: error,
};
};
productReducer.js
const initialState = {
loading: true,
products: [],
error: "",
};
const productReducer = (state = initialState, action) => {
switch (action.type) {
case "FETCH_PRODUCTS_REQUESTS":
return {
...state,
loading: true,
};
case "FETCH_PRODUCTS_SUCCESS":
return {
loading: false,
products: action.payload,
error: "",
};
case "FETCH_PRODUCTS_ERROR":
return {
loading: false,
products: [],
error: action.payload,
};
default:
return state;
}
};
export default productReducer;
Root Reducer
import { combineReducers } from "redux";
import productReducer from "./productReducer";
const rootReducer = combineReducers({
rootProduct: productReducer,
});
export default rootReducer;
You can do a quick check if there is data coming from your axios by doing this (it will prevent any undefined or null values)
dispatch(fetchProductsSuccess(response.data || 'no data'));
Also you should return your state in the reducer as follows:
case "FETCH_PRODUCTS_SUCCESS":
return {
...state,
loading: false,
products: action.payload,
error: "",
};
case "FETCH_PRODUCTS_ERROR":
return {
...state,
loading: false,
products: [],
error: action.payload,
};
Your
itemsProduct: state.rootProduct.products.filter(
(prod) => prod.id == ownProps.match.params.id
),
may return an empty array meaning you will not be able to retrieve that object in your view
<h4>{this.props.itemsProduct[0].name}</h4>
Hello, I am new to redux and I am struggling with a problem. I am trying to access and map over the comments within my post array. However, I am not sure how to do this. So far, I've tried changing the actions and reducers in order to solve this issue. I think the problem is within the react and redux. I can't tell if my mapStateToProps is working correctly. Also, the state is being fetched from my express server and it seems to be working properly as you can see in the picture.
My getPost action:
export const getPost = (group_id, post_id) => async dispatch => {
try {
const res = await axios.get(`/api/groups/${group_id}/${post_id}`);
dispatch({
type: GET_POST,
payload: res.data
});
} catch (error) {
dispatch({
type: POST_ERROR,
payload: { msg: error.response.statusText, status: error.response.status }
});
}
};
The initial state:
const initialState = {
groups: [],
group: [],
loading: true,
error: {}
};
The reducer:
case GET_POST:
return {
...state,
post: payload,
loading: false
};
Where I'm trying to map over the comments:
import React, { Fragment, useEffect } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { getPost } from '../../../redux/actions/group';
const Post = ({ getPost, post, match }) => {
useEffect(() => {
getPost(match.params.group_id, match.params.post_id);
}, [getPost, match.params.group_id, match.params.post_id]);
// I want to map over the comments here
return (
{post.comments.map(comment => ({ comment }))}
);
};
Post.propTypes = {
getPost: PropTypes.func.isRequired,
group: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
post: state.post
});
export default connect(mapStateToProps, { getPost })(Post);
You can access nested object with some tricks using redux, we have use this way in our prod env for some time.
First the reducer (you can make this reducer even more complex)
const LocalStorageReducer = createReducer<Store['localStorage']>(
new LocalStorage(),
{
saveLocalStorageItem(state: LocalStorage, action: any) {
return {...state, [action.payload.item]: action.payload.value}; // <= here
},
}
);
For Actions
export const actions = {
saveLocalStorageItem: (payload: InputAction) => ({type: 'saveLocalStorageItem', payload}),
};
For the type InputAction
export class InputAction {
item: string;
value: string | Array<string> | null | boolean;
constructor() {
this.item = '';
this.value = null;
}
}
For the handler in component
this.props.saveLocalStorage({ item: 'loading', value: false });
In this way you can go one way done to the nested redux store.
For complex (4-5 levels) and multiple (> 2 times) data structure, there are other ways, but in most situations, it's good enough.
TypeError: Cannot read property '' of undefined ı have no idea why ı am getting this error while I do check the code below everything seems fine :( trying to learn the way how react works :)
So what is the purpose of this since all the properties I wrap on contextprovider suchas contacts loading and the functions I need
import React, { useState, useContext } from 'react'
import ContactContext from '../context/contactContext'
export default function ContactForm() {
const name = useFormInput('')
const email = useFormInput('')
const contactContext = useContext(ContactContext)
const { addContact } = contactContext
const onSubmit = () => {
addContact(name.value, email.value)
name.onReset()
email.onReset()
}
return (
SOME HTML CODE HERE
)
}
//contactState.js
import React, { useReducer } from 'react'
import _ from 'lodash'
import ContactContext from './contactContext'
import ContactReducer from './contactReducer'
const ContactState = props => {
const initialState = {
contacts: [
{
id: '098',
name: 'Diana Prince',
email: 'diana#us.army.mil'
}
],
loading: false,
error: null
}
const [state, dispatch] = useReducer(ContactReducer, initialState)
const [contacts, loading] = state
const addContact = (name, email) => {
dispatch({
type: 'ADD_CONTACT',
payload: { id: _.uniqueId(10), name, email }
})
}
const delContact = id => {
dispatch({
type: 'DEL_CONTACT',
payload: id
})
}
return (
<ContactContext.Provider
value={{
contacts,
loading,
addContact,
delContact
}}
>
{props.children}
</ContactContext.Provider>
)
}
export default ContactState
//contactReducer.js
export default (state, action) => {
switch (action.type) {
case 'ADD_CONTACT':
return {
contacts: [...state, action.payload]
}
case 'DEL_CONTACT':
return {
contacts: state.contacts.filter(
contact => contact.id !== action.payload
)
}
case 'START':
return {
loading: true
}
case 'COMPLETE':
return {
loading: false
}
default:
throw new Error()
}
}
//contactContext.js
import { createContext } from 'react'
const contactContext = createContext()
export default contactContext
In your reducer, when adding a contact, you're spreading the wrong state key. This should fix it:
case 'ADD_CONTACT':
return {
contacts: [...state.contacts, action.payload]
}
I can't see where you are using ContactState in your app. If you don't use it and render your ContactForm component with it then you can't reach any context value. You should render it as:
<ContactState>
<ContactForm />
</ContactState>
in a suitable place in your app. Also, you can't get contacts and loading like that:
const [ contacts, loading ] = state;
state is not an array, it is an object here. You should use:
const { contacts, loading } = state
You can find a simplified version of your code below. I removed/changed some parts in order to run it as much as possible. You should fix your reducer as #Asaf David mentioned in their answer, but this is not the main problem here. After fixing the context issue, you can try to fix your reducer.
About your questions, if you try to understand how React works by looking at this example you can easily get confused. Because Context is an advanced concept (at least for the beginners). Also, the code uses useReducer with Context and this makes the things more complicated. If your intent is to understand the React itself then start with the beginner guide.
By using Context we can pass the data top-down to the deepest components. But, in order to use that data those components should be rendered as children of the context provider.
In your code, you are doing this in ContactState but you never use it. Also, in that component, you are defining a state with useReducer and feed your context with this state by value.
Finally, in your ContactForm component, you are using useContext hook to get the context data. In your current code since you don't render this component in a provider, contactContext is undefined and you are getting the error. You can't get addContact from undefined.
In my example, I'm retrieving the contacts to show something. Again, I've changed/removed some parts from your code.
const { createContext, useContext, useReducer } = React;
const ContactContext = createContext();
function ContactForm() {
// Changed those values
const name = "";
const email = "";
const contactContext = useContext(ContactContext);
// changed addContact -> contacts
const { contacts } = contactContext;
const onSubmit = () => {
addContact(name.value, email.value);
name.onReset();
email.onReset();
};
// added some data to render
return <div>{contacts[0].name}</div>;
}
function ContactReducer(state, action) {
switch (action.type) {
case "ADD_CONTACT":
return {
contacts: [...state, action.payload]
};
case "DEL_CONTACT":
return {
contacts: state.contacts.filter(
contact => contact.id !== action.payload
)
};
case "START":
return {
loading: true
};
case "COMPLETE":
return {
loading: false
};
default:
throw new Error();
}
}
const ContactState = props => {
const initialState = {
contacts: [
{
id: "098",
name: "Diana Prince",
email: "diana#us.army.mil"
}
],
loading: false,
error: null
};
const [state, dispatch] = useReducer(ContactReducer, initialState);
const { contacts, loading } = state;
const addContact = (name, email) => {
dispatch({
type: "ADD_CONTACT",
// removed lodash part, added a static id
payload: { id: 1, name, email }
});
};
const delContact = id => {
dispatch({
type: "DEL_CONTACT",
payload: id
});
};
return (
<ContactContext.Provider
value={{
contacts,
loading,
addContact,
delContact
}}
>
{props.children}
</ContactContext.Provider>
);
};
// added the relevant render part
ReactDOM.render(
<ContactState>
<ContactForm />
</ContactState>,
document.getElementById("root")
);
<script src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root" />
I wonder why my state todos named todo not todos in the redux dev tools ..
From where that name came ?
There is no initial state .. i wonder..
I'm following Stephen Grider udemy course but with todos instead of streams as a revision
why i have to return it by state.todo not state.todos??
My github Repo
Jsson server db.json file ( api file )
{
"todos": [
{
"title": "lorem ipsum ",
"description": "lorem ipsum",
"id": 4
}
]
}
todoReducer.js
import _ from 'lodash';
import {
CREATE_TODO,
EDIT_TODO,
FETCH_TODO,
FETCH_TODOS,
DELETE_TODO
} from '../actions/types';
export default (state = {}, action) => {
switch (action.type) {
case FETCH_TODOS:
return { ...state, ..._.mapKeys(action.payload, 'id') };
case CREATE_TODO:
case FETCH_TODO:
case EDIT_TODO:
return { ...state, [action.payload.id]: action.payload };
case DELETE_TODO:
return _.omit(state, action.payload);
default:
return state;
}
};
actions/index.js
import todos from '../apis/todos';
import history from '../history';
import {
SIGN_IN,
SIGN_OUT,
CREATE_TODO,
EDIT_TODO,
FETCH_TODO,
FETCH_TODOS,
DELETE_TODO
} from './types';
export const signIn = userId => {
return { type: SIGN_IN, payload: userId };
};
export const signOut = () => {
return { type: SIGN_OUT };
};
export const fetchTodos = () => async dispatch => {
const response = await todos.get('/todos');
dispatch({ type: FETCH_TODOS, payload: response.data });
};
export const createTodo = formValues => async dispatch => {
const response = await todos.post('/todos', formValues);
dispatch({ type: CREATE_TODO, payload: response.data });
history.push('/');
};
https://github.com/HosMercury/todos/blob/master/src/reducers/index.js here you are passing the list as todo not as todos.
Here you can check console in sandbox
https://codesandbox.io/s/github/HosMercury/todos
In the last couple of days I have been working on my Redux api call. I am actually having a problem getting the data back to the view component. Currently I'm able to see the data in the in the action generator, so I know at least I'm able to get it. However, nothing is showing in the view. I imagine it may have something to do with when it's loading. This is why I tried to load it when the component is rendering.
https://djangoandreact.herokuapp.com/user/1 is what is not loading.
codesandbox: https://codesandbox.io/s/zlor60q3jm?from-embed
Should be able to go to /user/1 at the end similar to going to /1 brings up an article(Tough Hope)
Heres the view component:
import React from "react";
import { connect } from "react-redux";
import { fetchUser } from "../store/actions/userActions";
class UserDetailView extends React.Component {
componentDidMount() {
const userID = this.props.match.params.userID;
fetchUser(userID); //fixed
}
render() {
const { user } = this.props.user;
console.log(user);
return (
<div>
<h3>{user.username}</h3>
</div>
);
}
}
const mapStateToProps = state => ({
user: state.user
});
const mapDispatchToProps = (dispatch, ownProps) => ({
fetchUser: dispatch(fetchUser(ownProps.match.params.userID))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(UserDetailView);
Action generator
import axios from "axios";
import { thunk } from "react-redux";
export function fetchUser(userID) {
console.log(userID);
return dispatch => {
return axios.get(`/api/user/${userID}`).then(res => {
dispatch(fetchUserSuccess(res.data));
console.log(res.data); // loads data
});
};
}
// Handle HTTP errors since fetch won't.
function handleErrors(response) {
if (!response.ok) {
throw Error(response.statusText);
}
return response;
}
export const FETCH_USER_BEGIN = "FETCH_USER_BEGIN";
export const FETCH_USER_SUCCESS = "FETCH_USER_SUCCESS";
export const FETCH_USER_FAILURE = "FETCH_USER_FAILURE";
export const fetchUserBegin = () => ({
type: FETCH_USER_BEGIN
});
export const fetchUserSuccess = user => ({
type: FETCH_USER_SUCCESS,
payload: { user }
});
export const fetchUserFailure = error => ({
type: FETCH_USER_FAILURE,
payload: { error }
});
Reducers(which are probably fine):
import {
FETCH_USER_BEGIN,
FETCH_USER_SUCCESS,
FETCH_USER_FAILURE
} from "../actions/actionTypes";
const initialState = {
user: {},
loading: false,
error: null
};
export default function userReducer(state = initialState, action) {
switch (action.type) {
case FETCH_USER_BEGIN:
return {
...state,
loading: true,
error: null
};
case FETCH_USER_SUCCESS:
return {
...state,
loading: false,
user: action.payload.user
};
case FETCH_USER_FAILURE:
return {
...state,
loading: false,
error: action.payload.error,
user: {}
};
default:
return state;
}
}
folks. I found it.
case FETCH_USER_SUCCESS:
return {
...state,
loading: false,
user: action.payload.user
};
user is supposed to be user:action.payload
Also, the user action was supposed to be
export const fetchUserSuccess = user => ({
type: FETCH_USER_SUCCESS,
payload: user
})
WOOOOW. But, honestly, I learned so much about Redux in the last two sleepless nights, it was worth the pain. Really was. Now, instead of copy pasta, I know what an action generator is and does, and reducer (obvi)