I am using addItem to add a value to a list from another component
I am adding it to this.state.movies. It appears, however it has the inactive/noresults className applied to it.
How do I determine which styling is applied to an item that has not appeared yet (ie using addItem)? Thanks
Full example on Codesandbox is here. Add an movie to the list and you will see it gets the stying applied: https://codesandbox.io/s/3OGK2pP9
Parent component where I add the item
<CreateNew addItem={item => this.setState({ movies: [{ name: item.value.name, genres: item.genres }].concat( movies, ), })} />
Child component that creates the item
class CreateNew extends React.Component {
constructor(props) {
super(props);
this.state = {
value: '',
genres: '',
};
}
handleSubmit1 = (e, value) => {
e.preventDefault();
this.props.addItem(this.state);
};
onChange = e => {
this.setState({
value: { name: e.target.value },
genres: [{ name: 'Test', type: 1 }, { name: 'Foo', type: 10 }],
});
};
render() {
const { value, genres } = this.props;
return (
<form onSubmit={this.handleSubmit1}>
Add a new movie
<input onChange={this.onChange} value={value} type="text" />
<button type="submit">Add</button>
</form>
);
}
}
Was related to filtering on my const x instead of my state this.state.movies.
I changed it from const filteredResults = andFilter({x}, Object.keys(selectedFilters)); to `const filteredResults = andFilter({this.state.movies}, Object.keys(selectedFilters));
Related
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]),
};
I'm working under the to-do list project and have got a problem with adding a comment to my list item. This is what I have right now:
App flow
After adding a list item you should be able to click in this item and a new window with comments will appear. In the comments section, comments should be added with Ctrl+Enter combination.
I've got a problem with adding comments to the list item (they should be added to the "comments" array of a particular list item).
Could you please explain what I'm doing wrong and why my comments aren't adding.
UPDATE: I've updated my code but the following mistake appears when I press Ctr+Enter to add a comment: [Error] (http://joxi.ru/Q2KR1G3U4lZJYm)
I've tried to bind the addItem method but no result. What's wrong with the addItem method?
Here is my main component:
App.js
import React, { Component } from 'react';
import './App.css';
import ListInput from './components/listInput'
import ListItem from './components/listItem'
import SideBar from './components/sideBar'
import CommentsSection from './components/commentsSection'
class App extends Component {
constructor(props){
super(props);
this.state = {
items: [
{
id: 0,
text: 'First item',
commentsCount: 0,
comments: [],
displayComment: false
},
{
id: 1,
text: 'Second item',
commentsCount: 0,
comments: [],
displayComment: false
},
{
id: 2,
text: 'Third item',
commentsCount: 0,
comments: [
'Very first comment',
'Second comment',
],
displayComment: false
},
],
nextId: 3,
activeComment: [],
}
}
// Add new item to the list
addItem = inputText => {
let itemsCopy = this.state.items.slice();
itemsCopy.push({id: this.state.nextId, text: inputText});
this.setState({
items: itemsCopy,
nextId: this.state.nextId + 1
})
}
// Remove the item from the list: check if the clicked button id is match
removeItem = id =>
this.setState({
items: this.state.items.filter((item, index) => item.id !== id)
})
setActiveComment = (id) => this.setState({ activeComment: this.state.items[id] });
addComment = (inputComment, activeCommentId ) => {
// find item with id passed and select its comments array
let commentCopy = this.state.items.find(item => item.id === activeCommentId)['comments']
commentCopy.push({comments: inputComment})
this.setState({
comments: commentCopy
})
}
render() {
return (
<div className='App'>
<SideBar />
<div className='flex-container'>
<div className='list-wrapper'>
<h1>Items</h1>
<ListInput inputText='' addItem={this.addItem}/>
<ul>
{
this.state.items.map((item) => {
return <ListItem item={item} key={item.id} id={item.id} removeItem={this.removeItem} setActiveComment={() => this.setActiveComment(item.id)}/>
})
}
</ul>
</div>
<CommentsSection inputComment='' items={this.state.activeComment}/>
</div>
</div>
);
}
}
export default App;
and my Comments Section component:
commentsSection.js
import React from 'react';
import './commentsSection.css';
import CommentInput from './commentInput'
import CommentsItem from './commentsItem'
export default class CommentsSection extends React.Component {
constructor(props){
super(props);
this.state = {value: this.props.inputComment};
this.handleChange = this.handleChange.bind(this);
this.handleEnter = this.handleEnter.bind(this);
this.addComment = this.addComment.bind(this)
}
handleChange = event => this.setState({value: event.target.value})
handleEnter(event) {
if (event.charCode === 13 && event.ctrlKey) {
this.addComment(this.state.value)
}
}
addComment(comment) {
// Ensure the todo text isn't empty
if (comment.length > 0) {
this.props.addComment(comment, this.props.activeComment);
this.setState({value: ''});
}
}
render() {
return (
<div className='component-section'>
<h1>{this.props.items.text}</h1>
<ul>
{ this.props.items.comments &&
this.props.items.comments.map((comment, index) => <p key={index}>{comment}</p>)
}
</ul>
<CommentsItem />
{/*<CommentInput />*/}
<div className='comment-input'>
<input type='text' value={this.state.value} onChange={this.handleChange} onKeyPress={this.handleEnter}/>
</div>
</div>
)
}
}
Change your CommentSection component addComment method and handleEnter method
addComment(comment) {
// Ensure the todo text isn't empty
if (comment.length > 0) {
// pass another argument to this.props.addComment
// looking at your code pass "this.props.items"
this.props.addComment(comment, this.props.items.id);
this.setState({value: ''});
}
}
handleEnter(event) {
if (event.charCode === 13 && event.ctrlKey) {
// passing component state value property as new comment
this.addComment(this.state.value)
}
}
Change your App Component addComment method
addComment = (inputComment, activeCommentId )=> {
// find item with id passed and select its comments array
let commentCopy = this.state.items.find(item => item.id === activeCommentId)['comments']
// if you want to push comments as object
// but looking at your code this is not you want
// commentCopy.push({comments: inputComment})
// if you want to push comments as string
commentCopy.push( inputComment)
this.setState({
comments: commentCopy
})
}
I'm having trouble setting the state of a component in React. The component is called "Search" and uses react-select. The full component is here:
class Search extends React.Component {
constructor(props){
super(props);
let options = [];
for (var x in props.vals){
options.push({ value: props.vals[x], label: props.vals[x], searchId: x });
};
this.state = {
inputValue: '',
value: options
};
}
handleChange = (value: any, actionMeta: any) => {
if(actionMeta.action == "remove-value"){
this.props.onRemoveSearch({ searchId: actionMeta.removedValue.searchId })
}
this.setState({ value });
};
handleInputChange = (inputValue: string) => {
this.setState({ inputValue });
};
handleSearch = ({ value, inputValue }) => {
this.setState({
inputValue: '',
value: [...value, createOption(inputValue)], // Eventually like to take this out...
});
this.props.onSearch({ inputValue });
}
handleKeyDown = (event: SyntheticKeyboardEvent<HTMLElement>) => {
const { inputValue, value } = this.state;
if (!inputValue) return;
switch (event.key) {
case 'Enter':
case 'Tab':
this.handleSearch({
value,
inputValue
});
event.preventDefault();
}
};
render() {
const { inputValue, value } = this.state;
return (
<div className="search">
<div className="search__title">Search</div>
<Tooltip
content={this.props.tooltipContent}
direction="up"
arrow={true}
hoverDelay={400}
distance={12}
padding={"5px"}
>
<CreatableSelect
className={"tags"}
components={components}
inputValue={inputValue}
isMulti
menuIsOpen={false}
onChange={this.handleChange}
onInputChange={this.handleInputChange}
onKeyDown={this.handleKeyDown}
placeholder="Add filters here..."
value={value}
/>
</Tooltip>
</div>
);
}
}
module.exports = Search;
You've probably noticed the strange thing that I'm doing in the constructor function. That's because I need to use data from my firebase database, which is in object form, but react-select expects an array of objects
with a "value" and "label" property. Here's what my data looks like:
To bridge the gap, I wrote a for-in loop which creates the array (called options) and passes that to state.value.
The problem: Because I'm using this "for in" loop, React doesn't recognize when the props have been changed. Thus, the react-select component doesn't re-render. How do I pass down these props (either modifying them inside the parent component or within the Search component) so that the Search component will re-render?
I would suggest not using the value state. What you do is simply copying props into your state. You can use props in render() method directly.
I reckon you use the value state because you need to update it based on user actions. In this case, you could lift this state up into the parent component.
class Parent extends React.Component {
constructor() {
this.state = { value: //structure should be the same as props.vals in ur code };
}
render() {
return (
<Search vals={this.state.value}/>
);
}
}
class Search extends React.Component {
constructor(props){
super(props);
this.state = {
inputValue: '',
};
}
render() {
const { inputValue } = this.state;
const { vals } = this.props;
let options = [];
for (var x in vals){
options.push({ value: vals[x], label: vals[x], searchId: x });
};
return (
<div className="search">
<div className="search__title">Search</div>
<Tooltip
content={this.props.tooltipContent}
direction="up"
arrow={true}
hoverDelay={400}
distance={12}
padding={"5px"}
>
<CreatableSelect
value={options}
/>
</Tooltip>
</div>
);
}
}
module.exports = Search;
class CreateEventForm extends Component {
constructor(props) {
super(props)
this.state = {
fields: {
name: '',
location: '',
description: '',
datetime: '',
},
friendsInEvent: [],
},
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
const { checked, value } = e.target
let { friendsInEvent } = { ...this.state }
if (checked) {
friendsInEvent = [...friendsInEvent, value]
} else {
friendsInEvent = friendsInEvent.filter(el => el !== value)
}
this.setState({ friendsInEvent }, () => console.log(this.state))
}
render() {
const { friends, createEvent, addFriendsToEvent } = this.props
const { fields: { name, location, description, datetime } } = this.state
const setter = propName => event => {
const nextFields = { ...this.state.fields }
nextFields[propName] = event.target.value
this.setState({ fields: nextFields })
}
return (
<div {...cssForm}>
<div>
<Label label="Event Name" />
<Field onChange={setter('name')} />
<Label label="Event Date/Time" />
<Field onChange={setter('datetime')} type="datetime-local" />
<Label label="Event Location" />
<Field onChange={setter('location')} />
<Label label="Event Description" />
<Field onChange={setter('description')} />
<SubHeader title="Add Friends to the Event..." />
<div>
{friends
.mapEntries(([friendId, frn]) => [
friendId,
<div key={friendId}>
<input
value={friendId}
type='checkbox'
onChange={this.handleChange}
defaultChecked={false} />
{frn.firstName} {frn.lastName}
</div>,
])
.toList()}
</div>
<Link to="/">
<Button style="blue" name="Done" onClick={() => createEvent(this.state.fields)} />
</Link>
</div>
</div>
)
}
}
export default CreateEventForm
This code above works and stores all of the correct values as expected. However I want to move friendsInEvent[ ] inside fields{ }. To look like:
super(props)
this.state = {
fields: {
name: '',
location: '',
description: '',
datetime: '',
friendsInEvent: [],
},
},
this.handleChange = this.handleChange.bind(this);
}
How can I achieve this? Everything I have tried breaks the handleChange(e) function or overwrites fields{ } with the friendsInEvent array.
I assume I have to change the values inside the handleChange(e) function after I move the array inside this.state.field?
Also, is there a way to merge my two functions setter and handleChange(e)? I kept them separate as they handle two different types (string and array).
Thanks.
From the looks of it you'll need to make the following changes to handleChange:
handleChange(e) {
const { checked, value } = e.target;
// Ensure that you're destructuring from fields
let { friendsInEvent } = { ...this.state.fields };
if (checked) {
friendsInEvent = [...friendsInEvent, value];
} else {
friendsInEvent = friendsInEvent.filter(el => el !== value)
}
// You need to take a copy of state, and then supply a new copy
// of the the fields property which takes a copy of the old fields
// property, and overwrites the friendsInEvent with the new data
this.setState({
...this.state,
fields: {...this.state.fields, friendsInEvent }
}, () => console.log(this.state));
}
Here's a small React-free example of how this would work.
Why can't you do it in the below way.
handleChange(e) {
const { checked, value } = e.target
let { friendsInEvent } = { ...this.state }
if (checked) {
friendsInEvent = [...friendsInEvent, value]
} else {
friendsInEvent = friendsInEvent.filter(el => el !== value)
}
let object = {};
object.name = this.state.name;
object.location = this.state.location;
object.description = this.state.description;
object.datetime = this.state.datetime;
object.friendsInEvent = friendsInEvent;
this.setState({
fields: object
})
}
Never do setState inside render. You are not suppose to do setState inside render. Event handlers should be handled before render method but not inside of render. Try avoiding doing setState inside render.
const setter = propName => event => {
const nextFields = { ...this.state.fields }
nextFields[propName] = event.target.value
this.setState({ fields: nextFields })
}
I would like to use AddItem to add items to a list in another component. However, I keep getting undefined.
How do I correctly add an item to a list?
I've put it inside a CodeSandbox too: https://codesandbox.io/s/Mjjrm3zrO
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
movies: [x.movies],
};
}
render() {
return (
<div>
<CreateNew addItem={item => this.setState({ movie: [item].concat(this.state.movie) })} />
{x.movies.map(movie => (
<Result key={movie.id} result={movie} addItem={item => this.setState({ genres: [item].concat(this.state.genres) })} />
))}
</div>
);
}
}
class CreateNew extends React.Component {
constructor(props) {
super(props);
this.state = {
value: '',
genres: '',
};
}
handleSubmit1 = (e, value) => {
e.preventDefault();
this.props.addItem(this.state.value)
console.log(this.props.item);
};
onChange = e => {
this.setState({ value: {'name': e.target.value}, genres: [{ name: 'Test', type: 1 }, { name: 'Foo', type: 10 }] });
console.log(this.state.value);
};
render() {
const { value, genres } = this.props;
return (
<form onSubmit={this.handleSubmit1}>
Add a new movie
<input onChange={this.onChange} type="text" />
<button type="submit">Save</button>
</form>
);
}
}
class Result extends React.Component {
render() {
const { result } = this.props;
return (
<div>
<li>
{result.name} {' '}
({result.genres.map(x => x.name).join(', ')}){' '}
</li>
</div>
);
}
}
Changes:
1. Instead of sending only name from child component send the whole state variable that will contain name and genres.
handleSubmit1 = (e, value) => {
e.preventDefault();
this.props.addItem(this.state)
};
2. You are storing the initial value movies from json to state variable so use that state variable to create the ui, because you are updating the state variable once you adding any new item, so if you use initial json to create ui then new item will not reflect in ui.
{this.state.movies.map(movie => (
<Result key={movie.id} result={movie} />
))}
3. Update the state variable movies like this:
<CreateNew addItem={item => this.setState({ movies: [{name: item.value.name, genres: item.genres}].concat(this.state.movies) })} />
Check the working solution: https://codesandbox.io/s/3nD0RgRp