Failed prop type issue - reactjs

I have the following structure:
and a TodoList component as follow:
import React from 'react'
import PropTypes from 'prop-types'
import Todo from './Todo'
const TodoList = ({ todos, onTodoClick }) => (
<ul>
{todos.map( (todo, i) => (
<Todo key={i} {...todo} onClick={() => onTodoClick(todo.id)} />
))}
</ul>
)
TodoList.propTypes = {
todos: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.number.isRequired,
completed: PropTypes.bool.isRequired,
text: PropTypes.string.isRequired
}).isRequired
).isRequired,
onTodoClick: PropTypes.func.isRequired
}
export default TodoList
I am getting the following error:
this is my index in the reducers folder:
import { combineReducers } from 'redux'
import {
ADD_TODO,
TOGGLE_TODO,
SET_VISIBILITY_FILTER,
VisibilityFilters
} from '../actions/index'
const { SHOW_ALL } = VisibilityFilters
function visibilityFilter(state = SHOW_ALL, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return action.filter
default:
return state
}
}
function todos(state = [], action) {
switch (action.type) {
case ADD_TODO:
return [
...state,
{
text: action.text,
completed: false
}
]
case TOGGLE_TODO:
return state.map((todo, index) => {
if (index === action.index) {
return Object.assign({}, todo, {
completed: !todo.completed
})
}
return todo
})
default:
return state
}
}
const todoApp = combineReducers({
visibilityFilter,
todos
})
export default todoApp
The onTodoClick doesn't fire either in TodoList and it's defined here:
import { connect } from 'react-redux'
import { toggleTodo } from '../actions/index'
import TodoList from '../components/TodoList'
const getVisibleTodos = (todos, filter) => {
switch (filter) {
case 'SHOW_COMPLETED':
return todos.filter(t => t.completed)
case 'SHOW_ACTIVE':
return todos.filter(t => !t.completed)
case 'SHOW_ALL':
default:
return todos
}
}
const mapStateToProps = state => {
return {
todos: getVisibleTodos(state.todos, state.visibilityFilter)
}
}
const mapDispatchToProps = dispatch => {
return {
onTodoClick: id => {
dispatch(toggleTodo(id))
}
}
}
const VisibleTodoList = connect(
mapStateToProps,
mapDispatchToProps
)(TodoList)
export default VisibleTodoList

Related

React-Redux TypeError: Cannot read property 'map' of undefined

When I press the button increment or decrement, I get this message.
I think all the steps are okay but I can not understand why, when I press the button (+ or -), the store/state is set to undefined;
src/App.js
import React, { Component } from "react";
import Counter from "./components/counter";
import { connect } from "react-redux";
class App extends Component {
render() {
return (
<div>
{this.props.data.map(counter => (
<Counter key={counter.id} id={counter.id} value={counter.value} />
))}
</div>
);
}
}
const mapStateToProps = state => {
return {
data: state.data
};
};
export default connect(mapStateToProps)(App);
src/components/counter.js
import React, { Component } from "react";
import { connect } from "react-redux";
class Counter extends Component {
render() {
return (
<div>
<h1>{this.props.value}</h1>
<button onClick={() => this.props.onIcrement(this.props.id)}>
+ UP
</button>
<button onClick={() => this.props.onDecrement(this.props.id)}>
- DOWN
</button>
</div>
);
}
}
const mapDispatchToProps = dispatch => {
return {
onIcrement: id => dispatch({ type: "INCREMENT", key: id }),
onDecrement: id => dispatch({ type: "DECREMENT", key: id })
};
};
export default connect(
null,
mapDispatchToProps
)(Counter);
src/store/reducer.js
const initialState = {
data: [{ id: 1, value: 4 }, { id: 2, value: 0 }]
};
const reducer = (state = initialState, action) => {
const newState = { ...state };
switch (action.type) {
case "INCREMENT":
return newState.data.map(el => {
if (action.key === el.id) {
return el.value++;
}
return el.value;
});
case "DECREMENT":
return newState.data.map(el => {
if (action.key === el.id) {
return el.value--;
}
return el.value;
});
default:
return newState;
}
};
export default reducer;
src/index.js
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import * as serviceWorker from "./serviceWorker";
import { createStore } from "redux";
import { Provider } from "react-redux";
import reducer from "./store/reducer";
const store = createStore(reducer);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
serviceWorker.unregister();
................................................................................
In your reducer, you are returning an array instead of an object with a key data which is an array.
The following code should work.
const reducer = (state = initialState, action) => {
switch (action.type) {
case "INCREMENT":
return {
data: state.data.map(el => { // <- return object with data instead of newState.data.map
if (action.key === el.id) {
return { ...el, value: el.value + 1};
}
return el;
})
};
case "DECREMENT":
return {
data: state.data.map(el => { // <- same here too
if (action.key === el.id) {
return { ...el, value: el.value - 1 };
}
return el;
})
};
default:
return state;
}
};

React / Redux - is a function prop for deferred state loading in mapStateToProps bad?

This is my component:
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { Divider } from "antd";
import MovieList from "../components/MovieList";
import IncreaseCountButton from "../components/IncreaseCountButton";
import DeleteButton from "../components/DeleteButton";
import { deleteMovie, increaseCount } from "../actions/movies";
import { getIsDeleting, getIsIncreasing } from "../reducers/actions";
export class MovieListContainer extends Component {
constructor(props) {
super(props);
this.handleIncrease = this.handleIncrease.bind(this);
this.handleDelete = this.handleDelete.bind(this);
}
static propTypes = {
isIncreasing: PropTypes.func.isRequired,
isDeleting: PropTypes.func.isRequired,
};
async handleIncrease(movie) {
await this.props.increaseCount(movie, this.props.token);
}
async handleDelete(movie) {
await this.props.deleteMovie(movie.id, this.props.token);
}
render() {
return (
<MovieList movies={this.props.movies}>
{(text, movie) => (
<div>
<IncreaseCountButton
onIncrease={() => this.handleIncrease(movie)}
loading={this.props.isIncreasing(movie.id)}
/>
<Divider type="vertical" />
<DeleteButton
onDelete={() => this.handleDelete(movie)}
loading={this.props.isDeleting(movie.id)}
/>
</div>
)}
</MovieList>
);
}
}
export const mapStateToProps = state => ({
isIncreasing: id => getIsIncreasing(state, id),
isDeleting: id => getIsDeleting(state, id),
});
export default connect(
mapStateToProps,
{ deleteMovie, increaseCount }
)(MovieListContainer);
I feel like this might be bad for performance/reconciliation reasons, but not sure how else to retrieve the state in a way that hides implementation details.
Gist link: https://gist.github.com/vitalicwow/140c06a52dd9e2e062b2917f5c741727
Any help is appreciated.
Here is how you can handle these asynchronous actions with redux. You can use thunk to perform 2 actions and can store a flag to determine what is being done to an object (Deleting, Changing, etc):
action
export const deleteMovieAction = id => {
return dispatch => {
dispatch({ type: "MOVIE_DELETING", id });
setTimeout(() => {
dispatch({ type: "MOVIE_DELETED", id });
}, 2000);
};
};
reducer
const rootReducer = (state = initialState, action) => {
switch (action.type) {
case "MOVIE_DELETING": {
const movies = [...state.movies];
movies.find(x => x.id === action.id).isDeleting = true;
return { ...state, movies };
}
case "MOVIE_DELETED": {
const movies = state.movies.filter(x => x.id !== action.id);
return { ...state, movies };
}
default:
return state;
}
};
https://codesandbox.io/s/k3jnv01ymv
An alternative is to separate out the ids into a new array that are being deleted
const rootReducer = (state = initialState, action) => {
switch (action.type) {
case "MOVIE_DELETING": {
const movieDeletingIds = [...state.movieDeletingIds, action.id];
return { ...state, movieDeletingIds };
}
case "MOVIE_DELETED": {
const movieDeletingIds = state.movieDeletingIds.filter(
x => x.id !== action.id
);
const movies = state.movies.filter(x => x.id !== action.id);
return { ...state, movieDeletingIds, movies };
}
default:
return state;
}
};
https://codesandbox.io/s/mj52w4y3zj
(This code should be cleaned up, but is just to demo using thunk)

React-redux state with external json

I want to list posts in PostList.js component from JSON file
I use react-redux for state managment and redux-saga to get json file
My components are Post.js and PostList.js:
Post.js
const Post = ({ post }) => {
<li>
{post}
</li>
}
export default Post
PostList.js
class PostList extends React.Component {
componentDidMount() {
console.log('did mount');
this.props.fetchPosts();
}
render() {
return (
<div>
<ul>
{this.state.posts(post => (
<Post key={post.id} {...post} />
))}
</ul>
</div>
)
}
}
export default PostList
Reducer
export default (state = [], action) => {
switch (action.type) {
case "FETCH_POSTS":
return {
...state,
loading: true,
posts: []
}
case "FETCH_FAILD":
return {
...state,
loading: false,
posts: []
}
case "FETCH_SUCCESS":
return Object.assign({}, state, {
posts: action.posts
})
default:
return state;
}
}
Actions.js
export const fetchPosts = () => {
return {
type: 'FETCH_POSTS'
}
}
export const fetchSuccess = data => ({
type: "FETCH_SUCCESS",
posts: data
})
export const fetchFaild = () => {
return {
type: 'FETCH_FAILD'
}
}
GetPosts.js (Container)
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import PostList from '../components/PostList'
import { fetchPosts } from '../actions'
const mapStateToProps = state => ({ posts: state.posts });
const mapDispatchToProps = dispatch => bindActionCreators({fetchPosts}, dispatch);
const GetPosts = connect(
mapStateToProps,
mapDispatchToProps
)(PostList)
export default GetPosts
Saga.js
export function* fetchProducts() {
try {
console.log('saga')
const posts = yield call(api_fetchPost);
yield put({ type: "FETCH_SUCCESS", posts});
} catch (e) {
yield put({ type: "FETCH_FAILD", e});
return;
}
}
export function* watchFetchProducts() {
yield takeEvery("FETCH_POSTS", fetchProducts)
}
You are fetching posts from the state of your postlist component. Redux mapStateToProps map the redux state to connected component's props and not state
class PostList extends React.Component {
componentDidMount() {
console.log('did mount');
this.props.fetchPosts();
}
render() {
return (
<div>
<ul>
{this.props.posts && this.props.posts.map(post => {
return ( <Post key={post.id} {...post} /> );
})}
</ul>
</div>
)
}
}
export default PostList
Change this.state.posts to this.props.posts

Redux action not firing onclick

Doing a redux todo to learn and having trouble on the toggle todo.
When i trigger the onclick i get an error in the console.
"Failed prop type: The prop items is marked as required in ItemsList, but its value is undefined."
and
"Cannot read property 'length' of undefined" on items.length
I've consoled logged the action and it seems to be returning the state. not sure what i'm doing wrong. code is below.
actions
export const addItem = content => {
return { type: ADD_ITEM, content };
};
export const toggleTodo = (id) => {
return {
type: TOGGLE_TODO,
id,
};
};
my initial state is:
import { ADD_ITEM, TOGGLE_TODO } from './constants';
let nextId = 4;
export const initialState = {
items: [
{ id: 1, content: 'Call mum', completed: false},
{ id: 2, content: 'Buy cat food', completed: true },
{ id: 3, content: 'Water the plants', completed: false },
],
};
const reducer = (state = initialState, action) => {
console.log("action", action.type);
switch (action.type) {
case ADD_ITEM:
const newItem = {
id: nextId++,
content: action.content,
completed: false
};
return {
...state,
items: [...state.items, newItem],
};
case TOGGLE_TODO:
return state.items.map(todo => {
console.log("state", state);
console.log("state.items", state.items);
console.log("todo",todo);
if (todo.id !== action.id) {
return state;
}
return {
...state,
completed: !todo.completed,
};
});
default:
return state;
}
};
export default reducer;
and my list component is
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import Todo from "../ItemTodo/index"
import { toggleTodo } from "../../logic/actions";
import './styles.css';
export const ItemsList = ({ items, onTodoClick }) => {
return (
<div>
<ul className={'itemsList-ul'}>
{items.length < 1 && <p id={'items-missing'}>Add some tasks above.</p>}
{items.map(item =>
<Todo
key={item.id}
{...item}
onClick={() => onTodoClick(item.id)}
/>
)}
</ul>
</div>
);
};
ItemsList.propTypes = {
items: PropTypes.array.isRequired,
onTodoClick: PropTypes.func.isRequired,
};
const mapStateToProps = state => {
return { items: state.todos.items };
};
const mapDispatchToProps = dispatch => ({
onTodoClick: id => dispatch(toggleTodo(id))
});
export default connect(mapStateToProps, mapDispatchToProps)(ItemsList);
and my todo component is
import React from 'react';
import PropTypes from 'prop-types';
const Todo = ({onClick, completed, content }) => (
<li
onClick={onClick}
style={{
textDecoration: completed ? 'line-through' : 'none',
}}
>
{content}
</li>
);
Todo.propTypes = {
onClick: PropTypes.func.isRequired,
completed: PropTypes.bool.isRequired,
content: PropTypes.string.isRequired,
};
export default Todo;
Create store
import React, { Component } from 'react';
import { Provider } from 'react-redux';
import configureStore from './redux/store';
import Header from './components/Header';
import ItemCreator from './components/ItemCreator';
import ItemsList from './components/ItemsList';
import './app.css';
const store = configureStore();
class App extends Component {
render() {
return (
<Provider store={store}>
<div className="app">
<Header />
<div>
<ItemCreator />
<ItemsList />
</div>
</div>
</Provider>
);
}
}
export default App;
import { createStore, applyMiddleware, compose } from 'redux';
import createReducer from './reducers';
const composeEnhancers =
(process.env.NODE_ENV !== 'production' &&
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) ||
compose;
const configureStore = (initialState = {}) => {
return createStore(
createReducer(),
initialState,
composeEnhancers(applyMiddleware())
);
};
export default configureStore;
import { combineReducers } from 'redux';
import reducer from '../logic/reducer';
export default function createReducer() {
return combineReducers({
todos: reducer,
});
}
I haven't tested it, but I think this part is incorrect TOGGLE_TODO:
return state.items.map(todo => {
console.log("state", state);
console.log("state.items", state.items);
console.log("todo",todo);
if (todo.id !== action.id) {
return state;
}
return {
...state,
completed: !todo.completed,
};
});
it should be:
return {
...state,
items: state.items.map((todo) => {
if (todo.id === action.id) {
return { ...todo, completed: !todo.completed };
}
return todo;
})
};

Redux can't find 'type' property in action

I use middleware in redux ,but when I dispatch a action ,but get error in browser:
Uncaught TypeError: Cannot read property 'type' of undefined
code is here:
import { createStore, applyMiddleware } from 'redux'
import thunkMiddleware from 'redux-thunk'
import createLogger from 'redux-logger'
import rootReducer from '../reducers/reducers'
import Immutable from 'immutable'
const loggerMiddleware = createLogger()
//const initialState=0
function configureStore() {
return createStore(
rootReducer,
{postsBySubreddit:{},selectedSubreddit:'reactjs'},
applyMiddleware(thunkMiddleware, loggerMiddleware)
)
}
export default configureStore
and my action :
import fetch from 'isomorphic-fetch'
import {actionCreator} from '../utils/creator'
const REQUEST_POSTS = 'REQUEST_POSTS'
const RECEIVE_POSTS = 'RECEIVE_POSTS'
const SELECT_SUBREDDIT = 'SELECT_SUBREDDIT'
const INVALIDATE_SUBREDDIT = 'INVALIDATE_SUBREDDIT'
export function selectSubreddit(reddit) {
return {
type: SELECT_SUBREDDIT,
reddit
}
}
export function invalidateReddit(reddit) {
return {
type: INVALIDATE_REDDIT,
reddit
}
}
function requestPosts(reddit) {
console.log('requestPosts')
return {
type: REQUEST_POSTS,
reddit
}
}
function receivePosts(reddit, json) {
return {
type: RECEIVE_POSTS,
reddit: reddit,
posts: json.data.children.map(child => child.data),
receivedAt: Date.now()
}
}
function fetchPosts(reddit) {
return dispatch => {
dispatch(requestPosts(reddit))
return fetch(`https://www.reddit.com/r/${reddit}.json`)
.then(response => response.json())
.then(json => dispatch(receivePosts(reddit, json)))
}
}
function shouldFetchPosts(state, reddit) {
if(state.postsByReddit.hasOwnProperty(reddit)){
const posts = state.postsByReddit[reddit]
}else{
posts=false
}
if (!posts) {
return true
}
if (posts.isFetching) {
return false
}
return posts.didInvalidate
}
export function fetchPostsIfNeeded(reddit) {
return (dispatch, getState) => {
if (shouldFetchPosts(getState(), reddit)) {
return dispatch(fetchPosts(reddit))
}
}
}
my components:
import React, { Component, PropTypes } from 'react'
import { connect } from 'react-redux'
import { selectSubreddit, fetchPostsIfNeeded, invalidateSubreddit } from '../actions/action'
import Picker from '../components/Picker'
import Posts from '../components/Posts'
class AsyncApp extends Component {
constructor(props) {
super(props)
this.handleChange = this.handleChange.bind(this)
this.handleRefreshClick = this.handleRefreshClick.bind(this)
}
componentDidMount() {
const { dispatch, selectedSubreddit } = this.props
dispatch(fetchPostsIfNeeded(selectedSubreddit))
}
componentWillReceiveProps(nextProps) {
if (nextProps.selectedSubreddit !== this.props.selectedSubreddit) {
const { dispatch, selectedSubreddit } = nextProps
dispatch(fetchPostsIfNeeded(selectedSubreddit))
}
}
handleChange(nextSubreddit) {
this.props.dispatch(selectSubreddit(nextSubreddit))
}
handleRefreshClick(e) {
e.preventDefault()
const { dispatch, selectedSubreddit } = this.props
console.log(this.props);
dispatch(invalidateSubreddit(selectedSubreddit))
dispatch(fetchPostsIfNeeded(selectedSubreddit))
}
render() {
const { selectedSubreddit, posts, isFetching, lastUpdated } = this.props
console.log(this.props)
return (
<div>
<Picker value={selectedSubreddit}
onChange={this.handleChange}
options={[ 'reactjs', 'frontend' ]} />
<p>
{lastUpdated &&
<span>
Last updated at {new Date(lastUpdated).toLocaleTimeString()}.
{' '}
</span>
}
{!isFetching &&
<a href='#'
onClick={this.handleRefreshClick}>
Refresh
</a>
}
</p>
{isFetching && posts.length === 0 &&
<h2>Loading...</h2>
}
{!isFetching && posts.length === 0 &&
<h2>Empty.</h2>
}
{posts.length > 0 &&
<div style={{ opacity: isFetching ? 0.5 : 1 }}>
<Posts posts={posts} />
</div>
}
</div>
)
}
}
AsyncApp.propTypes = {
selectedSubreddit: PropTypes.string.isRequired,
posts: PropTypes.array.isRequired,
isFetching: PropTypes.bool.isRequired,
lastUpdated: PropTypes.number,
dispatch: PropTypes.func.isRequired
}
function mapStateToProps(state) {
const { selectedSubreddit, postsBySubreddit } = state
console.log('1:'+postsBySubreddit)
const {
isFetching,
lastUpdated,
items: posts
} = postsBySubreddit[selectedSubreddit] || {
isFetching: true,
items: []
}
return {
selectedSubreddit,
posts,
isFetching,
lastUpdated
}
}
export default connect(mapStateToProps)(AsyncApp)
reducer:
import { combineReducers } from 'redux'
import {reducerCreator} from '../utils/creator'
import Immutable from'immutable'
import {SELECT_SUBREDDIT, INVALIDATE_SUBREDDIT ,REQUEST_POSTS, RECEIVE_POSTS} from '../actions/action'
let initialState=Immutable.fromJS({isFetching: false, didInvalidate: false,items:[]})
function selectedSubreddit(action) {
switch (action.type) {
case SELECT_SUBREDDIT:
return action.subreddit
default:
return state
}
}
function postsBySubreddit(action) {
console.log(action)
switch (action.type) {
case INVALIDATE_SUBREDDIT:
case RECEIVE_POSTS:
case REQUEST_POSTS:
return state.merge({
[action.subreddit]: posts(state[action.subreddit], action)
})
default:
return state
}
}
function posts(state=initialState,action) {
switch (action.type) {
case INVALIDATE_SUBREDDIT:
return state.merge({
didInvalidate: true
})
case REQUEST_POSTS:
return state.merge({
isFetching: true,
didInvalidate: false
})
case RECEIVE_POSTS:
return state.merge({
isFetching: false,
didInvalidate: false,
items: action.posts,
lastUpdated: action.receivedAt
})
default:
return state
}
}
const rootReducer = combineReducers({
postsBySubreddit,
selectedSubreddit
})
export default rootReducer
when I call fetchPostsIfNeeded,it call fetchPosts
,and then called dispatch(requestPosts(reddit)) but my reducer
can't handle this action ,and I print action in reducer on console ,it show undefined
Your reducers postsBySubreddit and selectedSubreddit receive the wrong parameters list;
they should be: postsBySubreddit(state=initialState,action) and selectedSubreddit(state=initialState,action)

Resources