Component Not Updating React/Redux - reactjs

I am using Redux on my app. I have an Items component connected to redux. It passes down props to individual Item components.I also have a ItemEditModal component that edits the individual component. The problem i'm facing is that when i edit an Item through the ItemEditModal, the Items component does not update eventhough the redux did change. Here is relevant parts of my code:
my reducer file ..
this is the action to focus on: actionTypes.SET_EDIT_SELECTED_ITEM
const reducer = (state=initialState, action) => {
switch(action.type) {
case (actionTypes.SET_EDIT_SELECTED_ITEM):
let set_edit_selected_item_arr = {...state};
set_edit_selected_item_arr = set_edit_selected_item_arr.items.map(item => {
if (item.id === action.editInfo.id ) {
item.color = action.editInfo.color;
item.size = action.editInfo.size;
item.quantity = action.editInfo.quantity;
return item;
} else {
return item;
}
});
return {
...state,
items: set_edit_selected_item_arr
};
default:
return state;
}
}
my Items Component
import Item from '../../components/Item';
import './Items.css';
class Items extends Component {
render() {
return (
<table className="Items">
<thead>
<tr className="Items__header">
<th className="item_col">{this.props.items.length} ITEMS</th>
<th className="size_col">SIZE</th>
<th className="qty_col">QTY</th>
<th className="price_col">PRICE</th>
</tr>
</thead>
<tbody>
{this.props.items.map(item => {
return (<Item item={item} key={item.name} />);
})}
</tbody>
</table>
)
}
}
const mapStateToProps = (state) => {
return {
items: state.itemRedux.items
}
}
export default connect(mapStateToProps)(Items);
my ItemEditModal component
import './ItemEditModal.css';
class ItemEditModal extends Component {
state = {
id: this.props.itemRedux.selectedItem.id,
size: this.props.itemRedux.selectedItem.size,
color: this.props.itemRedux.selectedItem.color,
quantity: this.props.itemRedux.selectedItem.quantity,
}
onUnselectItemFunc = () => {
this.props.onSetSelectedItem(null);
this.props.onSetEditItemMode(false);
}
onQuantityChange = (e) => {
//validation required
this.setState({quantity: e.target.value})
}
onSelectChange = (e) => {
this.setState({size: e.target.value})
}
onColorChange = (newColor) => {
this.setState({color: newColor})
}
onSubmitForm = (e) => {
e.preventDefault();
this.props.onSetEditSelectedItem(this.state);
this.props.onSetEditItemMode(false);
}
render() {
return (
<div className={"ItemEdit " + (this.props.itemRedux.editItemMode ? 'showModal': '')}>
<div className="ItemEdit__close">
<span onClick={this.onUnselectItemFunc}>x</span>
</div>
<div className="ItemEdit__container">
<div className="ItemEdit__info">
<h4>{this.props.itemRedux.selectedItem.name}</h4>
<h2>$ {this.props.itemRedux.selectedItem.price}</h2>
<p className="ItemEdit__styleNum">{this.props.itemRedux.selectedItem.styleNum}</p>
<p className="ItemEdit__control-color">
<span
className="ItemEdit__control-color--red"
onClick={()=> {this.onColorChange('red')}}>
</span>
<span
className="ItemEdit__control-color--green"
onClick={()=> {this.onColorChange('green')}}>
</span>
<span
className="ItemEdit__control-color--blue"
onClick={()=> {this.onColorChange('blue')}}>
</span>
</p>
<p>Color: {this.state.color}</p>
<form onSubmit={this.onSubmitForm}>
<select onChange={this.onSelectChange}>
<option value="S">small</option>
<option value="M">medium</option>
<option value="L">large</option>
</select>
<input
type="number"
maxLength="2"
defaultValue={this.props.itemRedux.selectedItem.quantity}
onChange={this.onQuantityChange}
/><br/>
<button className="btn btn-primary">EDIT</button>
</form>
Check product details
</div>
<div className="ItemEdit__img">
<img src={this.props.itemRedux.selectedItem.imgUrl} alt="shirt pic" />
</div>
</div>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
itemRedux: state.itemRedux
}
}
const mapDispatchToProps = (dispatch) => {
return {
onSetSelectedItem: (bool) => dispatch(actions.setSelectedItem(bool)),
onSetEditItemMode: (bool) => dispatch(actions.setEditItemMode(bool)),
onSetEditSelectedItem: (itemInfo) => dispatch(actions.setEditSelectedItem(itemInfo))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ItemEditModal);
Maybe i'm not seeing something. thanks in advance.

Nothing happens when I dispatch an action
Sometimes, you are trying to dispatch an action, but your view does not update. Why does this happen? There may be several reasons for this.
Never mutate reducer arguments
It is tempting to modify the state or action passed to you by Redux. Don't do this!
Redux assumes that you never mutate the objects it gives to you in the reducer. Every single time, you must return the new state object. Even if you don't use a library like Immutable, you need to completely avoid mutation
Source: https://redux.js.org/troubleshooting#nothing-happens-when-i-dispatch-an-action

Related

How do I loop through the Axios response to a table in React

I am trying to develop a mini project that retrieves the list of available listed jobs in React and Node as Backend. Little bit stuck at the response from the axios.
This is the response am getting from the axios response.
I want to display the array data into a table or list to show available jobs
Below is the code for that retrieves the data
import React, { useState, useEffect } from 'react'
import Layout from '../../core/Layout'
import axios from 'axios'
import { getCookie, isAuth, signout } from '../../auth/helpers';
const Find = () => {
const [values, setValues] = useState({
title:"",
duration:"",
durationSys:"",
budget:'',
addedBy:'',
category:'',
results:[],
searched: false
});
const { category} = values;
const token = getCookie('token');
const handleChange = category => event => {
console.log(event.target.value);
setValues({ ...values, [category]: event.target.value});
};
const handleClick = event =>{
event.preventDefault()
listJobs()
}
const listJobs = () =>{
axios.get( `${process.env.REACT_APP_API}/search-projects`,
{params: {category
}
})
.then(response => {
console.log('LOG SUCCESS --', response.data);
const data = response.data;
setValues({...values, results: data})
console.log('LOG STATE', data)
})
}
return (
<Layout>
<div class="form-group">
<label for="exampleFormControlSelect1">Category</label>
<select onChange={handleChange('category')} value={category} class="form-control"
id="exampleFormControlSelect1">
<option>--Select Category --</option>
<option value='Web Development'>Web Development</option>
<option value='Logo Design'>Logo Design</option>
<option value='Writing/Skills'>Writing/Skills</option>
<option value='Mobile App Development'>Mobile App Development</option>
<option value='SEO/Marketing'>SEO/Marketing</option>
</select>
</div>
<div class="d-flex justify-content-center">
<button onClick={handleClick} class="btn btn-default btn-info" style={{marginBottom: "15px"}}>Search</button>
</div>
<div>
<h5>List of available jobs</h5>
//here
</div>
</Layout>
)
}
export default Find;
Hi you can do something like this.
<ul>
(results || []).map((item, index) => <li key={index}> {item}</li>
</ul>
I would also suggest to convert your handleChange function ( and the rest ) to useCallback functions to reduce unnecessary updates.
Suppose job has some id, title and description:
{ results.map(( job, index ) => {
return (
<tr key={job.id}>
<td>{job.id}</td>
<td>{job.title}</td>
<td>{job.description}</td>
</tr>
);
})}
Or destructing:
{ results.map(({id, title, description}, index ) => {
return (
<tr key={id}>
<td>{id}</td>
<td>{jtitle}</td>
<td>{description}</td>
</tr>
);
})}
more info: https://flaviocopes.com/react-how-to-loop/

Is there something wrong with my react parent class?

I am practicing using react to build a simple table. Here my table has three columns. (name, job, delete). There is already some data in the table. In the third column, I want to build a button so the user can click and cancel the whole row
I already fixed several bugs but the table still does not show up
const TableBody = props => {
const rows = props.fillTheData.map((row, index) => {
return (
<tr key={index}>
<td>{row.name}</td>
<td>{row.job}</td>
<td><button onClick={() => props.removeCharacter(index)}>Delete</button></td>
</tr>
);
});
return <tbody>{rows}</tbody>;
}
class App extends React.Component {
state = {
character : [ ]
};
removeCharacter = index => {
const {character} = this.state;
this.setState({
character: character.filter((element, i) => {
return i !== index;
})
});
}
handleSubmit = character => {
this.setState({character:[...this.state.character,character]})
}
render() {
return(
<div class= "container">
<Table characters = {this.state.character} removeCharacter = {this.removeCharacter} />
<Form handleSubmit = {this.handleSubmit}/>
</div>
)
}
}
class Form extends React.Component {
constructor (props) {
super( props );
this.initialState = {
name: '',
job: ''
};
this.state = this.initialState;
}
handleChange = event => {
const{name,job} = event.target;
this.setState(
{
[name]: value
}
);
}
submitForm = () => {
this.props.handleSubmit(this.state);
this.setState(this.initialState);
}
render() {
const { name, job } = this.state;
return (
<div class="container2">
<form>
<label>Name</label>
<input
type="text"
name="name"
value={name}
onChange={this.handleChange} />
<label>Job</label>
<input
type="text"
name="job"
value={job}
onChange={this.handleChange}/>
</form>
<input
type="button"
value="Submit"
onClick={this.submitForm} />
</div>
);
}
}
export default Form;
class Table extends React.Component {
render(){
const {characters, removeCharacter} = this.props;
return(
<table>
<TableHeader />
<TableBody fillTheData = {characters} removeCharacter= {removeCharacter} />
</table>
)
}
}
const TableHeader = () => {
return (
<thead>
<tr>
<th>Name</th>
<th>Job</th>
<th>Delete</th>
</tr>
</thead>
);
}
Right now, we have a cool Table component, but the data is being hard-coded. One of the big deals about React is how it handles data, and it does so with properties, referred to as props, and with state. First, we’ll focus on handling data with props.
Then let’s move all that data to an array of objects, as if we were bringing in a JSON-based API. We’ll have to create this array inside our render().
The handleChange method of Form component is declaring a constant named job which doesn't exist in event.target object and it is setting the state with value variable which doesn't exists in that scope.
So change
const{name,job} = event.target;
to
const{name,value} = event.target;
your code will just work fine.

Referencing a react component from its class method

I have a dynamic todo list I would like to add a "highlight" feature to. Each list item renders with markup for the highlight that should show only for the list item clicked.
export class Todo extends Component {
constructor(props) {
super(props);
this.state = {input: '', todos: this.getOldTodo()};
this.selectItem = this.selectItem.bind(this);
}
//shortened
selectItem(i) {
this.setState({selected: i});
if (this.state.selected == i) {
// --- this is the code that needs to change the right list items child's class
???.props.childen[2].className = "active";
// ---
console.log("true")
}
console.log(i);
}
render() {
//markup also shortened
this.state.todos.map((todos, i) => {
return (
//What do I pass to the method here?
<li key={todos.key} className="todo-li-item" onClick={this.selectItem.bind(this, i)}>
<span className="todo-item">{todos.text}</span>
<span onClick={this.deleteItem.bind(this, i)} className="delet-todo">✕</span>
// --- This is the child that needs its class changed when it's parent is clicked
<div id="todo-select" className={"hidden"}>
<span id="todo-select-top"></span>
<span id="todo-select-left"></span>
</div>
</li>
);
})
</ul>
</div>
);
}
}
This is painfully simple and yet so un-obvious as to what I use to do this in react, but hey I'm still learning. Thanks for your time.
You've been quite close. Here's my implementation.
Key takeaway: Don't mutate the state object.
selectItem(idx) {
this.setState(state => {
const todos = [
state.todos.slice(0, idx),
{ ...state.todos[idx], selected: ! state.todos[idx].selected },
state.todos.slice(idx + 1, state.todos.length),
]
return {
...state,
todos,
}
})
}
deleteItem(idx) {
this.setState(state => {
const todos = [...state.todos]
todos.splice(idx, 1)
return {
...state,
todos,
}
})
}
render() {
return (
<div>
<ul>
{this.state.todos.map((todo, idx) => (
<li
key={todo.key}
className={'todo-li-item'}
onClick={this.selectItem.bind(this, idx)}
>
<span className="todo-item">{todo.text}</span>
<span
onClick={this.deleteItem.bind(this, idx)}
className="delete-todo"
>
✕
</span>
<div id="todo-select" className={todo.selected && 'active'}>
<span id="todo-select-top" />
<span id="todo-select-left" />
</div>
</li>
))}
</ul>
</div>
)
}
The list item can be a stateless component, so the onSelect and onDelete become callback functions.
Deleting item with index may get you in trouble, since React will not re-render the entire list every time.
I don't know what's inside getOldTodo, but custructor cannot wait. So it will be null initially, if it's an async function.
There is an implementation using ES6 syntax.
Each list item is stateless:
const ListItem = props => {
const { todo, deleteItem, selectItem } = props;
return (
<li key={todo.key} className="todo-li-item" onClick={selectItem}>
<span className="todo-item">{todos.text}</span>
<span onClick={deleteItem} className="delet-todo">
✕
</span>
clicked
<div id="todo-select" className={'hidden'}>
<span id="todo-select-top" />
<span id="todo-select-left" />
</div>
</li>
);
};
All events are handled by a stateful component:
export class Todo extends Component {
state = {
input: '',
todos: [],
};
async componentDidMount() {
const todos = await this.getOldTodo();
this.setState({ todos });
}
render() {
return (
<div>
{this.state.todos.map(todo => (
<ListItem
todo={todo}
key={todo.key}
selectItem={() => {
this.selectItem(todo);
}}
deleteItem={() => {
this.deleteItem(todo);
}}
/>
))}
</div>
);
}
selectItem = todo => {
const idx = this.state.todos.findIndex(i => i.key === todo.key);
const todos = this.state.todos.slice();
const todo = { ...this.state.todos[idx] };
// change
todos[idx] = todo;
this.setState({
todos
});
}
deleteItem = todo => {
const idx = this.state.todos.findIndex(i => i.key === todo.key);
const todos = this.state.todos.splice(idx, 1);
this.setState({
todos
});
}
getOldTodo = async () => {
//...
}
}
Does this make sense to you?

React - toggling input in a cell of a table

I have a table where I would like it to be possible to click on a cell and for that to toggle an input that allows you to change the data in that cell. At the moment I am just rendering the input straight off the bat but ideally I don't want this. What I would really like is to show the current data in the cells and then when you click on the cell it becomes the input and allows you to edit the data.
Table Component:
import React from 'react';
import TableWithDataHeader from './TableWithDataHeader.jsx';
import Row from './Row.jsx';
import {createRowHistory} from '../../actions/DALIActions';
import TokenStore from '../../stores/TokenStore';
import TableDataStore from '../../stores/TableDataStore';
export default class Table extends React.Component {
state = {data: TableDataStore.getCells().historycells};
handleSubmitEvent = (e) => {
e.preventDefault();
console.log(this.state.data);
let data = this.state.data;
let dataEntriesArray = [];
for (let key in data) {
if (data.hasOwnProperty(key)) {
dataEntriesArray.push({contents: data[key]});
}
}
console.log(dataEntriesArray);
let access_token = TokenStore.getToken();
let row_id = TableDataStore.getRowId();
createRowHistory(access_token, row_id, dataEntriesArray);
};
handleChangeEvent = (value, cell) => {
let newState = this.state.data.slice(0);
console.log(newState);
newState[cell] = value;
console.log(newState);
this.setState({data: newState});
console.log(this.state.data);
};
render() {
let {data} = this.state;
return (
<div>
<div className="row">
<table className="table table-striped table-bordered">
<thead>
<TableWithDataHeader />
</thead>
<tbody>
<Row data={this.state.data} handleSubmitEvent={this.handleSubmitEvent} handleChangeEvent={this.handleChangeEvent} />
</tbody>
</table>
</div>
</div>
);
}
}
Row Component:
import React from 'react';
import TableDataStore from '../../stores/TableDataStore';
export default class Row extends React.Component {
render() {
let cells = this.props.data.map((el, i) => {
return (
<td key={i}>
<input type="text" className="form-control" id={el.id} defaultValue={el.contents} onChange={(e) => {
this.props.handleChangeEvent(e.target.value, i)
}} />
</td>
);
});
cells.push(
<td className="dtable-button" key={this.props.data.length}>
<button className="btn btn-primary" onClick={this.props.handleSubmitEvent}>Submit</button>
</td>
);
return (
<tr id={TableDataStore.getRowId()}>{cells}</tr>
);
}
}
Can I just simply toggle a state that will switch between a default show state (a <div>?) and the input that I already have? Or do I need to do something a little more involved?
Any help would be much appreciated!
Thanks for your time
One approach would be to create a Cell component that renders a div, but when clicked, renders an input instead.
import React from 'react';
class Cell extends React.Component {
constructor(props) {
super(props);
this.state = { editing: false };
}
render() {
const { value, onChange } = this.props;
return this.state.editing ?
<input ref='input' value={value} onChange={e => onChange(e.target.value)} onBlur={() => this.onBlur()} /> :
<div onClick={() => this.onFocus()}>{value}</div>
}
onFocus() {
this.setState({ editing: true }, () => {
this.refs.input.focus();
});
}
onBlur() {
this.setState({ editing: false });
}
}
Then you can create your table rows like this:
let cells = data.map((el, i) => (
<td key={i}>
<Cell
value={el.contents}
onChange={v => { this.props.handleChangeEvent(v, i) }}
/>
</td>
));
You can it like these.
let cells = this.props.data.map((el, i) => {
return (
<td key={i}>
{
(el.editable)?
<input type="text" className="form-control" id={el.id} defaultValue={el.contents} onChange={(e) => {
this.props.handleChangeEvent(e.target.value, i)
}} />:
<div onClick={onCellClick(i){}}>{el.contents}</div>
}
</td>
);
});
editable flag should be toggled using onCellClick(i){} & this.props.handleChangeEvent(e.target.value, i) functions. u should update the approprite state based on the index you have here

React - How can I get a re-render to happen in my table?

I have a table which starts off with data from a store which can then be edited by row. When the editing has been completed, a button is clicked which saves the new values and should present them in the table. However, this is not happening for me. I have been stuck on this for a while now and have tried various things but none of them have worked. I think it may be a problem with the state but I can't figure out how to change it to make it work!
The code I have currently is:
Table:
import React from 'react';
import TableWithDataHeader from './TableWithDataHeader.jsx';
import TableWithDataBody from './TableWithDataBody.jsx';
import TableWithDataRowForm from './TableWithDataRowForm.jsx';
import {updateRowHistory} from '../../actions/DALIActions';
import AppStore from '../../stores/AppStore';
export default class TableWithData extends React.Component {
state = {rows: [], isEditing: false, input: null};
componentDidMount() {
let rows = this.state.rows;
rows.push({id: AppStore.getRowId(), cells: AppStore.getCells().historycells});
this.setState({rows});
console.log(rows);
}
handleEdit = (row) => {
this.setState({isEditing: true});
};
handleInputChange = (newCellValuesArray) => {
let input = this.state.input;
input = newCellValuesArray;
this.setState({input});
};
editStop = (row) => {
this.setState({isEditing: false});
};
handleSubmit = (access_token, row_id) => {
let newCellValuesArray = this.state.input;
updateRowHistory(access_token, row_id, newCellValuesArray);
this.setState({isEditing: false});
};
render() {
let {rows, isEditing, input} = this.state;
return (
<div>
<div className="row">
<table className="table table-striped">
<thead>
<TableWithDataHeader />
</thead>
<tbody>
{rows.map(row => this.state.isEditing ?
<TableWithDataRowForm
key={row.id}
cells={row.cells}
editStop={this.editStop.bind(null, row.id)}
handleSubmit={this.handleSubmit}
handleInputChange={this.handleInputChange}
/>
:
<TableWithDataBody
key={row.id}
cells={row.cells}
handleEdit={this.handleEdit.bind(null, row.id)}
/>
)}
</tbody>
</table>
</div>
</div>
);
}
}
Data table starts with:
import React from 'react';
export default class TableWithDataBody extends React.Component {
state = {cells: this.props.cells};
handleEdit = () => {
this.props.handleEdit();
};
render() {
let {cells} = this.state;
return (
<tr>
{cells.map(cell => {
return <td key={cell.id} className="text-center">{cell.contents}</td>
})}
<td>
<button className="btn btn-primary" onClick={this.handleEdit}><i className="fa fa-pencil"></i>Edit</button>
</td>
</tr>
);
}
}
In-row edit form:
import React from 'react';
import AppStore from '../../stores/AppStore';
export default class TableWithDataRowForm extends React.Component {
state = {cells: this.props.cells, newCellValues: []};
onChange = (e) => {
let newCellValues = this.state.newCellValues;
newCellValues[e.target.id] = e.target.value;
this.setState({newCellValues});
console.log(newCellValues);
let newCellValuesArray = [];
for (let key in newCellValues) {
if (newCellValues.hasOwnProperty(key)) {
newCellValuesArray.push({contents: newCellValues[key]});
}
}
console.log(newCellValuesArray);
this.props.handleInputChange(newCellValuesArray);
};
editStop = () => {
this.props.editStop();
};
handleSubmit = (e) => {
e.preventDefault();
let access_token = AppStore.getToken();
let row_id = AppStore.getRowId();
this.props.handleSubmit(access_token, row_id);
};
render() {
let {cells, newCellValues} = this.state;
return (
<tr>
{cells.map(cell => {
return <td key={cell.id} className="text-center"><input type="text" className="form-control" id={cell.id} defaultValue={cell.contents} onChange={this.onChange} /></td>
})}
<td>
<button className="btn btn-default"><i className="fa fa-ban" onClick={this.editStop}></i>Cancel</button>
<button className="btn btn-success"><i className="fa fa-cloud" onClick={this.handleSubmit}></i>Save</button>
</td>
</tr>
);
}
}
Help with examples would be much appreciated, sorry if the code is a bit of a mess right now!
Thanks for your time
I might be missing something, but I don't see where you're editing the rows state? The change handler just changes the input, and you aren't passing the input down to the data table.
The only time I see rows being set is in componentDidMount, which explains why it's being populated, but not changed.

Resources