I'm learning Redux and doing the basic tutorial, I don't know what happen with map even that I checked carefully.
This is the error:
TypeError: Cannot read property 'map' of undefined
This is my code:
const todo = (state, action) => {
switch (action.type) {
case 'ADD_TODO':
return {
id: action.id,
text: action.text,
completed: false
}
case 'TOGGLE_TODO':
if (state.id !== action.id) {
return state;
}
return {
...state,
completed: !state.completed
};
default:
return state;
}
}
let initialState = []
const todos = (state = initialState, action) => {
switch (action.type) {
case 'ADD_TODO':
return [
...state,
todo(undefined, action)
];
case 'TOGGLE_TODO':
return state.map(t => todo(t, action))
default:
return state;
}
}
const visibilityFilter = (state = 'SHOW_ALL', action) => {
switch (action.type) {
case 'SET_VISIBILITY_FILTER':
return action.filter;
default:
return state;
}
}
const todoApp = Redux.combineReducers(
todos,
visibilityFilter
)
let nextTodoId = 0
class App extends React.Component {
render() {
return (
<div>
<form>
<input type="text"/>
<button type="submit"
onClick={e => {
e.preventDefault()
store.dispatch({
type: 'ADD_TODO',
text: 'hello',
id: nextTodoId++,
completed: false
})
}}
>
Add Task
</button>
</form>
<ul>
{this.props.todos.map(todo =>
<li key={todo.id}>
{todo.text}
</li>
)}
</ul>
<div>
Show:
{" "}
All
{" . "}
Active
{" . "}
Completed
</div>
</div>
);
}
}
const store = Redux.createStore(todoApp)
const render = () => {
ReactDOM.render(
<App todos={store.getState().todos}/>,
document.getElementById('app')
);
}
store.subscribe(todoApp)
render()
Please help me.
I believe that the main culprit is this:
store.subscribe(todoApp)
Instead it should be this:
store.subscribe(render)
Also you should pass in an object to combineReducer function, like so:
const todoApp = combineReducers({
todos,
visibilityFilter
})
See this link for a working example.
your store.getState() returns undefined.
there is no
store.getState().todos
therefore, you can't map something that undefined.
your store creation is wrong.
to combine a reducers, do it like this. (please note the "{" )
Redux.combineReducers(
{
todos,
visibilityFilter
}
)
your store.subscribe will also provide another error.
it will call the store with action = undefined.
do it with the following method :
(taken from http://redux.js.org/docs/basics/Store.html)
let unsubscribe = store.subscribe(() =>
console.log(store.getState())
)
Perform a check for the todo property before using map as it may be undefined at the initial state
<ul>
{ this.props.todos && this.props.todos.map(todo =>
<li key={todo.id}>
{todo.text}
</li>
)}
</ul>
JSBIN
Related
I'm trying to create a very simple Project in React to learn Redux, and I'm expecting that, whenever I click a button, the text inside a paragraph will be rendered, however, I can't get the app component (the only one I have) to get updated. Where am i failing?.
import { createStore } from 'redux'
const deleteUsers = 'delete users';
const createUser = 'créate user';
const createUserStranger = 'create stranger';
function deleteUsersActionCreator() {
return {
type: deleteUsers,
data: []
}
}
function createUsersActionCreator() {
return {
type: createUser,
data: ['Irina', 'Denis', 'Julio']
}
}
function createUserStrangerActionCreator() {
return {
type: createUserStranger,
data: 'stranger'
}
}
const initialState = {
users: []
}
const reducer = (state = initialState, action) => {
switch (action.type) {
case createUser:
return {
...state,
users: [...state.users, ...action.data]
}
case deleteUsers:
return {
...state,
users: []
}
case createUserStranger:
return {
...state,
users: [...state.users, action.data]
}
default:
return state
}
};
function dispatchAction(action) {
store.dispatch(action)
}
const store = createStore(reducer);
let users = [];
store.subscribe(() => {
users = store.getState().users
console.log('users', users)
});
const App = () => {
console.log(store.getState().users.map(e => e));
console.log('rendering again')
return (
<div className="App">
<h1>People App </h1>
<hr/>
<p>
{
store.getState().users.map( user => {
return
(<p>{user}</p>)
})
}
</p>
<button onClick={()=> dispatchAction(createUsersActionCreator())}>Add Family</button>
<button onClick={()=> dispatchAction(createUserStrangerActionCreator())}>Add Stranger</button>
<button onClick={()=> dispatchAction(deleteUsersActionCreator())}>Delete users</button>
</div>
)
}
export default App
I know I should be using useState, usteEffect and this approaches, but since I'm learning react from zero, I thought it was a Good idea to learn redux as well. I've also Heard about Zustand (which I'm gonna learn too) but I'd like to have a Good grasp of raw redux. Any advise is very Good welcome :)
Since you are using React - better to use Hooks useSelector and useDispatch inside components
const App = () => {
const users = useSelector(state => state.users)
const dispatch = useDispatch()
console.log('rendering again', users)
return (
<div className="App">
<h1>People App </h1>
<hr/>
<p>
{
users.map( user => {
return
(<p>{user}</p>)
})
}
</p>
<button onClick={()=> dispatch(createUsersActionCreator())}>Add Family</button>
<button onClick={()=> dispatch(createUserStrangerActionCreator())}>Add Stranger</button>
<button onClick={()=> dispatch(deleteUsersActionCreator())}>Delete users</button>
</div>
)
}
export default App
I am building a to do list. One of the features is to be able to add tasks to the do list via user input. I am holding an initial list of todos in state in a reducer pattern. There is an action called ADD_ITEM that should add a new task to the todo list. However, this dispatch function does not appear to be working. When I click on the button that should add a new task to my todo list, it only adds a blank <li> to the list. When I try to use the user input to add a new todo and console.log newItemText which should be set by the input, it gets logged as undefined. Can someone take a look at the code and tell me why this is happening?
TodoList.js:
import React, { useState, useReducer } from"react";
import Todo from "./Todo";
import { initialState, reducer } from "../reducers/todoReducer";
import { ADD_ITEM } from "../actions/actions";
const TodoList = props => {
const [state, dispatch] = useReducer(reducer, initialState);
const [newItemText, setNewItemText] = useState("");
const handleChanges = e => {
console.log(e.target.value)
setNewItemText(e.target.vaue);
};
console.log(newItemText);
return (
<div>
<ul className="task-list">
{state.map(task => (
<Todo key={task.item} task={task} />
))}
</ul>
<input
className="add-input"
name="todo"
type="text"
placeholder="Enter a task"
value={newItemText}
onChange={handleChanges}
/>
<button
className="add-button"
onClick={() => dispatch({ type: ADD_ITEM, payload: newItemText })}
>
Add a Task
</button>
<button
className="add-button"
onClick={null}
>
Remove Completed
</button>
</div>
)
}
export default TodoList;
todoReducer.js:
import { ADD_ITEM, TOGGLE_COMPLETED, REMOVE_COMPLETED } from "../actions/actions";
export const initialState = [
{ item: 'Learn about context API', completed: false, id: 1},
{ item: 'Learn about reducers', completed: false, id: 2},
{ item: 'complete context API project', completed: false, id: 3},
{ item: 'complete reducers project', completed: false, id: 4}
];
export const reducer = (state = initialState, action) => {
console.log(action)
switch(action.type) {
case ADD_ITEM:
return [
...state,
{
item: action.payload,
completed: false,
id: Date.now()
}
]
case TOGGLE_COMPLETED:
const toggledState = [...state];
toggledState.map(item => {
if(item.id === action.payload) {
item.completed = !item.completed;
}
})
state = toggledState;
return state;
case REMOVE_COMPLETED:
return state.filter(item => !item.completed);
default:
return state;
}
}
Inside handleChanges function, you misspelt value:
setNewItemText(e.target.vaue);
;)
I am trying to understand redux with the help of an online todo application resource.
However, I can't seem to figure out where does the 'todos' reducer get the initial state from ?
I've consoled the state but can't seem to wrap my head around it ?
After the initial render, state is consoled 3 times as,
[ ]
[ ]
[ state object ]
Link: 'https://codepen.io/iamrkcheers/pen/rNNoBvB'
Any help is appreciated.
Thank You.
// --------- actions start ----------
const ADD_TODO = "ADD_TODO";
const TOGGLE_TODO = "TOGGLE_TODO";
const SET_VISIBILITY_FILTER = "SET_VISIBILITY_FILTER";
const VisibilityFilters = {
SHOW_ALL: "SHOW_ALL",
SHOW_COMPLETED: "SHOW_COMPLETED",
SHOW_ACTIVE: "SHOW_ACTIVE"
};
let nextTodoId = 3;
function addTodo(text) {
return {
type: ADD_TODO,
id: nextTodoId++,
text
}
}
function toggleTodo(id) {
return {
type: TOGGLE_TODO,
id
}
}
function setVisibilityFilter(filter) {
return {
type: SET_VISIBILITY_FILTER,
filter
}
}
// --------- actions end ----------
// --------- reducers start ----------
function todos(state = [], action) {
console.log('state is:',state);
switch (action.type) {
case ADD_TODO: {
return [...state, {
text: action.text,
completed: false,
id: action.id
}];
}
case TOGGLE_TODO: {
return state.map((todo, id) => {
if (id === action.id) {
return Object.assign({}, todo, {
completed: !todo.completed
});
}
return todo;
});
}
default: {
return state;
}
}
}
function visibilityFilter(state = VisibilityFilters.SHOW_ALL, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER: {
return action.filter;
}
default: {
return state
}
}
}
const todoApp = Redux.combineReducers({
visibilityFilter,
todos
});
// --------- reducers end ----------
// --------- components start ----------
const App = () => {
const getDate = date => new Date(date);
const days = ["Воскресенье", "Понедельник", "Вторник", "Среда", "Четверг", "Пятница", "Суббота"];
return (
<div className="block">
<div className="info-date">
<div className="date">{ getDate(Date.now()).toLocaleDateString("ru") }</div>
<div className="day">{ days[getDate(Date.now()).getDay()] }</div>
</div>
<AddTodo />
<Footer />
<VisibleTodoList />
</div>
);
};
const Footer = () => {
return (
<div className="filters">
<FilterLink filter="SHOW_ALL">Все задачи</FilterLink>
<FilterLink filter="SHOW_ACTIVE">Активные</FilterLink>
<FilterLink filter="SHOW_COMPLETED">Завершенные</FilterLink>
</div>
);
};
const Link = ({ active, children, onClick }) => {
if (active) {
return <span className="filter-item non-active">{ children }</span>
}
return (
<a className="filter-item" href="#" onClick = { event => {
event.preventDefault();
onClick();
} }>{ children }</a>
);
};
const Todo = ({ onClick, completed, text }) => {
const styles = {
textDecoration: completed ? "line-through" : "none"
};
return (
<li onClick = { onClick } style = { styles }>
<a>{ text }</a>
</li>
);
};
const TodoList = ({ todos, onTodoClick }) => {
return (
<div className="list">
<ul>
{
todos.map(todo => <Todo
key = { todo.id } { ...todo }
onClick = { () => onTodoClick(todo.id) } />)
}
</ul>
</div>
);
};
// --------- components end ----------
// --------- containers start ----------
let AddTodo = ({ dispatch }) => {
let input;
return (
<div>
<form className="addForm" onSubmit = { event => {
event.preventDefault();
if (!input.value.trim()) {
return;
}
dispatch(addTodo(input.value));
input.value = "";
} }>
<input type="text" placeholder="Что нужно сделать?" ref = { node => input = node }/>
<button type="submit" className="btn"></button>
</form>
</div>
);
};
AddTodo = ReactRedux.connect()(AddTodo);
var mapStateToProps = (state, ownProps) => {
return {
active: ownProps.filter === state.visibilityFilter
};
};
var mapDispatchToProps = (dispatch, ownProps) => {
return {
onClick: () => {
dispatch(setVisibilityFilter(ownProps.filter));
}
};
};
const FilterLink = ReactRedux.connect(
mapStateToProps,
mapDispatchToProps
)(Link);
const getVisibleTodos = (todos, filter) => {
switch (filter) {
case "SHOW_ALL": {
return todos;
}
case "SHOW_COMPLETED": {
return todos.filter(todo => todo.completed);
}
case "SHOW_ACTIVE": {
return todos.filter(todo => !todo.completed);
}
default: {
return todos;
}
}
};
var mapStateToProps = state => {
return {
todos: getVisibleTodos(state.todos, state.visibilityFilter)
}
};
var mapDispatchToProps = dispatch => {
return {
onTodoClick: id => {
dispatch(toggleTodo(id));
}
};
};
const VisibleTodoList = ReactRedux.connect(
mapStateToProps,
mapDispatchToProps
)(TodoList);
// --------- containers end ----------
// --------- application start ----------
const initialState = {
visibilityFilter: "SHOW_ALL",
todos: [
{
id: 0,
text: "Изучить React",
completed: true
},
{
id: 1,
text: "Изучить Redux",
completed: true
},
{
id: 2,
text: "Написать приложение \"Список задач\"",
completed: false
}
]
};
let store = Redux.createStore(todoApp, initialState);
ReactDOM.render(
<ReactRedux.Provider store = { store }>
<App />
</ReactRedux.Provider>,
document.querySelector("#root")
);
// --------- application end ----------
You are defining the initial state right here :
function todos(state = [], action) {
Generally, while defining reducers, we also define initialState(state = [] in your case) , which is the state that goes into the reducer till we populate it with data (from an external source like api, or user input).
You can read more on initial state here : https://redux.js.org/recipes/structuring-reducers/initializing-state#initializing-state
there are two ways where you can define initial state;
the first one is in your reducer where you did function
todos(state = [], action) and ,
the second is when you create the store, you can pass initial state as a second argument in your createStore function. In your case , you have a second argument when you create your store which is an array of three todos which you can see when you console log it. store = Redux.createStore(todoApp, initialState), here the reducer gets this initial state
I have an error after clicking the delete button saying:
Cannot read property 'map' of undefined.
I'm new in React Redux JS.
Please see my code below of my component reducers and actions:
Post.js
class Post extends Component {
constructor(){
super();
this.deletePost = this.deletePost.bind(this);
}
deletePost(postId){
this.props.deletePost(postId);
}
render(){
const postItems = this.props.posts.map(post => (
<div key={post.id} className="row">
<div className="container">
<h3>{post.title}</h3>
<p>{post.body}</p>
<button
onClick={() =>this.deletePost(post.id)}
className="btn btn-danger">
Delete
</button>
</div>
</div>
))
const divStyle = {
padding: '15px',
}
return (
<div style={divStyle}>
<PostForm />
<hr/>
{postItems}
</div>
)
}
}
const mapStateToProps = state => ({
posts: state.posts.items,
newPost: state.posts.item
})
export default connect(mapStateToProps, { fetchPosts, deletePost })(Post);
PostAction.js (Here is my delete action. I am using jsonplaceholder API post.)
export const deletePost = (postId) => dispatch => {
fetch('https://jsonplaceholder.typicode.com/posts/'+postId, {
method: 'DELETE',
})
.then(dispatch({
type: DELETE_POST,
payload: postId
}));
}
PostReducer.js (This is my reducer.)
case DELETE_POST:{
const newState = Object.assign([], state);`enter code here`
const filteredItems = newState.items.filter(items => {
return items.id != action.payload;
});
return filteredItems;
}
case DELETE_POST:{
const { items } = state;
const filteredItems = items.filter(items => {
return items.id != action.payload;
});
return {
...state,
items: [ ...filteredItems ]
};
}
Yes just replace
return filteredItems; to return { items: filteredItems }
But please can you check my code if it's correct. Thanks
I'm working with a classic to do list project to learn Redux and I'm having a strange issue.
Basically, I have a to-do list with checkboxes and when the user clicks on a checkbox, an action gets dispatched which should mark that object's completed property as true and the component should update.
However... When this action fires, the object which is supposed to be marked as complete successfully returns with all of it's properties but the rest of the todo list (the other objects in the array) get corrupted, losing all their properties and they turn into 'undefined' thus causing problems in the render.
I've tried to include all the code that I think is relevant but I think that I'm doing something wrong in my reducer, but I can't find seem to find the issue.
Todo List Component
class TodoList extends Component {
render(){
const {todos, showCompleted, searchTerm} = this.props;
const renderTodos = () => {
if (todos.length === 0) {
return (
<p className="container__message">Nothing to do.</p>
);
}
return TodoAPI.filterTodos(todos, showCompleted, searchTerm).map((todo) => {
return (
<Todo key={todo.id} {...todo}/>
);
});
};
return (
<div>
{renderTodos()}
</div>
);
}
}
export default connect((state) => {
return state;
})(TodoList);
Todo Component
class Todo extends Component {
render() {
const {id, text, completed, createdAt, completedAt, dispatch} = this.props;
const todoClass = completed
? 'todo todo-completed'
: 'todo';
const renderDate = () => {
let displayMessage = 'Created ';
let timestamp = createdAt;
if (completed) {
displayMessage = 'Completed ';
timestamp = completedAt;
}
return displayMessage + moment.unix(timestamp).format('MMM Do YYYY # h:mm a');
};
return (
<div className={todoClass}
onClick={event => dispatch(actions.toggleTodo(id)) }>
<input type="checkbox" checked={completed} readOnly/>
<div>
<p>{text}</p>
<p className="todo__subtext">{renderDate()}</p>
</div>
</div>
);
}
}
export default connect()(Todo);
Action
export const toggleTodo = (id) => {
return {
type: 'TOGGLE_TODO',
id: id
};
};
Reducer
export const todosReducer = (state = [], action) => {
switch (action.type) {
case 'TOGGLE_TODO':
return state.map((todo) => {
if (todo.id === action.id) {
let nextCompleted = !todo.completed;
return {
...todo,
completed: nextCompleted,
completedAt: todo.completed ? moment().unix() : 0
};
}
});
default:
return state;
}
};
Issue is you are not returning anything if the condition todo.id === action.id fail. With the map if you don't return anything, by default it will return undefined, Try this:
return state.map((todo) => {
if (todo.id === action.id) {
let nextCompleted = !todo.completed;
return {
...todo,
completed: nextCompleted,
completedAt: todo.completed ? moment().unix() : 0
};
}else{
return todo;
}
});
Check this:
a=[1,2,3,4,5,6];
b = a.map ( i => { if(i % 2 == 0) return i;})
console.log(b);