Adding clicked items to new array in React - javascript

I am making API calls and rendering different components within an object. One of those is illustrated below:
class Bases extends Component {
constructor() {
super();
this.state = {
'basesObject': {}
}
}
componentDidMount() {
this.getBases();
}
getBases() {
fetch('http://localhost:4000/cupcakes/bases')
.then(results => results.json())
.then(results => this.setState({'basesObject': results}))
}
render() {
let {basesObject} = this.state;
let {bases} = basesObject;
console.log(bases);
//FALSY values: undefined, null, NaN, 0, false, ""
return (
<div>
{bases && bases.map(item =>
<button key={item.key} className="boxes">
{/* <p>{item.key}</p> */}
<p>{item.name}</p>
<p>${item.price}.00</p>
{/* <p>{item.ingredients}</p> */}
</button>
)}
</div>
)
}
}
The above renders a set of buttons. All my components look basically the same.
I render my components here:
class App extends Component {
state = {
ordersArray: []
}
render() {
return (
<div>
<h1>Bases</h1>
<Bases />
<h1>Frostings</h1>
<Frostings />
<h1>Toppings</h1>
<Toppings />
</div>
);
}
}
I need to figure out the simplest way to, when a button is clicked by the user, add the key of each clicked element to a new array and I am not sure where to start. The user must select one of each, but is allowed to select as many toppings as they want.

Try this
We can use the same component for all categories. All the data is handled by the parent (stateless component).
function Buttons({ list, handleClick }) {
return (
<div>
{list.map(({ key, name, price, isSelected }) => (
<button
className={isSelected ? "active" : ""}
key={key}
onClick={() => handleClick(key)}
>
<span>{name}</span>
<span>${price}</span>
</button>
))}
</div>
);
}
Fetch data in App component, pass the data and handleClick method into Buttons.
class App extends Component {
state = {
basesArray: [],
toppingsArray: []
};
componentDidMount() {
// Get bases and toppings list, and add isSelected attribute with default value false
this.setState({
basesArray: [
{ key: "bases1", name: "bases1", price: 1, isSelected: false },
{ key: "bases2", name: "bases2", price: 2, isSelected: false },
{ key: "bases3", name: "bases3", price: 3, isSelected: false }
],
toppingsArray: [
{ key: "topping1", name: "topping1", price: 1, isSelected: false },
{ key: "topping2", name: "topping2", price: 2, isSelected: false },
{ key: "topping3", name: "topping3", price: 3, isSelected: false }
]
});
}
// for single selected category
handleSingleSelected = type => key => {
this.setState(state => ({
[type]: state[type].map(item => ({
...item,
isSelected: item.key === key
}))
}));
};
// for multiple selected category
handleMultiSelected = type => key => {
this.setState(state => ({
[type]: state[type].map(item => {
if (item.key === key) {
return {
...item,
isSelected: !item.isSelected
};
}
return item;
})
}));
};
// get final selected item
handleSubmit = () => {
const { basesArray, toppingsArray } = this.state;
const selectedBases = basesArray.filter(({ isSelected }) => isSelected);
const selectedToppings = toppingsArray.filter(({ isSelected }) => isSelected);
// submit the result here
}
render() {
const { basesArray, toppingsArray } = this.state;
return (
<div>
<h1>Bases</h1>
<Buttons
list={basesArray}
handleClick={this.handleSingleSelected("basesArray")}
/>
<h1>Toppings</h1>
<Buttons
list={toppingsArray}
handleClick={this.handleMultiSelected("toppingsArray")}
/>
</div>
);
}
}
export default App;
CSS
button {
margin: 5px;
}
button.active {
background: lightblue;
}

I think the following example would be a good start for your case.
Define a handleClick function where you can set state with setState as the following:
handleClick(item) {
this.setState(prevState => {
return {
...prevState,
clickedItems: [...prevState.clickedItems, item.key]
};
});
}
Create an array called clickedItems in constructor for state and bind handleClick:
constructor() {
super();
this.state = {
basesObject: {},
clickedItems: [],
}
this.handleClick = this.handleClick.bind(this);
}
You need to add a onClick={() => handleClick(item)} handler for onClick:
<button key={item.key} className="boxes" onClick={() => handleClick(item)}>
{/* <p>{item.key}</p> */}
<p>{item.name}</p>
<p>${item.price}.00</p>
{/* <p>{item.ingredients}</p> */}
</button>
I hope that helps!

Related

Checkbox doesn't change its value on click in React todo application

Please help me with this I don't understand exactly where I doing wrong. So when I click on the checkbox values are not changing(if it's by default true when I click on the click it should the false). For that in onChange in todoList component I am calling handleClick function there I change the todo.completed value(basically toggling the values).
In App.js inside the handleClick method When do console.log(todo) before returning from the map function value is toggling fine, but it is not updated in the updatedTodo.
App.js
import TodosData from "./todoData";
import TodoList from "./todoList";
import "./styles.css";
class App extends Component {
constructor() {
super();
this.state = {
todos: TodosData
}
this.handleChange = this.handleChange.bind(this)
}
handleChange(id) {
this.setState(prevState => {
const updatedTodo = prevState.todos.map(todo => {
// console.log(updatedTodo);
if(todo.id === id) {
console.log("before the opt "+todo.completed);
todo.completed = !todo.completed
console.log("after the opt "+todo.completed);
}
//console.log(todo);
return todo;
})
console.log(updatedTodo);
return {
todos: updatedTodo
}
});
}
render() {
const todoDataComponents = this.state.todos.map(item => {
return <TodoList key = {item.id} item = {item} handleChange = {this.handleChange} />
})
return (
<div className="todo-list">{todoDataComponents}</div>
);
}
}
export default App;
todoList.jsx
class TodoList extends Component {
constructor(props) {
super(props);
this.state = {}
}
render() {
// console.log(this.props)
return (
<div className="todo-item">
<input
type="checkbox"
checked={this.props.item.completed}
onChange = {() => this.props.handleChange(this.props.item.id)}
/>
<p>{this.props.item.text}</p>
</div>
);
}
}
export default TodoList;
todoData.js
const TodosData = [
{ id: 1, text: "Coding", completed: true },
{ id: 2, text: "Exercise", completed: false },
{ id: 3, text: "Learning", completed: true },
{ id: 4, text: "Programming", completed: true },
{ id: 5, text: "inspire", completed: false },
{ id: 6, text: "motivation", completed: true }
];
export default TodosData;
I can't see any error in your handleChange() method, it should work fine. However, I've just updated some of your code which you can test below.
I changed the name of your TodoList as it's not really a list but an item. I also changed it to a functional component as it's only presentational, there is no need to have its own state. Instead of adding a p tag after the input, you should use a label to make it accessible.
I haven't really changed anything inside your handleChange() method, only removed the console.logs and it works as expected.
Update: You're using React.StrictMode, where React renders everything twice on dev. As your handleChange() runs twice, it sets the clicked todo's completed state twice, making it to set back to its original state. So if it's false on first render it sets to true on click, but it's rendered again and on the second one it's back to false. You won't notice it as it's pretty fast.
To avoid it, you need to avoid mutating anything. So I've updated your onChange handler, it returns a new object if the completed property is changed.
Feel free to run the code snippet below and click on the checkboxes or the item texts.
const TodosData = [
{ id: 1, text: 'Coding', completed: true },
{ id: 2, text: 'Exercise', completed: false },
{ id: 3, text: 'Learning', completed: true },
{ id: 4, text: 'Programming', completed: true },
{ id: 5, text: 'Inspire', completed: false },
{ id: 6, text: 'Motivation', completed: true },
];
function TodoItem(props) {
const { handleChange, item } = props;
return (
<div className="todo-item">
<input
type="checkbox"
id={`item-${item.id}`}
checked={item.completed}
onChange={() => handleChange(item.id)}
/>
<label htmlFor={`item-${item.id}`}>{item.text}</label>
</div>
);
}
class App extends React.Component {
constructor() {
super();
this.state = {
todos: TodosData,
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(id) {
this.setState((prevState) => {
const updatedTodo = prevState.todos.map((todo) => {
return todo.id === id ? {
...todo,
completed: !todo.completed,
} : todo;
});
return {
todos: updatedTodo,
};
});
}
render() {
const { todos } = this.state;
return (
<div className="todo-list">
{todos.map((item) => (
<TodoItem
key={item.id}
item={item}
handleChange={this.handleChange}
/>
))}
</div>
);
}
}
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
body {
font-family: sans-serif;
line-height: 1.6;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Instead of using an additional variable, you can do it in one line. Can u see if this works for you.
handleChange(id) {
this.setState(prevState => prevState.map(todo => todo.id === id ? {...todo, completed: !todo.completed} : todo )
}

is there any solution for my react state problem?

So I am a beginner in react. I was messing around with a to-do list and encountered a state related problem.
//main todolist
import React from 'react'
import './todolist.css'
import Itemtodo from './Itemtodo'
class Todolist extends React.Component{
constructor(){
super()
this.state = {
todos: [
{
id:1,
text: 'html',
completed: true
},
{
id:2,
text: 'css',
completed: true
},
{
id:3,
text: 'js',
completed: false
},
{
id:4,
text: 'react',
completed: false
},
{
id:5,
text: 'review',
completed: false
}
]
}
}
// i think it came from this method
ev = (id) =>{
this.setState((prev) =>{
const newarr = prev.todos.map((data) =>{
if(data.id === id){
data.completed = !data.completed
}
return data
})
return {
todos:newarr
}
})
}
render(){
const array = this.state.todos.map(data => <Itemtodo key={data.id} todo={data.text} ic={data.completed} ev={this.ev} id={data.id}/>)
return (
<div className="todolist">
{array}
</div>
)
}
}
export default Todolist
// item todo
import React, { Component } from 'react'
import './itemtodo.css'
export class Itemtodo extends Component {
render() {
return (
<div className ='itemtodo'>
<input type='checkbox' checked={this.props.ic} onChange={() => this.props.ev(this.props.id)}/>
<p>{this.props.todo}</p>
</div>
)
}
}
export default Itemtodo
I REALLY think that the problem was from "main todolist" because if I changed the "ev" method to do "checked every checkbox with a click" like this, it worked
// i think it came from this method
ev = (id) =>{
this.setState((prev) =>{
const newarr = prev.todos.map((data) =>{
data.completed = true
return data
})
return {
todos:newarr
}
}) //set state ends
}
I did some experiment by console loging the "newar" and it did not change. So I think it's because of the
data.completed = !data.completed
did not work please help me! Thank you
Hi Please check this example. It is working fine.
Todolist Component
import React, {Component} from 'react'
class Todolist extends React.Component {
constructor(props) {
super(props);
this.state = {
todos: [
{id: 1, text: 'html', completed: true},
{id: 2, text: 'css', completed: true},
{id: 3, text: 'js', completed: false},
{id: 4, text: 'react', completed: false},
{id: 5, text: 'review', completed: false}
]
}
}
// i think it came from this method
ev = (id, changedValue) => {
this.setState((prev) => {
let item = prev.todos.filter((data) => data.id === id)[0];
item.completed = changedValue;
return{
todos: prev.todos
};
})
};
render() {
const array = this.state.todos.map(data => <Itemtodo key={data.id} todo={data.text} ic={data.completed}
ev={this.ev} id={data.id}/>);
return (
<div className="todolist">
{array}
</div>
)
}
}
export default Todolist
Itemtodo Component
class Itemtodo extends Component {
render() {
return (
<div className='itemtodo'>
<input type='checkbox' checked={this.props.ic} onChange={() => this.props.ev(this.props.id, !this.props.ic)}/>
{this.props.todo}
</div>
)
}
}
Here is a working example of a todo list that has TodoItem as a pure component, because toggleCompleted will not mutate this will work (you have to click on the todo to toggle completed).
//using React.memo will make TodoItem a pure
// component and won't re render if props didn't change
const TodoItem = React.memo(function TodoItem({
todo: { id, name, completed },
toggleCompleted,
}) {
const r = React.useRef(0);
r.current++;
return (
<li
onClick={toggleCompleted(id)}
style={{ cursor: 'pointer' }}
>
rendered:{r.current}, {name}, completed:
{completed.toString()}
</li>
);
});
function TodoList({ todos, toggleCompleted }) {
return (
<ul>
{todos.map((todo) => (
<TodoItem
key={todo.id}
todo={todo}
toggleCompleted={toggleCompleted}
/>
))}
</ul>
);
}
function App() {
const [todos, setTodos] = React.useState([
{ id: 1, name: '1', completed: false },
{ id: 2, name: '2', completed: false },
]);
//use callback so the handler never changes
const toggleCompleted = React.useCallback(
(id) => () =>
setTodos((todos) =>
//map todos into a new array of todos
todos.map(
(todo) =>
todo.id === id
? //shallow copy todo and toggle completed
{ ...todo, completed: !todo.completed }
: todo //just return the todo
)
),
[]
);
return (
<TodoList
todos={todos}
toggleCompleted={toggleCompleted}
/>
);
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
And here is a broken example where I will mutate the todo, even though you changed the data you did not change the reference of the todo item so React won't re render pure TodoItem component.
//using React.memo will make TodoItem a pure
// component and won't re render if props didn't change
const TodoItem = React.memo(function TodoItem({
todo: { id, name, completed },
toggleCompleted,
}) {
const r = React.useRef(0);
r.current++;
return (
<li
onClick={toggleCompleted(id)}
style={{ cursor: 'pointer' }}
>
rendered:{r.current}, {name}, completed:
{completed.toString()}
</li>
);
});
function TodoList({ todos, toggleCompleted }) {
return (
<ul>
{todos.map((todo) => (
<TodoItem
key={todo.id}
todo={todo}
toggleCompleted={toggleCompleted}
/>
))}
</ul>
);
}
function App() {
const [todos, setTodos] = React.useState([
{ id: 1, name: '1', completed: false },
{ id: 2, name: '2', completed: false },
]);
//use callback so the handler never changes
const toggleCompleted = React.useCallback(
(id) => () =>
setTodos((todos) =>
//map todos into a new array of todos
todos.map((todo) => {
if (todo.id === id) {
todo.completed = !todo.completed;
console.log('changed todo:', todo);
}
//it is always returning the same todo that it
// got passed into, only mutates the one that
// needs to be toggled but that todo is still the
// same
return todo;
})
),
[]
);
return (
<TodoList
todos={todos}
toggleCompleted={toggleCompleted}
/>
);
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Checkbox does not want to check or uncheck after clicking

Iterates on the todos array. Objects inside have the isChecked property. If isChecked === true marks the checkbox, ifisChecked === false the checkbox is uncheckbox. When I click on the checkbox. I can't mark or uncheckbox
Demo here: https://stackblitz.com/edit/react-ds9rsd
class App extends Component {
constructor() {
super();
this.state = {
todos: [
{
name:'A',
id: 1,
isChecked: true
},
{
name:'B',
id: 2,
isChecked: false
},
{
name:'C',
id: 3,
isChecked: true
}
]
};
}
checked = (e) => {
console.log(e.target.checked)
}
render() {
return (
<div>
{this.state.todos.map((todo, index) => {
return <input type="checkbox" checked={todo.isChecked} onChange={(e) => this.checked(e)}/>
})}
</div>
);
}
}
In checked() function you are just logging the value. Instead of that you need to do setState() to save new state.
A possibile solution could be updating the render function like this:
render() {
return (
<div>
{this.state.todos.map((todo, index) => {
return <input label={todo.name} type="checkbox" checked={todo.isChecked}
onChange={(e) => this.checked(todo)}/>
})}
</div>
);
}
and the checked method like this:
checked = (e) => {
this.setState(state => {
const list = state.todos.map((item) => {
if (item.name === e.name) {
return item.isChecked = !item.isChecked;
} else {
return item;
}
});
return {
list,
};
});
}
You will need to add a function and call it for each checkbox
import React, { Component } from "react";
import { render } from "react-dom";
import Hello from "./Hello";
import "./style.css";
class App extends Component {
constructor() {
super();
this.state = {
todos: [
{
name: "A",
id: 1,
isChecked: true
},
{
name: "B",
id: 2,
isChecked: false
},
{
name: "C",
id: 3,
isChecked: true
}
]
};
}
checked = index => {
/** get the current state */
let _todos = this.state.todos;
/** assign opposite value: true to false or false to true */
_todos[index].isChecked = !_todos[index].isChecked;
/** update state */
this.setState({ todos: _todos });
};
render() {
return (
<div>
{this.state.todos.map((todo, index) => {
/** call the function passing the index value */
return (
<input
label={todo.name}
type="checkbox"
checked={todo.isChecked}
onChange={this.checked.bind(this, index)}
/>
);
})}
</div>
);
}
}
render(<App />, document.getElementById("root"));

Prevent child's state from reset after parent component state changes also get the values of all child components:ReactJS+ Typescript

I am a bit new to react and I am stuck in this situation where I am implementing custom dropdown filter for a table in react. I have set of dropdown values for each column and there is a Apply button.
I have maintained a child component for this which takes in drop down values and sends the selected one's back to parent. Then I call a back-end API that gives me filtered data which in-turn sets parents state . The problem here is the checkbox values inside dropdown is lost after I get the data and set the parent state.
Each child components has as a set of checkboxes , an Apply and a clear button. So on click of Apply , I have to send the checked one's to the parent or in general whichever the checked one's without losing the previous content.
I am unable to understand why am I losing the checkbox values?
It would be of great help if someone can help me out with this
Sand box: https://codesandbox.io/s/nervous-elgamal-0zztb
I have added the sandbox link with proper comments. Please have a look. I am a bit new to react.
Help would be really appreciated
Parent
import * as React from "react";
import { render } from "react-dom";
import ReactTable from "react-table";
import "./styles.css";
import "react-table/react-table.css";
import Child from "./Child";
interface IState {
data: {}[];
columns: {}[];
selectedValues: {};
optionsForColumns: {};
}
interface IProps {}
export default class App extends React.Component<IProps, IState> {
// Here I have hardcoded the values, but data and optionsForColumns comes from the backend and it is set inside componentDidMount
constructor(props: any) {
super(props);
this.state = {
data: [
{ firstName: "Jack", status: "Submitted", age: "14" },
{ firstName: "Simon", status: "Pending", age: "15" }
],
selectedValues: {},
columns: [],
optionsForColumns: {
firstName: [{ Jack: "4" }, { Simon: "5" }],
status: [{ Submitted: "5" }, { Pending: "7" }]
}
};
}
// Get the values for checkboxes that will be sent to child
getValuesFromKey = (key: any) => {
let data: any = this.state.optionsForColumns[key];
let result = data.map((value: any) => {
let keys = Object.keys(value);
return {
field: keys[0],
checked: false
};
});
return result;
};
// Get the consolidated values from child and then pass it for server side filtering
handleFilter = (fieldName: any, selectedValue: any, modifiedObj: any) =>
{
this.setState(
{
selectedValues: {
...this.state.selectedValues,
[fieldName]: selectedValue
}
},
() => this.handleColumnFilter(this.state.selectedValues)
);
};
// Function that will make server call based on the checked values from child
handleColumnFilter = (values: any) => {
// server side code for filtering
// After this checkbox content is lost
};
// Function where I configure the columns array for the table . (Also data and column fiter values will be set here, in this case I have hardcoded inside constructor)
componentDidMount() {
let columns = [
{
Header: () => (
<div>
<div>
<Child
key="firstName"
name="firstName"
options={this.getValuesFromKey("firstName")}
handleFilter={this.handleFilter}
/>
</div>
<span>First Name</span>
</div>
),
accessor: "firstName"
},
{
Header: () => (
<div>
<div>
<Child
key="status"
name="status"
options={this.getValuesFromKey("status")}
handleFilter={this.handleFilter}
/>
</div>
<span>Status</span>
</div>
),
accessor: "status",
},
{
Header: "Age",
accessor: "age"
}
];
this.setState({ columns });
}
//Rendering the data table
render() {
const { data, columns } = this.state;
return (
<div>
<ReactTable
data={data}
columns={columns}
/>
</div>
);
}
}
const rootElement = document.getElementById("root");
render(<App />, rootElement);
Child
import * as React from "react";
import { Button, Checkbox, Icon } from "semantic-ui-react";
interface IProps {
options: any;
name: string;
handleFilter(val1: any, val2: any, val3: void): void;
}
interface IState {
showList: boolean;
selected: [];
checkboxOptions: any;
}
export default class Child extends React.Component<IProps, IState> {
constructor(props: any) {
super(props);
this.state = {
selected: [],
showList: false,
checkboxOptions: this.props.options.map((option: any) => option.checked)
};
}
// Checkbox change handler
handleValueChange = (event: React.FormEvent<HTMLInputElement>, data: any) => {
const i = this.props.options.findIndex(
(item: any) => item.field === data.name
);
const optionsArr = this.state.checkboxOptions.map(
(prevState: any, si: any) => (si === i ? !prevState : prevState)
);
this.setState({ checkboxOptions: optionsArr });
};
//Passing the checked values back to parent
passSelectionToParent = (event: any) => {
event.preventDefault();
const result = this.props.options.map((item: any, i: any) =>
Object.assign({}, item, {
checked: this.state.checkboxOptions[i]
})
);
const selected = result
.filter((res: any) => res.checked)
.map((ele: any) => ele.field);
console.log(selected);
this.props.handleFilter(this.props.name, selected, result);
};
//Show/Hide filter
toggleList = () => {
this.setState(prevState => ({ showList: !prevState.showList }));
};
//Rendering the checkboxes based on the local state, but still it gets lost after filtering happens
render() {
let { showList } = this.state;
let visibleFlag: string;
if (showList === true) visibleFlag = "visible";
else visibleFlag = "";
return (
<div>
<div style={{ position: "absolute" }}>
<div
className={"ui scrolling dropdown column-settings " + visibleFlag}
>
<Icon className="filter" onClick={this.toggleList} />
<div className={"menu transition " + visibleFlag}>
<div className="menu-item-holder">
{this.props.options.map((item: any, i: number) => (
<div className="menu-item" key={i}>
<Checkbox
name={item.field}
onChange={this.handleValueChange}
label={item.field}
checked={this.state.checkboxOptions[i]}
/>
</div>
))}
</div>
<div className="menu-btn-holder">
<Button size="small" onClick={this.passSelectionToParent}>
Apply
</Button>
</div>
</div>
</div>
</div>
</div>
);
}
}
This appears to be a case of state being managed in an inconvenient way. Currently, the state is managed at the Child level, but it would be easier to manage at the Parent level. This is known as lifting state up in React.
The gist - the shared state is managed in the Parent component, and it's updated by calling a function passed to the Child component. When Apply is clicked, the selected radio value is passed up to the Parent, which merges the new selection into the shared state.
I have created a minimal example of your code, showing how we can lift state up from the Child to the Parent component. I'm also using a few new-ish features of React, like useState to simplify the Child component.
// Child Component
const Child = ({name, options, updateSelections}) => {
const [selected, setSelected] = React.useState([]);
const handleChange = (event) => {
let updated;
if (event.target.checked) {
updated = [...selected, event.target.value];
} else {
updated = selected.filter(v => v !== event.target.value);
}
setSelected(updated);
}
const passSelectionToParent = (event) => {
event.preventDefault();
updateSelections(name, selected);
}
return (
<form>
{options.map(item => (
<label for={name}>
<input
key={name}
type="checkbox"
name={item}
value={item}
onChange={handleChange}
/>
{item}
</label>
))}
<button onClick={passSelectionToParent}>Apply</button>
</form>
)
}
// Parent Component
class Parent extends React.Component {
constructor(props) {
super(props);
this.fields = ["firstName", "status"],
this.state = {
selected: {}
};
}
getValuesFromKey = (data, key) => {
return data.map(item => item[key]);
}
updateSelections = (name, selection) => {
this.setState({
selected: {...this.state.selected, [name]: selection}
}, () => console.log(this.state.selected));
}
render() {
return (
<div>
{this.fields.map(field => (
<Child
key={field}
name={field}
options={this.getValuesFromKey(this.props.data, field)}
updateSelections={this.updateSelections}
/>
))}
</div>
)
}
}
const data = [
{ firstName: "Jack", status: "Submitted" },
{ firstName: "Simon", status: "Pending" },
{ firstName: "Pete", status: "Approved" },
{ firstName: "Lucas", status: "Rejected" }
];
ReactDOM.render(<Parent data={data}/>, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.7.0-alpha.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.7.0-alpha.2/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Your checkbox values are only lost when you hide/show the table, as the table goes out of
DOM the state of it and it's children are lost. When the table is mounted to DOM, Child
component is mounted again initializing a new state taking checkbox values from
getValuesFromKey method of which returns false by default clearing checkbox ticks.
return {
field: keys[0],
checked: false
};
Stackblitz reproducing the issue.
You have to set checkbox values checking the selectedValues object to see if it was selected.
return {
field: keys[0],
checked: this.state.selectedValues[key] && this.state.selectedValues[key].includes(keys[0]),
};

set state twice in the same component

I am trying to setState to an event category for display inside of handleCategoryChange. The categories are rendered from the getCategories fetch point. I need to send a different value to the action fetch call in createEventHandler. The set state only happens once though and omits the second to send the first value of the state. Is there a work-around for this? or is this a limitation of react?
//... styles and imports
class NewEvent extends Component {
constructor(props) {
super(props);
this.state = {
event: {
category: ''
}
};
this.createEventHandler = this.createEventHandler.bind(this);
this.handleCategoryChange = this.handleCategoryChange.bind(this);
}
handleCategoryChange(evnt) {
this.setState({
event: {
...this.state.event,
category: evnt.target.value
}
});
}
componentWillMount() {
this.props.getCategories();
}
renderStepOne() {
const { event } = this.state;
const { categories } = this.props;
return (
<div style={styles.flexColumn}>
<Typography variant="title">Event</Typography>
<Select
value={event.category}
onChange={this.handleCategoryChange}
error={categoryError.length > 0}
>
{categories.map(category => (
<MenuItem key={category.id} value={category.name}>
{category.name}
</MenuItem>
))}
</Select>
</div>
);
}
createEventHandler() {
const { event } = this.state;
if (!error) {
let categoryId = this.props.categories.filter(e => {
if (e.name === event.category) {
return e;
}
});
categoryId = categoryId[0].id;
this.setState({
event: {
...event,
category: categoryId
}
});
this.props.createEvent(event, this.props.history);
}
}
render() {
const { step } = this.state;
const { isFetching, user, categories } = this.props;
return (
<ViewContainer title="New Event" isFetching={isFetching}>
<Paper style={styles.paper}>
<div style={styles.body}>{this.renderStepOne()}</div>
<MobileStepper
type="dots"
steps={0}
position="static"
nextButton={
<Button
variant="raised"
color="primary"
onClick={this.createEventHandler}
disabled={isFetching}
>
Submit
<KeyboardArrowRight />
</Button>
}
/>
</Paper>
</ViewContainer>
);
}
}
const mapStateToProps = state => ({
categories: state.events.categories
});
const mapDispatchToProps = dispatch => ({
createEvent: (event, history) => dispatch(createEvent(event, history)),
getCategories: () => dispatch(getCategories())
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(withRouter(NewEvent));
You could try using functional setState like so:
this.setState(() => ({
event: {
...this.state.event,
category: evnt.target.value
})
});
So that everything involving a setting of state happens together.

Categories

Resources