I am trying to map a collection of keys to a list of toggle <Switch> components. The state of each toggle switch is coming from redux. When I click on the <Switch>, I expect the toggle state of the component to alternate.
I tried to call setState in the onChange handler on the <Switch>, but its not working as expected. How can I write a function that will toggle the switch?
This is what I have so far:
{Object.keys(all_product).map((item, index) => {
console.log(all_product[item]);
let product = all_product[item];
this.state.activevalue = product.is_active;
let val;
product.is_active == 1 ? (val = true) : (val = false);
this.state = { checked: val };
return (
<tr>
<th scope="row">{product.id}</th>
<td>
<img
src={`/img/${product.code}.jpg`}
alt={product.name}
class="img-fluid dataTableImg"
/>
</td>
<td>{product.name}</td>
<td>{product.brand}</td>
<td>{product.quantity}</td>
<td>{product.unit_price}</td>
<td>
<Switch
onChange={() => {
this.setState({ checked: !val });
}}
checked={this.state.checked}
value={"A"}
/>
</td>
</tr>
);
})}
If I understand your question correctly, you're getting the all_product map from a redux store. The all_products map contains an is_active field on values to determine the initial state of each <Switch> component and from there, the <Switch> is then controlled by state internal to the enclosing component.
Assuming my understanding is correct, one solution might be to read and update the checked prop of each <Switch> component via key/values tracked in the enclosing component:
/* Read checked state */
let isChecked = this.state[ item ]
/* Update (toggle) checked state */
this.setState({ [ item ] : !isChecked })
Integrating that with your code could look like this:
{Object.keys(all_product).map((item, index) => {
/* Get product */
const product = all_product[item];
/* First try to get "checked value" from state */
let isChecked = this.state[ item ]
/* If isChecked fetched from state not present or
then default to the is_active value supplied by
your redux store */
if(typeof isChecked === 'undefined') {
isChecked = product.is_active;
}
return (
<tr>
<th scope="row">{product.id}</th>
<td>
<img
src={`/img/${product.code}.jpg`}
alt={product.name}
class="img-fluid dataTableImg"
/>
</td>
<td>{product.name}</td>
<td>{product.brand}</td>
<td>{product.quantity}</td>
<td>{product.unit_price}</td>
<td>
{ /* Use locally defined isChecked to set checked
prop, and update setState to set negated isChecked
for item by key */}
<Switch
onChange={() => {
this.setState({ [ item ]: !isChecked });
}}
checked={ isChecked }
value={"A"}
/>
</td>
</tr>
);
})}
In your handleChange of Switch, pass the index/id in your callback as well. see this
import React from "react";
import Switch from "react-switch";
import update from "react-update";
class App extends React.Component {
constructor(props) {
super(props);
//Assuming you've state in your component, that might as well be your redux state
this.state = {
all_products: [{ isActive: false, id: 1 }, { isActive: true, id: 2 }]
};
}
handleChange = (activeStatus, itemId) => {
const productIndex = this.state.all_products.findIndex(function(
item,
index
) {
return item.id === itemId;
});
let products = [...this.state.all_products];
let product = { ...products[productIndex] };
product.isActive = activeStatus;
products[productIndex] = product;
//fire a redux action if all_products is stored in redux state
this.setState({ all_products: products });
};
render() {
const that = this;
console.log(this.state.all_products);
return (
<div>
{this.state.all_products.map(function(item, index) {
return (
<Switch
checked={item.isActive}
onChange={isActive => that.handleChange(isActive, item.id)}
/>
);
})}
</div>
);
}
}
export default App;
Related
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.
I have a CheckboxGroup component which takes in an options array prop and generates CheckboxInput components. On page load I make a call to an API which returns an array of pre-selected checkboxes (delivered to the value prop). Depending on the logged in user, this call can return an empty array or a selection of previously selected checkbox options.
The following code successfully takes the response of the API call and sets the relevant checkboxes to 'checked'. The issue I have is that this code doesn't allow me to make changes to the checkboxes after page load (clicking a checkboxes has no effect).
I think there is also some disconnect between the initial selectedCheckboxes state and the value of the API call but I read that setting props as initial state is an anti-pattern (e.g. selectedCheckboxes: props.value,).
export default class CheckboxGroup extends Component {
constructor(props) {
super(props);
this.state = {
selectedCheckboxes: [],
};
}
addCheckboxToSelected = (id) => {
if (this.state.selectedCheckboxes.includes(id)) {
// Remove checkbox from array and update state
const filteredArray = this.state.selectedCheckboxes.filter(item => item !== id);
this.setState({ selectedCheckboxes: filteredArray });
} else {
// Add checkbox to array and update state
this.setState({ selectedCheckboxes: this.state.selectedCheckboxes.concat(id) });
}
}
checkIfSelected = (checkboxValue) => {
const preSelectedCheckboxes = this.props.value;
let selectedBool = false;
preSelectedCheckboxes.some(function(object) {
if (object.id === checkboxValue) {
selectedBool = true;
}
return false;
});
return selectedBool;
}
render() {
const { label, name, options } = this.props;
return (
<div className="form-group form-inline">
<span className="checkboxgroup-heading">{label}</span>
<div className="form-group-container">
{options.map(object => (
<CheckboxInput
key={object.value}
name={name}
label={object.label}
onChange={this.addCheckboxToSelected}
value={object.value}
checked={this.checkIfSelected(object.value)}
/>
))}
</div>
</div>
);
}
}
This is the stateless CheckboxInput component
const CheckboxInput = ({ name, label, onChange, value, checked }) => {
return (
<div className="field form-group filter-input">
<input
type="checkbox"
id={value}
name={name}
value={value}
onChange={() => onChange(value)}
checked={checked}
/>
<label htmlFor={value} className="form-control">{label}</label>
</div>
);
};
Check the following code snippet. This might help. Let me know if you have questions.
const CheckboxField = ({checked, onChange}) => {
return (
<input type="checkbox" checked={checked} onChange={ev => onChange(ev.target.checked)} />
);
};
class App extends React.Component {
constructor() {
super();
this.state = {
options: [{id: "1", checked: true}, {id: "2", checked: false}]
};
}
handleCheckboxChange(checked, option) {
const {options} = this.state;
var cOptions = [...options];
for(var i in cOptions) {
if(cOptions[i].id == option.id) {
cOptions[i].checked = checked;
}
}
this.setState({
options: cOptions
}, () => console.log(options));
}
render() {
const {options} = this.state;
return (
<div>
{
options.map(option => {
return (
<CheckboxField key={option.id} checked={option.checked} onChange={value => this.handleCheckboxChange(value, option)} />
)
})
}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
I created a basic interface using checkboxes that used a react design pattern that has served me well before and that I thought worked well - namely lifting up state and passing down props to UI components. My checkbox components are passed a value(a metric), an state-changing method, and a boolean for checked. The problem is that the checkboxes do not update immediately, even though you can see them updating in the React dev tools. They only update on the next click, as in when another checkbox is checked. Here is the code:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
metricsSelected: []
}
this.selectMetric = this.selectMetric.bind(this)
}
selectMetric(metric) {
const metricsSelected = this.state.metricsSelected
const index = metricsSelected.indexOf(metric)
if (index !== -1){
metricsSelected.splice(index, 1)
}
else {
metricsSelected.push(metric)
}
this.setState({
metricsSelected,
});
}
render() {
return (
<div>
<Sidebar
metricsSelected={this.state.metricsSelected}
selectMetric={this.selectMetric}/>
<SomethingElse/>
</div>
)
}
}
const SomethingElse = () => (<div><h2>Something Else </h2></div>)
const Sidebar = ({ metricsSelected, selectMetric }) => {
const metrics = ['first thing', 'second thing', 'third thing']
return (
<div>
<h3>Select Metrics</h3>
{ metrics.map( (metric, i) =>
<Checkbox
key={i}
metric={metric}
selectMetric={selectMetric}
checked={metricsSelected.includes(metric)}/>
)}
</div>
)
}
const Checkbox = ({ metric, selectMetric, checked }) => {
const onChange = e => {
e.preventDefault()
selectMetric(e.target.value)
}
return (
<ul>
<li>{metric}</li>
<li><input
type='checkbox'
value={metric}
checked={checked}
onChange={onChange} /></li>
</ul>
)
}
I've read pretty much everything I can get my hands on about checkboxes for react and most of the applications of the checkbox are doing something different from what I want to do. I've tried adding state to the Checkbox component, but that didn't seem to help, since the checked value still needs to come in from elsewhere. I thought react components rerendered when the props changed. What gives?
Here's a codepen: https://codepen.io/matsad/pen/QpexdM
Here is a working version: http://codepen.io/TLadd/pen/oWvOad?editors=1111
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
metricsSelected: {}
}
this.selectMetric = this.selectMetric.bind(this)
}
selectMetric(metric) {
this.setState(({ metricsSelected }) => ({
metricsSelected: {
...metricsSelected,
[metric]: !metricsSelected[metric]
}
}))
}
render() {
return (
<div>
<Sidebar
metricsSelected={this.state.metricsSelected}
selectMetric={this.selectMetric}/>
<SomethingElse/>
</div>
)
}
}
const SomethingElse = () => (<div><h2>Something Else </h2></div>)
const Sidebar = ({ metricsSelected, selectMetric }) => {
const metrics = ['first thing', 'second thing', 'third thing']
return (
<div>
<h3>Select Metrics</h3>
{ metrics.map( (metric, i) =>
<Checkbox
key={i}
metric={metric}
selectMetric={selectMetric}
checked={Boolean(metricsSelected[metric])}/>
)}
</div>
)
}
const Checkbox = ({ metric, selectMetric, checked }) => {
return (
<ul>
<li>{metric}</li>
<li>
<input
type='checkbox'
name={metric}
checked={checked}
onChange={() => selectMetric(metric)}
/>
</li>
</ul>
)
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
The couple of things that were causing issues were that you were mutating state in selectMetric, and your checkbox input's onChange function is using e.target.value instead of e.target.checked.
I changed the metricsSelected state to be an object, since I think it makes the management of it quite a bit easier.
I'm trying to create rows of inputs that updates the state of my application. The app is currently only two components, TimeCalc and ItemsList. State is stored in TimeCalc, and ItemsList handles the conditional render (either show state, or show rows of input.
I have managed to fetch the edited and updated object, but I'm struggling to replace the updated object with the correct object in state. The objects contain the props _id, title, start and end, and preferably I'd like to search for the matching _id, and replace the entire object in the state. But seeing as the _id prop is a sibling prop of the other props, I'm not sure how to do this.
Here is TimeCalc:
import React from 'react';
import ItemsList from '../components/ItemsList';
const items = [
{
_id: '112233',
title: 'M1',
start: 900,
end: 1800
},
{
_id: '223344',
title: 'M2',
start: 1000,
end: 1900
}
];
export default class TimeCalc extends React.Component {
state = {
items
}
handleUpdate = (update) => {
}
render = () => {
return (
<div class="timeCalc flex center">
<ItemsList items={this.state.items} handleUpdate={this.handleUpdate}/>
</div>
)
}
}
And here is ItemsList:
import React from 'react';
export default class ItemsList extends React.Component {
state = {
editing: null
}
toggleEditing = (itemId) => {
this.setState({
editing: itemId
})
}
handleEditItem = () => {
let itemId = this.state.editing;
this.handleItemsUpdate({
_id: itemId,
title: this.refs[`title_${itemId}`].value,
start: this.refs[`start_${itemId}`].value,
end: this.refs[`end_${itemId}`].value,
})
}
handleItemsUpdate = (update) => {
console.log(update);
this.props.handleUpdate(update);
this.setState( { editing: null } );
}
renderItemOrEditField = (item) => {
if(this.state.editing === item._id) {
return <li key={`editing-${item._id} `} class="list-item flex row">
<input
onKeyDown={this.handleEditField}
type="text"
class="form-input"
ref={`title_${item._id}`}
name="title"
defaultValue={item.title}
/>
<input
onKeyDown={this.handleEditField}
type="text"
class="form-input"
ref={`start_${item._id}`}
name="start"
defaultValue={item.start}
/>
<input
onKeyDown={this.handleEditField}
type="text"
class="form-input"
ref={`end_${item._id}`}
name="end"
defaultValue={item.end}
/>
<button onClick={this.handleEditItem} label="Update Item"/>
</li>
} else {
return <li
onClick = {this.toggleEditing.bind(null, item._id)}
key = {item._id}
class = "list-position">
{` ${item.title} & ${item.start} && ${item.end} && ${item._id}`}
</li>
}
}
render = () => {
return (
<ul class="itemsList">
{this.props.items.map((item) => {
return this.renderItemOrEditField(item);
})}
</ul>
)
}
}
I'm trying to recreate MeteorChef's "Click to Edit fields in React", but he's storing the state in a Meteor way.
I'd suggest that you move all of the state to the parent component. Keeping ItemsList stateless separates your concerns more neatly and makes the code much simpler to reason about. In this scenario, all ItemsList needs to be concerned with is presenting data based on the state stored in the parent. In your TimeCalc component, add an "editing" key to the state object, and add a method to handle updates from ItemsList:
state = {
items,
editing: null,
}
handleUpdate = (editing) => {
this.setState({ editing });
}
Then when rendering the child component, pass this handler function, the items array, and the "editing" key as props:
<ItemsList items={this.state.items} editing={this.state.editing} handleUpdate={this.handleUpdate}/>
Now ItemsList can become a stateless functional component, like this:
import React from 'react';
const renderItemOrEditField = (item, editing, handleUpdate) => (
{editing === item._id ? (
<li key={item._id} className="list-item flex row">
<input onKeyDown={handleUpdate(item._id}
// The rest of your content to render if item is being edited
) : (
// content to be rendered if item isn't being edited goes here
)}
);
export default const ItemsList = (props) => (
<ul className="itemsList">
{props.items.map((item) => {
return renderItemOrEditField(item, props.editing, props.handleUpdate);
})}
</ul>
);
This strategy of using "smart" container components that manage state and business logic and "dumb" presentational components can go a long way towards keeping your application more maintainable and less fragile. Hope this helps!
I have a table with dynamically generated column headers and rows, a user can enter in data to the rows and when they click save the data should be saved into that row but at the moment it isn't saving the values to the table but it is saving them on my database. Ideally I would like it so that when the save button is clicked the data will be saved to the row and is then viewable in that row (if that makes any sense).
Here is the code I am working with (I know it is a mess at the moment!):
Code for data input form:
import React from 'react';
import AppStore from '../../stores/AppStore';
export default class RowForm extends React.Component {
state = {dataEntries: []};
onChange = (event, element) => {
let dataEntries = this.state.dataEntries;
dataEntries[element] = event.target.value;
this.setState({dataEntries});
};
editStop = () => {
this.props.editStop();
};
handleSubmit = (e) => {
e.preventDefault();
let access_token = AppStore.getToken();
let id = AppStore.getTable().id;
let dataEntries = this.state.dataEntries;
let dataEntriesArray = [];
for (let key in dataEntries) {
if (dataEntries.hasOwnProperty(key)) {
dataEntriesArray.push({contents: dataEntries[key]});
}
}
this.props.handleSubmit(access_token, id, dataEntriesArray);
};
componentDidMount() {
let nameArray = AppStore.getTable().columns.map((obj) => {
return obj.name;
});
let dataEntries = nameArray.reduce((obj, name) => {
obj[name] = null;
return obj;
}, {});
this.setState({dataEntries});
}
render() {
let {dataEntries} = this.state;
return (
<tr>
{Object.keys(dataEntries).map((element) => {
return (
<td key={element}><input type="text" className="form-control" id={element} placeholder="enter data" value={dataEntries[element]} onChange={event => this.onChange(event, element)} /></td>
);
})}
<td>
<button className="btn btn-default" onClick={this.editStop}><i className="fa fa-ban"></i>Cancel</button>
<button className="btn btn-success" onClick={this.handleSubmit}><i className="fa fa-check"></i>Save</button>
</td>
</tr>
);
}
After the data has been entered and submitted (it is an array of objects like dataEntriesArray = [{contents: "value"}, {contents: "value"}, {contents: "value"}, {contents: "value"}].
And here is how I am rendering the table (this is where the problem is I think):
import React from 'react';
import TableHeader from './TableHeader.jsx';
import RowForm from './RowForm.jsx';
import {createRow} from '../../actions/DALIActions';
import AppStore from '../../stores/AppStore';
export default class Table extends React.Component {
state = {rows: [], isNew: false, isEditing: false};
handleAddRowClickEvent = () => {
let rows = this.state.rows;
rows.push({isNew: true});
this.setState({rows: rows, isEditing: false});
};
handleEdit = (row) => {
this.setState({isEditing: true});
};
editStop = () => {
this.setState({isEditing: false});
};
handleSubmit = (access_token, id, dataEntriesArray) => {
createRow(access_token, id, dataEntriesArray);
};
render() {
let {rows, isNew, isEditing} = this.state;
let headerArray = AppStore.getTable().columns;
return (
<div>
<div className="row" id="table-row">
<table className="table table-striped">
<thead>
<TableHeader />
</thead>
<tbody>
{rows.map((row, index) => this.state.isEditing ?
<RowForm formKey={index} key={index} editStop={this.editStop} handleSubmit={this.handleSubmit} /> :
<tr key={index}>
{headerArray.map((element, index) => {
return (
<td key={index} id={element.id}></td>
);
})}
<td>
<button className="btn btn-primary" onClick={this.handleEdit.bind(this, row)}><i className="fa fa-pencil"></i>Edit</button>
</td>
</tr>)}
</tbody>
</table>
</div>
<div className="row">
<div className="col-xs-12 de-button">
<button type="button" className="btn btn-success" onClick={this.handleAddRowClickEvent}>Add Row</button>
</div>
</div>
</div>
);
}
}
I am using flux at the moment, and would ideally like to keep using it for now (I know about redux but I would ideally like to get it working in flux before I start refactoring my code). I suspect it is a problem with the way I am rendering my table.
Any help would be much appreciated, especially examples!
Thanks for you time!
It looks like you probably want to extract your table data into your store, your UI child elements trigger change events, your store then updates its data and trigger change events which your parent component can listen for and update.
Something like the following simplified example, which mutates array elements:
class Store extends EventEmitter {
constructor() {
super()
this.data = [ 'a', 'b', 'c' ]
}
onChange() {
this.emit( 'update', this.data )
}
mutate( index, value ) {
this.data[ index ] = value
this.onChange()
}
}
var store = new Store()
class ChildComponent extends React.Component {
constructor( props ) {
super( props )
}
// You probably want to use a dispatcher rather than directly accessing the store
onClick = event => {
store.mutate( this.props.index, this.props.value + 'Z' )
}
render() {
return <button onClick={ this.onClick }>{ this.props.value }</button>
}
}
class ParentComponent extends React.Component {
constructor( props ) {
super( props )
// You probably want to be smarter about initially populating state
this.state = {
data: store.data
}
}
componentWillMount() {
store.on( 'update', data => this.setState({ data: data }) )
}
render() {
let cells = this.state.data.map( ( value, index ) => <ChildComponent index={ index } value={ value } /> )
return (
<div>
{ cells }
</div>
)
}
}
For brevity child components here directly tell the store to change values, you'd probably want to dispatch messages/actions and have the store decide how to respond, the key is simply that the store data is passed to the parent component, which updates its state and triggers a re-render.
The flow here is that essentially the UI is dumb, it simply renders the data that it gathers from the store and dispatches a message to tell the store to update/mutate when a user action is detected (in this case a button press but it sound like you'll need an input of some sort), when the data in the store changes it emits (or could use a dispatcher also) a change event which forces the UI to re-render the new state. As child components are re-rendered at this stage they are populated with the new data state, ensuring that your UI remains consistent.