TypeError: state is not iterable - javascript

I am new to React js, and I am trying to create a contact list. Where you can add contacts, update, and delete contacts from your contact list. However, I am trying to initialize the state of my contact list but when I run it, it displays the contact list screen without the "initialState" of the contact list. Then when I try to add a contact it then throws a type error screen(state is not iterable). How do I fix this issue?
contactReducer.js:
const initialState = [
{ id: 0, name: "Raman Sharma", Level: " 20", Progress: "Master" },
{ id: 1, name: "Test Name", Level: " 25", Progress: "Master" },
];
export const contactReducer = (state = initialState , action) => {
switch (action.type) {
case "ADD_CONTACT":
state = [...state, action.payload];
return state;
case "DELETE_CONTACT":
const contactFilter = state.filter((contact) =>
contact.id === action.payload ? null : contact
);
state = contactFilter;
return state;
case "UPDATE_CONTACT":
const contactUpdate = state.filter((contact) =>
contact.id === action.payload.id
? Object.assign(contact, action.payload)
: contact
);
state = contactUpdate;
return state;
case "RESET_CONTACT":
state = [{ name: null, Level: null, Progress: null }];
return state;
default:
return state;
}
};
./client/AddContact/index.js
import React, { useState } from "react";
import { connect } from "react-redux";
import { useHistory } from "react-router";
import { toast } from "react-toastify";
const AddPost = ({ contacts, addContact }) => {
const [name, setName] = useState("");
// const [Level, setLevel] = useState("");
// const [Progress, setProgress] = useState("");
const history = useHistory();
const handleSubmit = (e) => {
e.preventDefault();
// const checkContactLevelExists = contacts.filter((contact) =>
// contact.Level === Level ? contact : null
//);
// const checkContactProgressExists = contacts.filter((contact) =>
// contact.Progress === Progress ? contact : null
//);
if ( !name) {
return toast.warning("Please fill in field!!");
}
// if (checkContactLevelExists.length > 0) {
// return toast.error("This Level already exists!!");
// }
// if (checkContactProgressExists.length > 0) {
// return toast.error("This Progress number already exists!!");
// }
const data = {
id: contacts.length > 0 ? contacts[contacts.length - 1].id + 1 : 0,
// Level,
name,
// Progress,
};
addContact(data);
toast.success("Player Added successfully!!");
history.push("/");
};
return (
<div className="container-fluid">
<h1 className="text-center text-dark py-3 display-2">Add Friend</h1>
<div className="row">
<div className="col-md-6 p-5 mx-auto shadow">
<form onSubmit={handleSubmit}>
<div className="form-group">
<input
className="form-control"
type="text"
placeholder="Full name"
value={name}
onChange={(e) => setName(e.target.value)}
/>
</div>
{/* <div className="form-group">
<input
className="form-control"
type="Level"
placeholder="Level"
value={Level}
onChange={(e) => setLevel(e.target.value)}
/>
</div> */}
{/* <div className="form-group">
<input
className="form-control"
type="number"
placeholder="Progress"
value={Progress}
onChange={(e) => setProgress(e.target.value)}
/>
</div> */}
<div className="form-group">
<input
className="btn btn-block btn-dark"
type="submit"
value="Add Student"
/>
</div>
</form>
</div>
</div>
</div>
);
};
const mapStateToProps = (state) => ({
contacts: state,
});
const mapDispatchToProps = (dispatch) => ({
addContact: (data) => {
dispatch({ type: "ADD_CONTACT", payload: data });
},
});
export default connect(mapStateToProps, mapDispatchToProps)(AddPost);
home.js
import React from "react";
import { connect } from "react-redux";
import { Link } from "react-router-dom";
const Friends = ({ contacts, deleteContact }) => {
return (
<div className="container">
<div className="row d-flex flex-column">
<Link to="/add" className="btn btn-outline-dark my-5 ml-auto ">
Add Friend
</Link>
<div className="col-md-10 mx-auto my-4">
<table className="table table-hover">
<thead className="table-header bg-dark text-white">
<tr>
<th scope="col">Id</th>
<th scope="col">Name</th>
<th scope="col">Level</th>
<th scope="col">Progess</th>
<th scope="col">Actions</th>
</tr>
</thead>
<tbody>
{contacts.length > 0 ? (
contacts.map((contact, id) => (
<tr key={id}>
<td>{id + 1}</td>
<td>{contact.name}</td>
<td>{contact.Level}</td>
<td>{contact.Progress}</td>
<td>
<Link
to={`/edit/${contact.id}`}
className="btn btn-sm btn-primary mr-1"
>
Edit
</Link>
<button
type="button"
onClick={() => deleteContact(contact.id)}
className="btn btn-sm btn-danger"
>
Remove
</button>
</td>
</tr>
))
) : (
<tr>
<th>No contacts found</th>
</tr>
)}
</tbody>
</table>
</div>
</div>
</div>
);
};
const mapStateToProps = (state) => ({
contacts: state,
});
const mapDispatchToProps = (dispatch) => ({
deleteContact: (id) => {
dispatch({ type: "DELETE_CONTACT", payload: id });
},
});
export default connect(mapStateToProps, mapDispatchToProps)(Friends);

The initial state's an object with a contactReducer property that is the array of contacts.
I'm certain you've just specified incorrect default state value.
Instead of
state = { contactReducer: initialState }
You likely want
state = initialState
All the reducers will now correctly specify/update state as an array.
Since the contactReducer reducer was combined with another reducer and provided to the store, this nests the state slice under the key you combined them with.
const reducer = combineReducers({
login: authReducer,
contactInfo: contactReducer, // <-- this
});
Instead of accessing via just state.X it is now state.contactInfo.X.
const mapStateToProps = (state) => ({
contacts: state.contactInfo,
});

However, there might be an issue in delete contact case in the use of filter.
Callback passed to a filter should return true or false only based on which it will be included or not in resultant array, instead you seem to have returned null or object.
Corrected:
case "DELETE_CONTACT":
const contactFilter = state.filter((contact) =>
contact.id !== action.payload
);
state = contactFilter;
return state;
The above filter will return true if the id's are not equal, which means it will be considered or false which means it will not be included in the resultant array
Additionally, in case of Update, you need to replace filter with map function as below:
case "UPDATE_CONTACT":
const contactUpdate = state.map((contact) =>
contact.id === action.payload.id
? {...contact, ...action.payload}
: contact
);
state = contactUpdate;
return state;
Furthermore, i would changes the contactReducer.js as below:
const initialState = { contacts: [
{ id: 0, name: "Raman Sharma", Level: " 20", Progress: "Master" },
{ id: 1, name: "Test Name", Level: " 25", Progress: "Master" },
]};
export const contactReducer = (state = initialState , action) => {
switch (action.type) {
case "ADD_CONTACT":
return {contacts: [...state.contacts, action.payload]};
case "DELETE_CONTACT":
return {contacts: state.contacts.filter((contact) =>
contact.id !== action.payload
)};
case "UPDATE_CONTACT":
return {contacts: state.contacts.map((contact) =>
contact.id === action.payload.id
? {...contact, ...action.payload}
: contact
)};
case "RESET_CONTACT":
return {contacts: [{ name: null, Level: null, Progress: null }]};
default:
return state;
}
};

Related

Handle dynamically created text inputs with usestate - ReactJS

I'm making a todo list in react js. Each time a new todo item is created, some buttons are appended next to it along with a edit input text box. I'm trying to avoid using refs but purely usestate for my case, however I can't figure out how to do it. At its current state, all edit text inputs are using the same state and that brings focus loss along with other issues. I'd highly appreciate any suggetsions.
import "./theme.css"
import * as appStyles from "./styles/App.module.css"
import * as todoStyles from "./styles/Todo.module.css"
import { useState } from "react"
const initialState = [
{
id: "1",
name: "My first ToDo",
status: "new",
},
]
export function App() {
const [numofItems, setNumofItems] = useState(2)
const [newToDo, setnewToDo] = useState('');
const [todos, setTodos] = useState(initialState);
const [editTodo, setEditTodo] = useState({name: ""});
const onAddTodo = () => {
setnewToDo("");
setTodos((old) => [
...old,
{ id: numofItems.toString(), name: newToDo, status: "new" },
])
setNumofItems(numofItems + 1);
}
deleteList = () =>{
setTodos([]);
}
const handleEdit = (id, description) =>{
let el = todos.map((item) => {if(item.id === id) {item.name = description} return item});
setTodos(el);
setEditTodo('');
}
const handleMove = (id, position) =>{
const search = obj => obj.id === id;
const todoIndex = todos.findIndex(search);
if(position === "up"){
if (todos[todoIndex - 1] === undefined) {
} else {
const newTodo1 = [...todos];
const temp1 = newTodo1[todoIndex - 1];
const temp2 = newTodo1[todoIndex]
newTodo1.splice(todoIndex - 1, 1, temp2);
newTodo1.splice(todoIndex, 1, temp1);
setTodos([...newTodo1]);
}
}
else if(position === "down"){
if (todos[todoIndex + 1] === undefined) {
} else {
const newTodo1 = [...todos];
const temp1 = newTodo1[todoIndex + 1];
const temp2 = newTodo1[todoIndex]
newTodo1.splice(todoIndex + 1, 1, temp2);
newTodo1.splice(todoIndex, 1, temp1);
setTodos([...newTodo1]);
}
}
}
const Todo = ({ record }) => {
return <li className={todoStyles.item}>{record.name}
<button className={appStyles.editButtons} onClick={() => deleteListItem(record.id)} >Delete</button>
<button className={appStyles.editButtons} onClick={() => handleEdit(record.id, editTodo.name)}>Edit</button>
<button className={appStyles.editButtons} onClick={() => handleMove(record.id, "down")}>Move down</button>
<button className={appStyles.editButtons} onClick={() => handleMove(record.id, "up")}>Move up</button>
<input className={appStyles.input}
type = "text"
name={`editTodo_${record.id}`}
value = {editTodo.name}
onChange={event => {event.persist();
setEditTodo({name: event.target.value});}}
/></li>
}
const deleteListItem = (todoid) => {
setTodos(todos.filter(({id}) => id !== todoid))
}
return (
<>
<h3 className={appStyles.title}>React ToDo App</h3>
<ul className={appStyles.list}>
{todos.map((t, idx) => (
<Todo key={`todo_${idx}`} record={t} />
))}
</ul>
<div className={appStyles.actions}>
<form>
<label>
Enter new item:
<input className={appStyles.input} type="text" name="newToDo" value={newToDo} onChange={event => setnewToDo(event.target.value)}/>
</label>
</form>
<button
className={appStyles.button}
onClick={onAddTodo}
>
Add
</button>
<br></br>
<button className={appStyles.button} onClick={this.deleteList}>
Delete List
</button>
</div>
</>
)
}
Never define components in the body of another component. It will result in unmount/mount of that element every time it's rendered.
Here is how you can split up the Todo component from you App:
const Todo = ({ record, onDelete, onEdit, onMove }) => {
const [inputValue, setInputValue] = useState(record.name);
return (
<li className={todoStyles.item}>
{record.name}
<button className={appStyles.editButtons} onClick={() => onDelete()}>
Delete
</button>
<button
className={appStyles.editButtons}
onClick={() => onEdit(inputValue)}
>
Edit
</button>
<button className={appStyles.editButtons} onClick={() => onMove("down")}>
Move down
</button>
<button className={appStyles.editButtons} onClick={() => onMove("up")}>
Move up
</button>
<input
className={appStyles.input}
type="text"
value={inputValue}
onChange={(event) => {
setInputValue(event.target.value);
}}
/>
</li>
);
};
function App() {
return (
<>
<ul className={appStyles.list}>
{todos.map((t, idx) => (
<Todo
key={`todo_${idx}`}
record={t}
onDelete={() => deleteListItem(t.id)}
onEdit={(description) => handleEdit(t.id, description)}
onMove={(position) => handleMove(t.id, position)}
/>
))}
</ul>
</>
);
}
Note: I've shown only the interesting bits, not your entire code.
If you're going to do it this way I would suggest using useReducer instead of useState.
const initialState = [
{
id: "1",
name: "My first ToDo",
status: "new",
},
]
export const types = {
INIT: 'init',
NEW: 'new'
}
export default function (state, action) {
switch (action.type) {
case types.INIT:
return initialState;
case types.NEW:
return { ...state, { ...action.item } };
default:
return state;
}
}
Now in your component you can use it like this:
import {useReducer} from 'react';
import reducer, { initialState, types } from './wherever';
const [state, dispatch] = useReducer(reducer, initialState);
const handleSubmit = (event) => {
event.preventDefault();
dispatch({ type: types.NEW, item: event.target.value });
}

When I remove an item from a array, how to change the status to deleted?

When the user adds a value to an object that is inside an array the status is updated to added.
I'm trying to do the same thing when that value is deleted, to update the status to deleted.
const initialName = [
{
name: "",
status: "",
},
];
export default function changeNames(){
const [name, setName] = useState([initialName ]);
const handleAdd = () => {
setName([...name, ""]);
};
const handleItemChanged = (event, index) => {
const value = event.target.value;
const list = [...name];
list[index] = { name: value + "-" + id, status: "added" };
setName(list);
};
...
}
So when I add an input field and type the name, the array looks like this:
[{…}]
0:
name: "John-id"
status: "added"
When I remove John from the array, I want smth like this:
[{…}]
0:
name: "John-id"
status: "deleted"
This is the remove function
const handleRemoveClick = (index) => {
const list = [...name];
list.splice(index, 1);
setName(list);
};
<div>
{name.map((o, i) => {
return (
<tr key={"item-" + i}>
<td>
<div>
<input
type="text"
value={o.item}
onChange={(event) => handleItemChanged(event, i)}
placeholder="Name it"
/>
</div>
</td>
<td>
<button type="button" onClick={handleRemoveClick}>
Delete
</button>
</td>
</tr>
);
})}
<div>
<button Click={handleAddClick}>
Add Language
</button>
</div>
</div>
);
How can I make it work?
I think this might help you.thank you
import { useState } from "react";
import "./styles.css";
export default function App() {
const [Name, setName] = useState([]);
const [input, setInput] = useState({ name: "", status: "" });
const handleItemChanged = (event, index) => {
const { value } = event.target;
setInput({ name: value + "-id", status: "added" });
};
const addHandler = () => {
setName((prev) => {
return [...prev, input];
});
};
const removeHandler = (i) => {
const arr = [...Name];
arr[i].status = "deleted";
setName(arr);
};
return (
<div className="App">
<input type="text" name="name" onChange={(e) =>
handleItemChanged(e)} />
<button onClick={addHandler} style={{ margin: "1rem" }}>
Add
</button>
<div>
{Name &&
Name.map((data, i) => {
return (
<div key={i}>
<h3>
{data.name} {data.status}
<button
style={{ margin: "1rem" }}
onClick={() => removeHandler(i)}
>
Delete
</button>
</h3>
</div>
);
})}
</div>
</div>
);
}
You need to change the "status" property, because there is no way of removing item and setting its property.
const handleRemoveClick = (event, index) => {
const list = [...name];
list[index].status = "deleted";
setName(list);
};
And later while rendering you have to either perform a check in map function (not really elegant), or first filter your array and then map it:
// first, not elegant in my opinion
...
{name.map(item => item.status !== "deleted" ? <div>item.name</div> : null)}
// second approach
...
{name.filter(item => item.status !== "deleted").map(item => <div>item.name</div>)}

React list.map is not a function when deleting from local storage

I'm working on a budget tracking app, where the user can add different incomes and expenses. I'm using useReducer to manage state. Each income and expense is an object and when the user submits an income or expense, it is displayed as an income list with income items or expense list with expense items. Each item in the list has a remove button that should remove the income item or expense item. But when I try click the remove button to remove an income item for example, I get an error saying list.map is not a function.
Not exactly sure why this happens, but I think it has something to do with the removeItem function in IncomeOutputList.js. What would be the proper way to remove an income item?
App.js
import React, { useState, useReducer } from 'react';
import './App.css';
import BudgetInput from './components/input/BudgetInput';
import BudgetOutput from './components/output/BudgetOutput';
import IncomeOutputList from './components/output/IncomeOutputList';
import ExpenseOutputList from './components/output/ExpenseOutputList';
const useSemiPersistentState = (key, initialState) => {
// console.log(JSON.parse(localStorage.getItem(key)));
const [value, setValue] = React.useState(
localStorage.getItem(key) ? JSON.parse(localStorage.getItem(key)) : initialState
);
React.useEffect(()=>{
localStorage.setItem(key, JSON.stringify(value));
}, [value, key])
return [value, setValue];
};
const initialBudget = {
description: '',
type: '+',
key: 'income',
value: ''
};
const initialState = {
incomes: [{}],
expenses: [{}],
budgetObj: initialBudget
};
const budgetReducer = (state, action) => {
switch(action.type) {
case "CHECK_STATE":
console.log(state); //just to check state on submit
case "ON_DESC_CHANGE":
return {
...state,
budgetObj: {
...state.budgetObj,
description: action.payload
}
}
case "ON_TYPE_CHANGE":
const isExpense = action.payload === '-';
return {
...state,
budgetObj: {
...state.budgetObj,
type: action.payload,
key: isExpense ? 'expense' : 'income'
}
}
case "ON_VALUE_CHANGE":
return {
...state,
budgetObj: {
...state.budgetObj,
value: action.payload,
}
}
case 'SUBMIT_BUDGET':
console.log(state.incomes);
const budget = {...state};
const isIncome = budget.budgetObj.type === '+';
return {
incomes: isIncome ? state.incomes.concat(budget.budgetObj) : state.incomes,
expenses: isIncome ? state.expenses : state.expenses.concat(budget.budgetObj),
budgetObj: initialBudget,
}
case "REMOVE_ITEM":
console.log('test');
return {
...state,
// not sure if the following line is the correct way to remove from local storage
incomes: (index) => JSON.parse(localStorage.getItem("income")).splice(index,1),
// expenses: (index) => JSON.parse(localStorage.getItem("expense")).splice(index,1)
}
default:
return state;
}
}
const useSemiPersistantReducer = (key, initialState) => {
const [value, dispatch] = React.useReducer(
budgetReducer,
localStorage.getItem(key) ? JSON.parse(localStorage.getItem(key)) : initialState
);
React.useEffect(()=>{
localStorage.setItem(key, JSON.stringify(value));
}, [value, dispatch]) //[inocmes, setIncomes]
return [value, dispatch];
}
const App = () => {
const [budgetState, setBudget] = useSemiPersistantReducer(initialState.budgetObj.key,initialState);
const {incomes, expenses, budgetObj, key} = budgetState;
return (
<div className="App">
<link href="http://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css" rel="stylesheet" type="text/css"></link>
<div className="top">
<BudgetOutput />
</div>
<div className="bottom">
<BudgetInput
descValue={budgetObj.description || ''} //event.target.value
onDescChange={event => setBudget({ type: "ON_DESC_CHANGE", payload: event.target.value })}
onSelectChange={event => setBudget({ type: "ON_TYPE_CHANGE", payload: event.target.value })}
type={budgetObj.type || ''}
onBudgetSubmit={ () => setBudget({ type : 'SUBMIT_BUDGET' }) }
budgetValue={budgetObj.value || ''}
onValChange={event => setBudget({ type: "ON_VALUE_CHANGE", payload: event.target.value })}
/>
<div className="container clearfix">
<IncomeOutputList
list={incomes}
removeIncome={ () => setBudget({ type: "REMOVE_ITEM" })} //not sure if this is correct?
/>
<ExpenseOutputList
list={expenses}
// removeExpense={(index)=>removeExp(index)}
/>
</div>
</div>
</div>
)
};
export default App;
IncomeOutput.js
import React from 'react';
import IncomeOutput from './IncomeOutput';
// list will be list of income objects
const IncomeOutputList = ({ list, removeIncome }) => {
return (
<div className="income__list">
<div className="income__list--title">INCOME</div>
{list.map((item, index, arr) => <IncomeOutput
id={item.id}
value={item.value}
type={item.type}
desc={item.description}
// error has something to do with the following line?
handleButton={()=>removeIncome(index)}
/>
)}
</div>
)
}
export default IncomeOutputList;
IncomeOutput.js
import React from 'react';
import ValueOutput from './ValueOutput';
const IncomeOutput = ({ desc, type,id, value, handleButton }) => {
return (
<>
<div className="item clearfix income" id={id}>
<div className="item__description">{desc}</div>
<ValueOutput
type={type}
value={value}
handleClick={handleButton}
/>
</div>
</>
)
}
export default IncomeOutput;
ValueOutput.js
import React from 'react';
import Button from '../buttons/Button';
const ValueOutput = ({type, value, handleClick}) => {
return (
<>
<div className="right clearfix">
<div className="item__value">{type} {value}</div>
<Button buttonType="item__delete--btn" handler={handleClick}/>
</div>
</>
)
}
export default ValueOutput;
Button.js
import React from 'react';
const Button = ({buttonType, handler}) => (
<>
<div className="item__delete">
<button className={buttonType} onClick={handler}>
<i className="ion-ios-close-outline"></i>
</button>
</div>
</>
)
export default Button;
I didn't run your code but just a guess
incomes: (index) => JSON.parse(localStorage.getItem("income")).splice(index,1),
incomes is a function not an array, so "incomes.map()" does not exist.

map function insert same data multiple times (React-Redux)

I am trying to implement the React Accordion project using React-Redux.
Here is my Reducer code where I have a map function to perform operations one by one on every id:
import * as actionTypes from '../actions/actions';
const initialState = {
active: [
{ id: 1, status: false },
{ id: 2, status: false },
{ id: 3, status: false },
{ id: 4, status: false }
]
}
const reducer = (state = initialState, action) => {
switch(action.type) {
case actionTypes.ACTIVE_STATE:
return {
...state,
active: state.active.map((acdnobj) => {
const panel = document.querySelector(`.panel-${acdnobj.id}`);
return {
...acdnobj,
acdnobj: acdnobj.id === parseInt(action.id) ? (
acdnobj.status = true,
panel.style.maxHeight = panel.scrollHeight + "px"
) : (
acdnobj.status = false,
panel.style.maxHeight = '0px'
)
}
})
}
default:
}
return state;
}
export default reducer;
And this is my Accordion where I have another map function to increase the id numbers:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as actionTypes from '../store/actions/actions';
class Accordion extends Component {
localDispatch = (key) => {
this.props.expandAccordion(key.target.value);
}
render() {
return (
<div>
{this.props.accordions.map((accordion, index) => {
return (
<div key={index}>
<button value={ index + 1 } className={`accordion ${accordion.status}`}
onClick={this.localDispatch.bind(this)}>
{this.props.title}
</button>
<div className={`panel panel-${accordion.id}`}>
{this.props.content}
</div>
</div>
)
})}
</div>
);
}
}
const mapStateToProps = (state) => {
return {
accordions: state.data.active
};
}
const mapDispatchToProps = (dispatch) => {
return {
expandAccordion: (key) => dispatch({type: actionTypes.ACTIVE_STATE, id: key})
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Accordion);
And in App.js component where I have another map function to fetch data from an excel file:
if(list.length > 0) {
accordionDIV = list[0].map((d, index) => (
<Accordion _key={index}
title = {
<Table>
<tr key={d.ID}>
<td>{d.ID}</td>
<td>{d.Mail}</td>
<td>{d.Name}</td>
<td>{d.PhoneNo}</td>
<td>{d.City}</td>
<td>{d.Date}</td>
<td>{d.Time}</td>
</tr>
</Table>
}
content = {
<div>
<p className="header">
<span style={{color:"#3c67a5"}}>Shipping Address:</span>
292 Naqashband Colony, Near rabbania Mosque, Multan
</p>
<Table size="sm" className="content">
<thead>
<tr>
<th style={{width:"10%"}}></th>
<th style={{width:"15%"}}>Article No</th>
<th style={{textAlign:"left", width:"30%"}}>Product Name</th>
<th style={{width:"15%"}}>Quantity</th>
<th style={{width:"15%"}}>Price</th>
<th style={{width:"20%"}}>Total Amount</th>
</tr>
</thead>
<tbody>
{list[1].map((c) =>
c.ID === d.ID ? (
<tr key={c.ID}>
<td> <FontAwesomeIcon icon={faTrashAlt} size="xs" className="icon" /> </td>
<td>{c.ArticleNo}</td>
<td style={{textAlign:"left"}}>{c.ProductName}</td>
<td>{c.Quantity}</td>
<td>{c.Price}</td>
<td>{c.TotalAmount}</td>
</tr>
) : null
)}
</tbody>
</Table>
</div>
}
/>
))
}
The problem is that in the run time I have multiple accordions for the same data.
Suggest me what can I do for now to solve this problem.
import * as actionTypes from '../actions/actions';
const initialState = {
active: [
{ id: 1, status: false },
{ id: 2, status: false },
{ id: 3, status: false },
{ id: 4, status: false }
]
}
const reducer = (state = initialState, action) => {
switch(action.type) {
case actionTypes.ACTIVE_STATE:
let newActive = [...state.active];
newActive = newActive.map((acdnobj) => {
const panel = document.querySelector(`.panel-${acdnobj.id}`);
panel.style.maxHeight = (acdnobj.id === parseInt(action.id)) ? panel.scrollHeight + "px" : '0px';
return {
...acdnobj,
status: acdnobj.id === parseInt(action.id)
}
})
return {
...state,
active: newActive
}
default:
}
return state;
}
export default reducer;
This could be the array mutable issue on Javascript so you can use the above code or try with immutable.js

Update component when the store changes

I'm trying to update a component based on store updates, the objective of that is, when I click on a table item, I want to update the form buttons to edit form, and Edit the table item.
My source code:
I have an action which updates currentUser. currentUser is the user I want to update
src/actions/user.js
export const updateCurrentUserSuccess = (currentUser) => {
return {
type: UPDATE_CURRENT_USER,
currentUser
}
}
export const updateCurrentUser = (id) => {
return (dispatch) => {
return axios.get(`${apiUrl}/users/${id}`)
.then(response => {
console.log(response.data.data)
dispatch(updateCurrentUserSuccess(response.data.data))
})
.catch(error => {
throw (error);
});
};
};
my currentUserReducer:
src/reducers/currentUserReducer.js
import { UPDATE_CURRENT_USER } from '../constants/ActionTypes';
const initialState = {
currentUser: [],
}
export default function userReducer(state = initialState, action) {
switch (action.type) {
case UPDATE_CURRENT_USER:
return action.currentUser;
default:
return state;
}
}
now my components:
my NewUser form:
src/components/NewUser.js
import React, { Component } from 'react';
import { Store } from '../store'
class NewUser extends Component {
state = {
id: '',
name: '',
cpfcnpj: '',
isEdit: false
};
componentDidMount(){
this.handleUserChange()
}
handleInputChange = e => {
this.handleUserChange();
this.setState({
[e.target.name]: e.target.value
});
};
handleSubmit = e => {
e.preventDefault();
if (!this.state.isEdit) {
if (this.state.name.trim() && this.state.cpfcnpj.trim()) {
this.props.onAddUser(this.state);
this.handleReset();
}
} else {
if (this.state.name.trim() && this.state.cpfcnpj.trim() && this.state.id !== '') {
this.props.onEdit(this.state);
this.handleReset();
}
}
};
handleReset = () => {
Store.getState().currentUser = []
this.setState({
id: '',
name: '',
cpfcnpj: '',
isEdit: false
});
};
handleUserChange() {
console.log('store', Store.getState().currentUser._id);
if (Store.getState().currentUser._id !== undefined) {
this.setState({
id: Store.getState().currentUser._id,
name: Store.getState().currentUser.name,
cpfcnpj: Store.getState().currentUser.cpfcnpj,
isEdit: true
});
}
}
render() {
return (
<div>
<form className="form-inline" onSubmit={this.handleSubmit}>
<div className="form-group margin-right">
<input
type="text"
placeholder="Name"
className="form-control"
name="name"
onChange={this.handleInputChange}
value={this.state.name}
/>
</div>
<div className="form-group margin-right">
<input
type="text"
placeholder="CPF/CNPJ"
className="form-control"
name="cpfcnpj"
onChange={this.handleInputChange}
value={this.state.cpfcnpj}>
</input>
</div>
<div className="form-group">
<button type="submit" className={this.state.isEdit ? "btn btn-success margin-right hidden" : "btn btn-success margin-right"}>
<span className="glyphicon glyphicon-plus" aria-hidden="true"></span>
Adicionar
</button>
<button type="submit" className={this.state.isEdit ? "btn btn-primary margin-right" : "btn btn-primary margin-right hidden"}>
<span className="glyphicon glyphicon-floppy-disk" aria-hidden="true"></span>
Salvar
</button>
<button type="button" className="btn btn-default margin-right" onClick={this.handleReset}>
<span className="glyphicon glyphicon-erase" aria-hidden="true"></span>
Limpar
</button>
</div>
</form>
</div>
);
}
}
export default NewUser;
my component User item:
***src/components/User.js***
import React from 'react';
export default ({ user: { name, cpfcnpj, _id }, onDelete, onEditUser }) => {
return (
<tr>
<th scope="row">{name}</th>
<td>{cpfcnpj}</td>
<td>
<button className="btn btn-warning btn-xs margin-right" type="button" onClick={() => onEditUser(_id)}>
<span className="glyphicon glyphicon-edit" aria-hidden="true"> </span>
Editar
</button>
<button className="btn btn-danger btn-xs margin-right" type="button" onClick={() => onDelete(_id)}>
<span className="glyphicon glyphicon-trash" aria-hidden="true"> </span>
Excluir
</button>
</td>
</tr>
);
};
now my smart components:
src/containers/UserList.js
import React from 'react';
import { connect } from 'react-redux';
import User from '../components/User';
import { deleteUser, updateCurrentUser } from '../actions/user';
import NewUser from '../components/NewUser';
function UserList({ users, onDelete, onEditUser }) {
if (!users.length) {
return (
<div className="margin-top">
No Users
</div>
)
}
return (
<div className="margin-top">
<table className="table table-striped">
<thead>
<tr>
<th scope="col">Nome</th>
<th scope="col">CPF/CNPJ</th>
</tr>
</thead>
<tbody>
{users.map(user => {
return (
<User user={user} onDelete={onDelete} onEditUser={onEditUser} key={user._id} />
);
})}
</tbody>
</table>
</div>
);
}
const mapStateToProps = state => {
return {
users: state.users
};
};
const mapDispatchToProps = dispatch => {
return {
onDelete: id => {
dispatch(deleteUser(id));
},
onEditUser: (id) => {
dispatch(updateCurrentUser(id))
}
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(UserList, NewUser);
src/containers/CreateUser.js
import { connect } from 'react-redux';
import { createUser, updateUser } from '../actions/user';
import NewUser from '../components/NewUser';
const mapDispatchToProps = dispatch => {
return {
onAddUser: user => {
dispatch(createUser(user));
},
onEdit: (id, name, cpfcnpj) => {
dispatch(updateUser(id, name, cpfcnpj))
}
};
};
export default connect(
null,
mapDispatchToProps
)(NewUser);
src/App.js
import React, { Component } from 'react';
import CreateUser from './containers/CreateUser';
import UserList from './containers/UserList';
import './css/main.css'
class App extends Component {
render() {
return (
<div className="container">
<h1 className="styles-app">Usuários</h1>
<div className="row styles-app">
<div className="col-md-12">
<CreateUser />
</div>
<div className="col-md-12">
<UserList />
</div>
</div>
</div>
);
}
}
export default App;
Here is something you might try. Connect your NewUser.js to the store.
import { connect } from 'react-redux;
export default connect(mapStateToProps)(NewUser);
Then map your currentUser state to props.
const mapStateToProps = state => {
return {
currentUser: state.currentUser
};
};
In your currentUserReducer
initialState = {
//Assuming these are the only values in response
id: '',
name: '',
cpfcnpj: '',
isEdit: false
};
export default function userReducer(state = initialState, action) {
switch (action.type) {
case UPDATE_CURRENT_USER:
return {
...state,
id: action.currentUser.id,
name: action.currentUser.name,
cpfcnpj: action.currentUser.cpfcnpj,
isEdit: true
};
default:
return state;
}
}
You should have access to the current user object now in props.
Then in your input value field
value={this.props.currentUser.name}
value={this.props.currentUser.cpfcnpj}
You may also need to do a check to see if these values have been updated. Also, not sure if the placeholder text might interfere.
Hope this gets you closer to the solution.
Edit
In the case of clearing props, you might just add another action to do so.
In your actions for currentUser:
export const clearUserData = () => {
return {
type: CLEAR_USER_DATA,
}
}
And in your reducer:
export default function userReducer(state = initialState, action) {
switch (action.type) {
case UPDATE_CURRENT_USER:
return {
...state,
id: action.currentUser.id,
name: action.currentUser.name,
cpfcnpj: action.currentUser.cpfcnpj,
isEdit: true
};
case CLEAR_USER_DATA:
return {
...state,
id: '',
name: '',
cpfcnpj: '',
isEdit: false
};
default:
return state;
}
}
Add the clearUserData action to execute after you submit your edits and it should reset your reducer. You might even be able to just do
return {
...state,
initialState
};

Categories