I have a simple todo app in which i add my "todos" and if they are done i just simply click done. Although after clicking the state is updated and the payload is being printed to the console with proper actions "TODO_DONE", done field still remains false.
my case for "TODO_DONE" in Reducer:
case "TODO_DONE":
return state.map((todo) => {
if (todo.id === action.payload) {
return {
...todo,
done: true,
};
}
return todo;
});
i use it here:
<button onClick={() => doneTodo(todo)}>Done</button>
in the TodoList component:
import { deleteTodoAction, doneTodo } from "../actions/TodoActions";
import { connect } from "react-redux";
const TodoList = ({ todoss, deleteTodoAction, doneTodo }, props) => {
return (
<div>
<h3>Director list</h3>
{todoss.map((todo) => {
return (
<div>
<div> {todo.name} </div>
<button onClick={() => deleteTodoAction(todo)}>Usuń</button>
<button onClick={() => doneTodo(todo)}>Done</button>
</div>
);
})}
</div>
);
};
const mapStateToProps = (state) => {
return {
todoss: state.todoss,
};
};
const mapDispatchToProps = {
deleteTodoAction,
doneTodo,
};
export default connect(mapStateToProps, mapDispatchToProps)(TodoList);
Ofc, the "done" value is my initial value inside TodoForm with Formik:
<Formik
initialValues={{
id: uuidv4(),
name: "",
date: "",
done: false,
}}
onSubmit={(values) => handleSubmit(values)}
enableReinitialize={true}
>
Anyone knows why this doest not work?
Check your doneTodo action. Since you are passing todo object to it. It should be action.payload.id instead of action.payload.
todo.id === action.payload.id
Related
I'm using React Redux and want to be able to change the title and description of a post, using the onChange method. When only using React the way you would do this is that you keep an useState which you change whenever a change occurs, but I can't seem to get it to work with using redux in react. Instead of the state changing the original title, and description remains and cannot be changed.
From what I have read the basic idea is to have a listener on the input (onChange, usually) and have that fire a redux action. You then have the action tell the reducer to make the change to the store.
I have tried doing this, but could make it work correctly. What am I doing wrong and how do you solve it? I'm also wondering how do I specify that I want to change either title or description when using onChange, or do I simply send everything in post each time a change occurs?
This is what the redux state looks like when entering a post:
{
auth: {
isSignedIn: true,
user: {
id: '624481f22566374c138cf974',
username: 'obiwan',}
},
posts: {
'62448632b87b223847eaafde': {
_id: '62448632b87b223847eaafde',
title: 'hellothere',
desc: 'its been a long time since I heard that name...',
username: 'vorbrodt',
email: 'example#gmail.com',
categories: [],
createdAt: '2022-03-30T16:32:50.158Z',
updatedAt: '2022-03-30T16:32:50.158Z',
__v: 0
}
},
}
Here is where the onChange happens.
Post.js
import { getPostById, editPost } from "../actions";
const Post = ({ getPostById, editPost, username }) => {
const [updateMode, setUpdateMode] = useState(false);
let { id } = useParams();
let post = useSelector((state) => state.posts[id]);
const handleInputChange = (e) => {
try {
editPost(e.target.value);
} catch (err) {}
};
return (
<div className="post">
<div className="post-wrapper">
{updateMode ? (
<input
type="text"
value={post.title}
className="post-title-input"
autoFocus
onChange={(e) => handleInputChange(e)}
/>
) : (
<h1 className="post-title">
{post.title}
</h1>
)}
<div className="desc-area">
{updateMode ? (
<textarea
className="post-desc-input"
value={post.desc}
onChange={(e) => handleInputChange(e)}
/>
) : (
<p className="post-desc">{post.desc}</p>
)}
</div>
</div>
</div>
);
};
const mapStateToProps = (state) => {
return { username: state.auth.user.username };
};
export default connect(mapStateToProps, { getPostById, editPost })(Post);
Here is the action creator:
//edit post in redux state
const editPost = (postValues) => (dispatch) => {
dispatch({ type: EDIT_POST, payload: postValues });
};
And here is the reducer which is suppose to change the state.
postReducer.js
import _ from "lodash";
import { GET_POSTS, GET_POST, CREATE_POST, EDIT_POST } from "../actions/types";
function postReducer(state = {}, action) {
switch (action.type) {
case GET_POSTS:
return { ...state, ..._.mapKeys(action.payload, "_id") };
case GET_POST:
return { ...state, [action.payload._id]: action.payload };
case CREATE_POST:
return { ...state, [action.payload._id]: action.payload };
case EDIT_POST:
//here the change should occur, not sure how to specify if title or desc should
//change
return { ...state, [action.payload._id]: action.payload };
default:
return state;
}
}
export default postReducer;
Hey there something like this should be of help
const handleInputChange = (e, key, id) => {
try {
editPost({ [key]: e.target.value, id });
} catch (err) {}
};
Usage
<textarea
className="post-desc-input"
value={post.desc}
onChange={(e) => handleInputChange(e, "title", post.id)}
/>
action
const editPost = (postValues) => (dispatch) => {
dispatch({ type: EDIT_POST, payload: postValues });
};
Reducer
case EDIT_POST:
//here we destructure the id and return the data without the id cause we //need it below
const {id, ...newData} = action.payload
const indexToUpdate = state.posts.find(post => post.id === id)
const newPostsData = [...state.posts]
//Here we update the actual object and its property that is in the state at //the specific value
newPostsData[indexToUpdate] = {...newPostData[indexToUpdate], {...newData}
return { ...state, posts: newPostsData};
EDIT:
I fixed the problem in the reducer...changed this:
case ADD_LIST_ITEM:
return {
...state,
lists: {
...state.lists.map(list =>
list._id === payload.id
? { ...list, listItems: payload.data }
: list
)
},
loading: false
};
to this:
case ADD_LIST_ITEM:
return {
...state,
lists: [
...state.lists.map(list =>
list._id === payload.id
? { ...list, listItems: payload.data }
: list
)
],
loading: false
};
Stupid error on my part.
I have a MERN todo application using redux for state management and useEffect() for UI updates (all functional instead of class-based components). However, when I change state in the redux store, the UI does not update. This seems to only happen during an update triggered by a post request from the front end to the backend, where I pass data to an action, which is handled in a reducer (a js file rather than the useReducer() hook in this app). My backend will update properly, but the UI will crash.
What happens is, I input, say, a new list item in a given todo list, and the error I get is:
Uncaught TypeError: list.lists.map is not a function
at Dashboard (Dashboard.jsx:32)
I'm not sure where to use an additional useEffect(), if needed, or if there's a problem in my reducer...here's the relevant flow (removed all className declarations and irrelevant parts):
/* Dashboard.jsx */
// imports //
const Dashboard = ({ auth: { user }, list, getLists }) => {
useEffect(() => {
getLists();
}, [getLists]);
return (
<>
<p>Lists...</p>
{list.lists &&
list.lists.map(list => <List key={list._id} list={list} />)}
</>
);
};
Dashboard.propTypes = {
getLists: PropTypes.func.isRequired,
list: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
list: state.list
});
export default connect(mapStateToProps, { getLists })(Dashboard);
/* List.jsx */
// imports
const List = ({ list, addListItem, getLists }) => {
const [text, setText] = useState('');
useEffect(() => {
getLists();
}, []);
const handleAddItem = e => {
e.preventDefault();
addListItem(list._id, { text });
setText('');
};
return (
<div>
{list.listItems &&
list.listItems.map((item, index) => (
<ListItem
key={index}
item={item}
listId={list._id}
itemIndex={index}
/>
))}
<div>
<form onSubmit={handleAddItem}>
<input
type="text"
name="text"
placeholder="add a to-do item"
value={text}
onChange={e => setText(e.target.value)}
/>
<input type="submit" value="add" />
</form>
</div>
</div>
);
};
List.propTypes = {
addListItem: PropTypes.func.isRequired,
getLists: PropTypes.func.isRequired
};
export default connect(null, {
addListItem,
getLists
})(List);
/* list.actions.js */
// imports
export const addListItem = (listId, text) => async dispatch => {
try {
const res = await api.post(`/lists/${listId}`, text); // returns all list items after adding new item
dispatch({
type: ADD_LIST_ITEM,
payload: { id: listId, data: res.data }
});
} catch (err) {
dispatch({
type: LIST_ERROR,
payload: { message: err.response.statusText, status: err.response.status }
});
}
};
/* list.reducer.js */
// imports
const initialState = {
lists: [],
list: null,
loading: true,
error: {}
};
const list = (state = initialState, action) => {
const { type, payload } = action;
switch (type) {
case GET_LISTS:
return { ...state, lists: payload, loading: false };
case LIST_ERROR:
return { ...state, error: payload, loading: false };
case ADD_LIST_ITEM:
return {
...state,
lists: {
...state.lists.map(list =>
list._id === payload.id
? { ...list, listItems: payload.data }
: list
)
},
loading: false
};
default:
return state;
}
};
export default list;
I assume when creating your app's store, you are passing list as rootReducer,
Meaning your app's main state is exactly the state list is managing.
So if you need to access property lists of the state, you need to do it like this:
const mapStateToProps = state => ({
lists: state.lists /// state in here is exactly the state of list reducer
});
Now, in Dashboard lists is that array that you manipulate in list reducer.
Also, you have defined a property also named list in list reducer. It is initially defined to be null, also in the reducer, you never change it:
const initialState = {
lists: [],
list: null, /// none of actions ever change this, meaning it's currently useless.
loading: true,
error: {}
};
I am following a tutorial trying to learn Redux. I got the first action working, which is a simple GET API call, but am stuck on the next action I'm trying to create. The code looks like the following:
In the Component:
class ShoppingList extends Component {
componentDidMount() {
this.props.getItems();
}
handleClick = id => {
console.log("component " + id);
this.props.deleteItem(id);
};
render() {
const { items } = this.props.item;
return (
<Container>
<ListGroup>
<TransitionGroup className="shoppingList">
{items.map(({ id, name }) => (
<CSSTransition key={id} timeout={500} classNames="fade">
<ListGroupItem>
<Button
className="button1"
color="danger"
size="sm"
onClick={e => this.handleClick(id, e)}
>
×
</Button>
{name}
</ListGroupItem>
</CSSTransition>
))}
</TransitionGroup>
</ListGroup>
</Container>
);
}
}
ShoppingList.propTypes = {
getItems: PropTypes.func.isRequired,
item: PropTypes.object.isRequired,
deleteItem: PropTypes.func.isRequired
};
const mapStateToProps = state => ({
item: state.item
});
export default connect(mapStateToProps, { getItems, deleteItem })(ShoppingList);
In my reducer:
const initialState = {
items: [
{ id: 3, name: "Eggs" },
{ id: 4, name: "Milk" },
{ id: 5, name: "Steak" },
{ id: 6, name: "Water" }
]
};
export default function(state = initialState, action) {
switch (action.type) {
case GET_ITEMS:
return {
...state
};
case DELETE_ITEM:
console.log("reducer");
return {
...state,
items: state.items.filter(item => item.id !== action.id)
};
default:
return state;
}
}
In my actions file:
export const getItems = () => {
return {
type: GET_ITEMS
};
};
export const deleteItem = id => {
console.log("actions");
return {
type: DELETE_ITEM,
payload: id
};
};
However, when I click on the button to try to delete an item from the list, nothing happens. I can see in the Redux console that the action is being dispatched, however it seems to have no effect. Any suggestions?
You have in deleteItem action { type, payload }. Instead you can have { type, id } or using payload in the reducer return statement.
I would do the following - so you are passing the id with the action instead of payload:
export const deleteItem = id => {
console.log("actions");
return {
type: DELETE_ITEM,
id
};
};
Or the best option for later purposes - keep payload just adding id as property:
// action
export const deleteItem = id => {
console.log("actions");
return {
type: DELETE_ITEM,
payload: { id }
};
};
// reducer
case DELETE_ITEM:
// here destructuring the property from payload
const { id } = action.payload;
return {
...state,
items: state.items.filter(item => item.id !== id)
};
I hope this helps!
I have error in my reducer:
props actual- this is the time the airplane departure or arrival, this property is located in my API
API has this structure:
{"body":{
"departure":[{actual: value},{term: value}],
"arrival":[{....},{......}]}
}
code:
airplanes.js(reducer)
import { searchFilter } from "../components/app";
export function reducer(state = {}, action) {
switch (action.type) {
case "SET_SHIFT":
return Object.assign({}, state, {
shift: action.shift
});
case "SET_SEARCH":
return Object.assign({}, state, {
search: action.search.toLowerCase()
});
case "RUN_FILTER":
var newData = state.data[action.shift].filter(x => {
return (
x.actual &&
x.actual.includes(
state.day
.split("-")
.reverse()
.join("-")
)
);
});
return Object.assign({}, state, {
shift: action.shift || state.shift,
search: action.search || state.search,
filteredData: searchFilter(state.search, newData)
});
case "LOAD_DATA_START":
return Object.assign({}, state, {
day: action.day
});
case "LOAD_DATA_END":
var newData = action.payload.data[state.shift].filter(x => {
return (
x.actual &&
x.actual.includes(
action.payload.day
.split("-")
.reverse()
.join("-")
)
);
});
return Object.assign({}, state, {
data: action.payload.data,
shift: Object.keys(action.payload.data)[0],
filteredData: searchFilter(state.search, newData)
});
default:
return state;
}
}
app.js(main component)
import React from "react";
import { Component } from "react";
import { connect } from "react-redux";
import { fetchData } from "../actions";
import TableData from "./TableData";
import TableSearch from "./TableSearch";
import Header from "./Header";
import Footer from "./Footer";
import "./app.css";
export function searchFilter(search, data) {
return data.filter(n => n.term.toLowerCase().includes(search));
}
const days = ["23-08-2019", "24-08-2019", "25-08-2019"];
class Root extends React.Component {
componentDidMount() {
this.props.onFetchData(days[this.props.propReducer.day]);
}
render() {
const { onFilter, onSetSearch, onFetchData } = this.props;
const { search, shift, data, filteredData } = this.props.propReducer;
console.log(filteredData);
return (
<div>
<Header/>
<h1>SEARCH FLIGHT</h1>
<TableSearch
value={search}
onChange={e => onSetSearch(e.target.value)}
onSearch={() => onFilter()}
/>
{days &&
days.map((day, i) => (
<button
key={day}
onClick={() => onFetchData(day)}
className={i === day ? "active" : ""}
>
{day}
</button>
))}
<br />
<div className="buttonShift">
{data &&
Object.keys(data).map(n => (
<button
data-shift={n}
onClick={e => onFilter({ shift: e.target.dataset.shift })}
className={n === shift ? "active" : "noActive"}
>
{n}
</button>
))}
</div>
{data && <TableData data={filteredData} />}
<Footer/>
</div>
);
}
}
export const ConnectedRoot = connect(
state => state,
dispatch => ({
onFilter: args => dispatch({ type: "RUN_FILTER", ...args }),
onSetSearch: search => dispatch({ type: "SET_SEARCH", search }),
onFetchData: day => dispatch(fetchData(day))
})
)(Root);
by the way the function searchFilter is written property term and not property actual. Maybe the problem is partly due to this, but not only this, because I tried to replace the term with actual, but the error remained.
How to fix this error?
Given onFilter action creator in app.js:
onFilter: args => dispatch({ type: "RUN_FILTER", ...args })
When called without the first argument:
<TableSearch ... onSearch={() => onFilter()} />
Then in airplanes.js reducer, action.shift is undefined, so state.data[action.shift] is undefined too and state.data[action.shift].filter is a TypeError.
To fix, you can use a default value:
state.data[action.shift || state.shift].filter
However, there is an additional problem, inside TableSearch.js you call:
<button ... onClick={() => onSearch(value)}>
So you shouldn't ignore the value in app.js:
<TableSearch ... onSearch={(value) => onFilter({search: value})} />
...and use action.search || state.search inside your .filter(...) depending on the business logic.
I have an application that has a dashboard with a list of soups. Every soup has the ability to be a daily soup. So each soup has a button that if clicked, triggers an action to update my MongoDB to make the soup a daily soup. When a soup is a daily soup, it then has 3 buttons: Remove, Low, Out. If any of these buttons are clicked they trigger an action to update my MongoDB to update that particular soup. The issue I have is that when any of these buttons are clicked, it performs the action but it is not re-rendered on the screen. I have to manually refresh the page to see that it actually worked.
Note: I am using reduxThunk to immediately dispatch the action (see code below)
I have tried using
Object.assign({}, state, action.payload)
in my reducer to be sure to avoid changing the state directly.
I also tried rewriting my reducer with:
case "UPDATE_SOUP":
return {
...state,
isDaily: action.payload.isDaily,
isLow: action.payload.isLow,
isOut: action.payload.isOut
};
React Soup Component:
class Soup extends Component {
render() {
const { soup } = this.props;
return (
<div>
<div key={soup.name} className="card">
<div
className={`card-header ${
soup.isDaily ? "alert alert-primary" : null
}`}
>
{soup.isDaily ? (
<span className="badge badge-primary badge-pill">Daily Soup</span>
) : (
"Soup"
)}
</div>
<div className="card-body">
<h5 className="card-title">{soup.name}</h5>
<p className="card-text">
{soup.isLow ? (
<span className="badge badge-warning badge-pill">
This soup is marked as LOW.
</span>
) : null}
{soup.isOut ? (
<span className="badge badge-dark badge-pill">
This soup is marked as OUT.
</span>
) : null}
</p>
{soup.isDaily ? (
<div>
<button
onClick={() =>
this.props.updateSoup(soup._id, {
isDaily: false,
isLow: false,
isOut: false
})
}
className="btn btn-danger "
>
Remove
</button>
<button
onClick={() =>
this.props.updateSoup(soup._id, {
isLow: true
})
}
className="btn btn-warning"
>
Getting Low
</button>
<button
onClick={() =>
this.props.updateSoup(soup._id, {
isOut: true
})
}
className="btn btn-dark"
>
Ran Out
</button>
</div>
) : (
<button
onClick={event =>
this.props.updateSoup(soup._id, {
isDaily: true
})
}
className="btn btn-primary"
>
Make Daily
</button>
)}
</div>
</div>
</div>
);
}
}
function mapStateToProps({ soupsReducer }) {
return { soupsReducer };
}
export default connect(
mapStateToProps,
actions
)(Soup);
React SoupList Component (To show all Soups):
class SoupList extends Component {
componentDidMount() {
this.props.allSoups();
}
renderSoup() {
const { soupsReducer } = this.props;
if (soupsReducer.length > 0) {
return soupsReducer.map(soup => {
if (soup.name !== "date") {
return <Soup key={soup._id} soup={soup} />;
} else {
return null;
}
});
}
}
render() {
console.log("SoupListProps=", this.props);
return <div>{this.renderSoup()}</div>;
}
}
function mapStateToProps({ soupsReducer, dateReducer }) {
return { soupsReducer, dateReducer };
}
export default connect(
mapStateToProps,
actions
)(SoupList);
Action:
export const updateSoup = (id, update) => async dispatch => {
const res = await axios.put(`/api/allsoups/${id}`, update);
dispatch({ type: "UPDATE_SOUP", payload: res.data });
};
Reducer:
export default function(state = [], action) {
switch (action.type) {
case "FETCH_SOUPS":
return action.payload;
case "ALL_SOUPS":
return action.payload;
case "UPDATE_SOUP":
return action.payload;
default:
return state;
}
}
The issue is that you are re-writing your whole state in every action by doing
return action.payload;
You need to do something like
return { ...state, someStateKey: action.payload.data.someKey }
Where depending on the action type you pull the required data from the response and set that in your state.
If you can provide more info on the response, I can update the answer with more specific details
My thoughts are revolving around this part of your code...
export const updateSoup = (id, update) => async dispatch => {
const res = await axios.put(`/api/allsoups/${id}`, update);
dispatch({ type: "UPDATE_SOUP", payload: res.data });
};
export default function(state = [], action) {
// ...code...
case "UPDATE_SOUP":
return action.payload;
// ...code...
}
}
Try this:
Identify the souptype AND the change to your action...
dispatch({ type: "UPDATE_SOUP", payload: res.data, souptype: id, update: update });
Update the state to the souptype to your reducer...
export default function(state = [], action) {
case "UPDATE_SOUP":
const newstate = action.payload;
neswstate.soups[action.souptype] = action.isDaily ? true : false;
return newstate;
Of course, why won't this work? Simply because I'm guessing what kind of state you have and how the soups are stored in this state. There is no constructor or state definition in your code, so, you'll need to adjust what's above to match how your state is defined.