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);
;)
Related
Want to open and close the navigation drawer by clicking on the navbar icon this is inside header component and storing the value in slice using redux toolkit that is in util.js
<img
src={NavIcon}
alt=""
className="icon nav-icon colapse-icon"
onClick={() => {
setDrawer(!drawer);
!drawer
? dispatch(drawerOpneClose(true))
: dispatch(drawerOpneClose(false));
}}
/>
Util.js slice code
import { createSlice } from "#reduxjs/toolkit";
const UtilSlice = createSlice({
name: "util",
initialState: { openDrawer: true },
reducers: {
drawerOpneClose: (state, action) => {
return {
...state,
openDrawer: action.payload,
};
},
},
});
export const UtilReducer = UtilSlice.reducer;
export const { closeNav, openNav, drawerOpneClose } = UtilSlice.actions;
Leistinig for change in data in SideBar.js
function SideBar() {
const [drawer] = useState(useSelector((state) => state.util) || false);
console.log(drawer);
return (
<div className={`${true ? "open-nav" : "close-nav"}`}>
)
}
State value is not getting updated as initially it is true it stays true if i pass false it is not getting updated
useState won't update the state value when the reducer updates.
Just use the selector value
const {openDrawer} = useSelector((state) => state.util);
return (
<div className={`${openDrawer ? "open-nav" : "close-nav"}`}>
)
I have a simple "to-do-list" that I am trying to create that will display a to-do (an object with 4 properties) in a list (an array of objects). I am initializing the list with two to-dos already, and I want the ability to remove a to-do from the list with a button click (the button is held inside the "to-do" component).
Here is the component that creates a single to-do and includes the button for removing it from the list.
import React from "react";
import { useDispatch } from "react-redux";
import "./app.css";
import { remove } from "./redux/ducks/listDuck";
const Listitem = (props) => {
const dispatch = useDispatch();
const handleRemove = (id) => {
dispatch(remove(id));
};
return (
<div className="listitem">
<div className="listItemHeader">{`${props.title}`}</div>
<div className="listItemParameters">
<p>{`Assigned to: ${props.assignedTo}`}</p>
<p>{`Due date: ${props.dueDate}`}</p>
</div>
<button
id={props.id}
type="submit"
className="removeButton"
onClick={handleRemove}
>
Remove
</button>
</div>
);
};
export default Listitem;
I am using the "ducks" method for Redux and below is my state, actions, and reducer:
// State
const initialState = {
toDoListArray: [
{
id: 1,
title: "get groceries",
assignedTo: "bob",
dueDate: "05/14/2021",
},
{
id: 2,
title: "haircut",
assignedTo: "tony",
dueDate: "05/20/2021",
},
],
};
// Add To-Do Action
const ADDtoLIST = "add-to-list";
export const add = (text) => {
return {
type: ADDtoLIST,
text,
};
};
// Remove To-Do Action
const REMOVEfromLIST = "remove-from-list";
export const remove = (id) => {
return {
type: REMOVEfromLIST,
id,
};
};
// Reducer
const items = (state = initialState, action) => {
switch (action.type) {
case ADDtoLIST:
console.log("add");
console.log(action);
return {
...state,
toDoListAray: state.toDoListArray.push({
id: 5,
title: "new to do",
assignedTo: "john",
dueDate: "today",
}),
};
case REMOVEfromLIST:
console.log("remove");
console.log(action);
return {
...state,
toDoListArray: state.toDoListArray.filter((x) => x.id !== action.id),
};
default:
return state;
}
};
export default items;
I believe I have set up Redux appropriately because I am able to delete a to-do from the list by specifying an id in the REMOVEfromLIST case return; however, my problem is that I am unable to figure out how to delete the specific to-do that the user clicked on. In the code above I am using action.id because that was a solution I saw elsewhere, but it doesn't actually delete anything.
I am sure that I am missing something obvious, but I am a beginner and can't seem to figure it out. Any help / suggestions would be welcomed!
I have also put all the code in this CodeSandbox if helpful: https://codesandbox.io/s/to-do-list-o1s0h?file=/src/redux/ducks/listDuck.js
You have to pass the id into the component like:
<Listitem
key={x.id}
id={x.id}
Which enables you to do:
dispatch(remove(props.id));
Whenever I call a function from the reducer, it gets called once the first time, and then twice every other time.
Here's the code:
reducer.js:
import data from './data'
export const initialState = {
notes: data,
filter: '',
};
export const setFilter = filter => ({ type: 'setFilter', filter });
export const createNote = id => ({ type: 'createNote', id })
export const deleteNote = note => ({ type: 'deleteNote', note })
export const reducer = (state, action) => {
switch (action.type) {
case 'setFilter':
return { ...state, filter: action.filter };
case 'createNote':
console.count('Create note fired')
state.notes.push({
id: action.id,
tags: [],
content: ""
})
return { ...state }
case 'deleteNote':
return {
...state,
notes: state.notes.filter((note) => note.id !== action.note.id)
}
default: return state;
}
};
The component that calls the delete method:
import React from 'react'
import PropTypes from 'prop-types';
import { deleteNote } from "../../state/reducer";
import { useStateValue } from "../../state/StateContext";
import './Body.css'
import { Card, Badge } from 'react-bootstrap'
const Body = ({ notes }) => {
let [state, dispatch] = useStateValue();
return (
<div className="Body">
{
notes.map(note =>
<Card key={note.id} className="Card">
<Card.Body className="CardText HideScrollbar">
<Card.Text>{note.content}</Card.Text>
</Card.Body>
<Card.Footer>
{note.tags.map(tag =>
<Badge variant="primary">
{tag} </Badge>)}
</Card.Footer>
<div className="DeleteButton" onClick={() => dispatch(deleteNote(note))}>
<svg className="svg-icon" viewBox="0 0 20 20">
<path d="M10.185,1.417c-4.741,0-8.583,3.842-8.583,8.583c0,4.74,3.842,8.582,8.583,8.582S18.768,14.74,18.768,10C18.768,5.259,14.926,1.417,10.185,1.417 M10.185,17.68c-4.235,0-7.679-3.445-7.679-7.68c0-4.235,3.444-7.679,7.679-7.679S17.864,5.765,17.864,10C17.864,14.234,14.42,17.68,10.185,17.68 M10.824,10l2.842-2.844c0.178-0.176,0.178-0.46,0-0.637c-0.177-0.178-0.461-0.178-0.637,0l-2.844,2.841L7.341,6.52c-0.176-0.178-0.46-0.178-0.637,0c-0.178,0.176-0.178,0.461,0,0.637L9.546,10l-2.841,2.844c-0.178,0.176-0.178,0.461,0,0.637c0.178,0.178,0.459,0.178,0.637,0l2.844-2.841l2.844,2.841c0.178,0.178,0.459,0.178,0.637,0c0.178-0.176,0.178-0.461,0-0.637L10.824,10z"></path>
</svg>
</div>
</Card>
)
}
</div>
)
}
Body.propTypes = {
notes: PropTypes.arrayOf(PropTypes.object),
}
export default Body
Any kind of help would be really helpful, please tell me if there's any file missing or if I implemented the reducer in the wrong way, what I did was mostly following notes from a friend's University professor
make seperate action file. And get that action from redux through mapDispatchToProps in your component , where you want to dispatch that action.
const mapDispatchToProps = {
setProfileDialog: ProfileAction.setProfileDialog,
}
The issue is that reducers must be pure. When react is in 'strict-mode' it will fire reducers twice to ensure that the result is the same both times. Mutating the original state will cause unwanted side effects.
Changing:
case 'createNote':
console.count('Create note fired')
state.notes.push({
id: action.id,
tags: [],
content: ""
})
return { ...state }
To:
case 'createNote':
const notes = [
...state.notes,
{
id: action.id,
tags: [],
content: "",
}
]
return {...state, notes}
Should fix your example.
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
I'm trying to do the redux basic usage tutorial which uses react for the UI.
I'm getting this warning (in red though - perhaps it is an error?) in the console logs when I click a button labelled "Add Todo":
warning.js:36 Warning: Failed prop type: The prop todos[0].id is
marked as required in TodoList, but its value is undefined.
in TodoList (created by Connect(TodoList))
in Connect(TodoList) (at App.js:9)
in div (at App.js:7)
in App (at index.js:12)
in Provider (at index.js:11)
So the todo that is getting added, has no id field - I need to figure out how to add an id.
in actions.js
/*
* action creators
*/
export function addTodo(text) {
return { type: ADD_TODO, text }
}
actions/index.js
let nextTodoId = 0
export const addTodo = (text) => {
return {
type: 'ADD_TODO',
id: nextTodoId++,
text
}
}
export const setVisibilityFilter = (filter) => {
return {
type: 'SET_VISIBILITY_FILTER',
filter
}
}
export const toggleTodo = (id) => {
return {
type: 'TOGGLE_TODO',
id
}
}
containers/AddTodo.js
import React from 'react'
import { connect } from 'react-redux'
import { addTodo } from '../actions'
let AddTodo = ({ dispatch }) => {
let input
return (
<div>
<form onSubmit={e => {
e.preventDefault()
if (!input.value.trim()) {
return
}
dispatch(addTodo(input.value))
input.value = ''
}}>
<input ref={node => {
input = node
}} />
<button type="submit">
Add Todo
</button>
</form>
</div>
)
}
AddTodo = connect()(AddTodo)
export default AddTodo
It looks like actions/index.js does add the todo id?
It's hard to debug because for some reason the chrome dev tools sources are missing the actions and reducers folders of my app:
How do I get the todos[0] to have a unique id?
note that when I add id: 1 here it does get the id added but it is not unique:
function todos(state = [], action) {
switch (action.type) {
case ADD_TODO:
return [
...state,
{
text: action.text,
completed: false,
id: 1
}
]
case TOGGLE_TODO:
return state.map((todo, index) => {
if (index === action.index) {
return Object.assign({}, todo, {
completed: !todo.completed
})
}
return todo
})
default:
return state
}
}
Maybe:
/*
* action creators
*/
export function addTodo(text) {
return { type: ADD_TODO, text }
}
needs id added?
I briefly looked over your code, and I believe you're not getting that id because you're not importing the action creator function you think you are.
in containers/AddTodo.js:
import { addTodo } from '../actions'
In your project, you have
./src/actions.js
./src/actions/index.js
When you import or require anything (without using file extensions like the above '../actions'), the JS interpreter will look to see if there's a file called actions.js in the src folder. If there is none, it will then see if there's an actions folder with an index.js file within it.
Since you have both, your AddTodo component is importing using the action creator in ./src/actions.js, which does not have an id property as you had originally guessed.
Remove that file, and it should work as you intended.
You have to add an 'id' variable to the actions file then increase the value every time you call the action creator.
action creator:
let nextTodoId = 0;
export const addTodo = text => ({
type: 'ADD_TODO',
id: nextTodoId++,
text
});
reducer:
const todos = (state = [], action) => {
switch (action.type) {
case 'ADD_TODO':
return [
...state,
{
id: action.id, // unique id
text: action.text,
completed: false
}
]
case 'TOGGLE_TODO':
return state.map(todo =>
todo.id === action.id ? { ...todo, completed: !todo.completed } : todo
)
default:
return state
}
}