Adding / removing objects in an array based on button click with Redux - reactjs

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));

Related

How to Revert to the Original Background Color of previously Clicked Components When Clicking Another in React

What I am trying to achieve is, as mentioned in the title, to revert the component's background color when another entry component in the sidebar gets clicked. I use React Context API for state management. The initial state contains an array of objects named numbers which has two elements (id & number) and current. On Sidebar.js, it renders the SidebarEntry component iterating the numbers array.
I know why I am stuck at this point. It is because there is no way that I can change the state of the previously clicked component unless it gets clicked again. The following code snippet is what I reproduced my issue.
reducer.js
export const initialState = {
numbers: [
{
id: 1,
number: 101
},
{
id: 2,
number: 102
},
{
id: 3,
number: 103
},
],
current: null
}
const reducer = (state, action) => {
switch(action.type) {
case 'CHANGE_STATE':
return {
...state,
current: action.current
};
default:
return state;
}
}
export default reducer;
StateProvider.js (This wraps in index.js with initialState and reducer arguments)
import React, { createContext, useContext, useReducer } from "react";
export const StateContext = createContext();
export const StateProvider = ({ reducer, initialState, children }) => (
<StateContext.Provider value={useReducer(reducer, initialState)}>
{children}
</StateContext.Provider>
);
export const useStateValue = () => useContext(StateContext);
Sidebar.js
const Sidebar = () => {
const [{ numbers }] = useStateValue();
return (
<div className="sidebar">
{
numbers.map(number => (
<SidebarEntry key={number.id} number = {number.number} />
))
}
</div>
)
}
export default Sidebar
SidebarEntry.js
const SidebarEntry = ({ number }) => {
const [{ current }, dispatch] = useStateValue();
const [selected, setSelected] = useState(false);
const changeState = (e) => {
e.preventDefault();
dispatch({
type: "CHANGE_STATE",
current: {number}
});
setSelected(!selected);
}
return (
<div className="sidebarEntry">
<h3
className={selected && "sidebarEntry__color"}
onClick={changeState}
>
{number}
</h3>
</div>
)
}
export default SidebarEntry
Sidebar & SidebarEntry
When clicking 101, then clicking 102 (101's background color is supposed to be back to gray)
The problem is that when you dispatch a new active value in redux, you are actually changing the redux state.
But you are not changing the current state of the component selected.
I would delete this state altogether and do it like this:
const SidebarEntry = ({ number }) => {
const [{ current }, dispatch] = useStateValue();
const changeState = (e) => {
e.preventDefault();
dispatch({
type: "CHANGE_STATE",
current: {number}
});
setSelected(!selected);
}
return (
<div className="sidebarEntry">
<h3
className={current === number && "sidebarEntry__color"}
onClick={changeState}
>
{number}
</h3>
</div>
)
}
export default SidebarEntry
This should work for you

React dispatch from reducer firing twice

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.

updating state through dispatch function

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);
;)

Failed prop type: The prop todos[0].id is marked as required in TodoList, but its value is undefined

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
}
}

Building a Filter function from OnClick Table row with react/redux

So here is what I'm trying to do : I have a list of three name and when I'm clicking on one of them, the remaining two disappear. My final objective is to build a table so I can filter my data by clicking on the row. I don't really now where to start. I'm guessing that my OnClick Function should trigger some filter method on one of my redux store state. But I don't know how should I articulate this with my action and reducer. so I have at least two questions for now :
What should be my filter function ?
How does this filter function works with my action/dispatch and reducer ?
I'm not asking for code or completed solution, I'm more looking for overall logic. Here is some parts of my code if needed for illustration :
My component :
class NameList extends React.Component {
constructor(props) {
super(props);
}
render() {
var props = this.props.name;
return (
<div>
<ul>
{ props.map((m, i) =>
<li key={i} onClick={???}>{m.name}</li>
)}
</ul>
</div>
)
};
}
const mapStateToProps = (state) => {
return {
name: state.nameData.allname,
};
};
My reducer :
var items = [
{ name: 'Louise', age: 27, color: 'red' },
{ name: 'Margaret', age: 15, color: 'blue'},
{ name: 'Lisa', age:34, color: 'yellow'}
];
const nameData = (state = {
allname: items
}, action) => {
switch (action.type) {
case 'SET_NAME':
return {
...state,
name: action.name
};
default:
return state;
}
};
My action :
export function AuthorFilter() {
return {
type: 'SET_NAME',
name,
};
}
thanks.
One way to do it would be to use mapDispatchToProps. Then from within your component you would be able to dispatch suitable action.
If you want to get detailed explanation on how all these things are connected (store, action creators etc) you can check this article: https://github.com/reactjs/redux/blob/master/docs/basics/UsageWithReact.md

Resources