React dispatch from reducer firing twice - reactjs

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.

Related

Reducers is not updating the state in redux toolkit

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"}`}>
)

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 Redux: How to create state objects based on data that will change and how to update individual elements within each object

Problem: I don't know how to organise my state to easily make changes to it. It is retrieved as an array of objects. it is mapped in components but making updates to individual elements and then figuring out how to update these in the database.
The application set up: React Redux Saga Node Express Postgres
fetch call retrieves an array of objects (user details), this is added to a state object and then this array is used to map a component with 4 inputs. I want to be able to change any element of any object and have it update to the state. I would then add a fetch to update any changes to the original data to the database. I am looking for information such as websites, books, courses etc that would help to build a foundation so I could solve this issue.
for reference below are sections of my code;
data
userData: Array(4)
0: {firstname: "shane", surname: "smith", email: "shanesmith#gmail.com", auth: false}
1: {firstname: "Sahne", surname: "Smith", email: "shane#gmail.com", auth: false}
etc....
Reducer
FETCH_ADMIN_PAGE_START,
FETCH_ADMIN_PAGE_SUCCESS,
FETCH_ADMIN_PAGE_FAILURE,
UPDATE_ADMIN_USER_DATA,
} from "../types/types";
// import utility functions
const INITIAL_STATE = {
isFetching: false,
userData: [],
errorMessage: null,
};
const adminReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case FETCH_ADMIN_PAGE_START:
return {
...state,
isFetching: true,
};
case FETCH_ADMIN_PAGE_SUCCESS:
return {
...state,
isFetching: false,
userData: action.payload.user[0],
};
case FETCH_ADMIN_PAGE_FAILURE:
return {
...state,
errorMessage: action.payload,
};
case UPDATE_ADMIN_USER_DATA:
return {
...state,
userData: [haven't found a silution],
};
default:
return state;
}
};
export default adminReducer;
Action
import {
FETCH_ADMIN_PAGE_START,
FETCH_ADMIN_PAGE_SUCCESS,
FETCH_ADMIN_PAGE_FAILURE,
UPDATE_ADMIN_USER_DATA,
} from "../types/types";
export const fetchAdminPageStart = () => ({
type: FETCH_ADMIN_PAGE_START,
});
export const fetchAdminPageSuccess = (data) => ({
type: FETCH_ADMIN_PAGE_SUCCESS,
payload: data,
});
export const fetchAdminPageFailure = (errorMessage) => ({
type: FETCH_ADMIN_PAGE_FAILURE,
payload: errorMessage,
});
export const updataAdminUserData = (data) => ({
type: UPDATE_ADMIN_USER_DATA,
payload: data,
});
Component
import * as React from "react";
import { useState, useEffect } from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import PropTypes from "prop-types";
// Material Ui Componnents
import AppBar from "#material-ui/core/AppBar";
import Tab from "#material-ui/core/Tab";
//Components
import UserDetailsComponent from "../../components/container-withheader/users-details.component";
// Styled Components
import { StyledTabs } from "./admin-page.styles";
// Selectors
import { selectAdminPageUserData } from "../../redux/admin-page/admin-page.selectors";
// Actions
import { updataAdminUserData } from "../../redux/admin-page/admin-page.actions";
const AdminPageComponent = ({ userDetails, updataAdminUserData }) => {
useEffect(() => {}, []);
// const [userData, setUserData] = useState({ users: userDetailData });
const [selectedTab, setSelectedTab] = useState(0);
const handleTabChange = (event, newValue) => {
setSelectedTab(newValue);
};
const handleChangeData = (event) => {
console.log(event.target.name);
return updataAdminUserData(event.target);
};
const handleToggleChange = (event) => {
console.log(event.target);
return updataAdminUserData(event.target);
};
const tabArray = ["Users", "Options"];
return (
<div>
<AppBar position='static' color='default'>
<StyledTabs
value={selectedTab}
onChange={handleTabChange}
variant='scrollable'
scrollButtons='auto'
aria-label='scrollable auto tabs example'
>
{tabArray.map((tab, i) => (
<Tab label={tab} key={i} />
))}
</StyledTabs>
</AppBar>
{selectedTab === 0 && (
<UserDetailsComponent
userData={userDetails}
handleChangeData={handleChangeData}
handleToggleChange={handleToggleChange}
/>
)}
</div>
);
};
AdminPageComponent.propTypes = {
userDetails: PropTypes.object,
updataAdminUserData: PropTypes.func,
};
const mapStateToProps = createStructuredSelector({
userDetails: selectAdminPageUserData,
});
const mapDispatchToProps = (dispatch) => ({
updataAdminUserData: (data) => dispatch(updataAdminUserData(data)),
});
export default connect(mapStateToProps, mapDispatchToProps)(AdminPageComponent);
Component Child of above
import React, { useState } from "react";
import { makeStyles } from "#material-ui/core/styles";
import Paper from "#material-ui/core/Paper";
import FormControlLabel from "#material-ui/core/FormControlLabel";
import Switch from "#material-ui/core/Switch";
import PropTypes from "prop-types";
// Styled Components
import {
StyledToolBar,
GridContainer,
RightElement,
StyledTextField,
StyledSwitch,
} from "./users-details.styles";
const useStyles = makeStyles((theme) => ({
root: {
flexGrow: 1,
},
}));
const UserDetailsComponent = ({
userData,
handleChangeData,
handleToggleChange,
}) => {
const classes = useStyles();
const [edit, setEdit] = useState({
editUser: true,
});
const handleEditChange = (event) => {
setEdit({ ...edit, [event.target.name]: event.target.checked });
};
return (
<div className={classes.root}>
<br></br>
<StyledToolBar elevation={3}>
<GridContainer>
<RightElement>
<FormControlLabel
control={
<Switch
checked={edit.editUser}
onChange={handleEditChange}
name='editUser'
/>
}
label='Edit User'
/>
</RightElement>
</GridContainer>
</StyledToolBar>
<br></br>
<Paper styles={{ paddingTop: "2rem" }} elevation={3}>
<form className={classes.root} noValidate autoComplete='on'>
{userData.map((user, i) => (
<div key={i}>
<StyledTextField
name={Number(i)}
title={"Enter first name here"}
alt={"firstname"}
id={`firstname`}
input={`firstname`}
onChange={handleChangeData}
disabled={edit.editUser}
label='First Name'
value={user.firstname}
variant='outlined'
/>
<StyledTextField
name={Number(i)}
id={"surname"}
disabled={edit.editUser}
label='Surame'
value={user.surname}
variant='outlined'
/>
<StyledTextField
name={Number(i)}
id={"email"}
disabled={edit.editUser}
label='Email'
value={user.email}
variant='outlined'
/>
<React.Fragment>
<FormControlLabel
control={
<StyledSwitch
disabled={edit.editUser}
checked={user.auth}
onChange={handleToggleChange}
name={Number(i)}
id={"auth"}
/>
}
label='Has Administration Access'
/>
</React.Fragment>
</div>
))}
</form>
</Paper>
</div>
);
};
export default UserDetailsComponent;
As you can see I need to also find a better way to pass information in the event to differentiate each pice of state I wish to modify.
I don't know if I should modify the array into another type of object, how do I then map the users if I do so. What are best practices for this issue.
Ultimately I just want to learn or be pointed in the direction of information that would help me move past this problem. Any assistance if greatly appreciated.
Thank you in advance.
Solved - converted to object, used multiple values in name then split into array.

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

Resources