How to save data in an array inside state in react js - javascript

I have TextField and FlatButton inside the Dialog. I want to save complete task list in an array which I defined in a state.
this.state = {
open: false,
todos: [{ id: -1, text: "", completed: false }],
notetext: ""
};
I am able to get text of TextField from the state. I want to save task in an array on clicking of FlatButton. I have handleCreateNote function which is attached on tap on FlatButton.
I don't know what is the way to add task in an array. Can anyone help me what is the way in the react ?
const AppBarTest = () =>
<AppBar
title={strings.app_name}
iconClassNameRight="muidocs-icon-navigation-expand-more"
style={{ backgroundColor: colors.blue_color }}
/>;
class App extends Component {
constructor(props) {
injectTapEventPlugin();
super(props);
this.state = {
open: false,
todos: [{ id: -1, text: "", completed: false }],
notetext: ""
};
this.handleChange = this.handleChange.bind(this);
}
handleOpen = () => {
this.setState({ open: true });
};
handleClose = () => {
this.setState({ open: false });
};
handleCreateNote = () => {
console.log(this.state.notetext);
};
handleChange(event) {
this.setState({ [event.target.name]: event.target.value });
}
render() {
return (
<MuiThemeProvider>
<div>
<AppBarTest />
<FloatingActionButton
style={styles.fab}
backgroundColor={colors.blue_color}
onTouchTap={this.handleOpen}
>
<ContentAdd />
</FloatingActionButton>
<Dialog
open={this.state.open}
onRequestClose={this.handleClose}
title={strings.dialog_create_note_title}
>
<TextField
name="notetext"
hintText="Note"
style={{ width: "48%", float: "left", height: 48 }}
defaultValue={this.state.noteVal}
onChange={this.handleChange}
/>
<div
style={{
width: "4%",
height: "48",
backgroundColor: "red",
float: "left",
visibility: "hidden"
}}
/>
<FlatButton
label={strings.create_note}
style={{ width: "48%", height: 48, float: "left" }}
onTouchTap={this.handleCreateNote}
/>
</Dialog>
</div>
</MuiThemeProvider>
);
}
}
export default App;

First create a copy of existing state array, then use array.push to add a new data, then use setState to update the state array value.
Like this:
handleCreateNote = () => {
let todos = [...this.state.todos]; //creating the copy
//adding new data
todos.push({
id: /*unique id*/,
text: this.state.notetext,
completed: false
});
//updating the state value
this.setState({todos});
};
Check this answer for details about "..." --> What do these three dots in React do?
MDN: Spread Operator
Update:
Instead of spread operator you can also use slice(), that will also return a new array, the key point is we need to create a copy of state array (before doing any change) by using any method.
Check this snippet:
let a = [{key: 1}, {key: 2}];
let b = [...a];
console.log('b', b);

you can use concat to create a new array:
this.setState({
todos: [].Concat(this.state.todos, {id, text, complete})
})

Related

Creating an element using .map function in Javascript

I'm struggling while creating an element that is passed by the .map function. Basically, I want my webpage to create a div element with some date in it when a button is clicked for that I'm using a .map function but it isn't working out.
const handleSubmit = (e) => {
e.preventDefault();
const data = {title:`${title}`, desc:`${desc}`, date:`${date}`};
data.map(userinfo =>{
return(<div>
<h1>{userinfo.title}</h1>
</div>)
})
console.log(data);
}
In reactJS, if we want to display our data in HTML webpage we usually do that in the render funciton.
We can use userInfo variable in the state object.
The userInfo data is hardcoded for demonstration purposes but you can also populate the userInfo variable either using API or in any other way you like.
Moreover, showUserInfo is another variable (initially false) that would render the data once it is set to true
this.state = {
userInfo: [
{
title: 'one',
desc: '',
date: new Date()
},
{
title: 'two',
desc: '',
date: new Date()
}
],
showUserInfo: false
}
On a click event we can set showUserInfo to true using setState function.
more on setState function via this link ->
https://medium.com/#baphemot/understanding-reactjs-setstate-a4640451865b
handleSubmit = async (event) => {
event.preventDefault();
this.setState(
{
...this.state,
showUserInfo: true
}
)
}
In the render function, if showUserInfo is false then userInfo.map is never going to render unless showUserInfo is set to true which we do using a click listener that is associated with our function handleSubmit.
render(){
return (
<div>
<button onClick={this.handleSubmit}>Click Me</button>
{ this.state.showUserInfo &&
this.state.userInfo.map(item =>(
<div>
<p> {item.date.toString()} </p>
</div>
) ) }
</div>
);
}
Overall the result looks a something like this.
export default class App extends React.Component {
constructor() {
super();
this.state = {
showUserInfo: false,
userInfo: [
{
title: 'one',
desc: '',
date: new Date()
},
{
title: 'two',
desc: '',
date: new Date()
}
],
}
}
handleSubmit = async (event) => {
event.preventDefault();
this.setState(
{
...this.state,
showUserInfo: true
}
)
}
render(){
return (
<div>
<button onClick={this.handleSubmit}>Click Me</button>
{ this.state.showUserInfo &&
this.state.userInfo.map(item =>(
<div>
<p> {item.date.toString()} </p>
</div>
) ) }
</div>
);
}
}

ReactJS Search input by multiple values

I have a search and select filters on my page. The issue that I am having is that I can't seem to make the search work with multiple json values.
Example value is { "id": "1", "role": "teacher", "subject": "mathematics", "name": "Jonathan Kovinski" } and I want to be able to use key and values.
I've tried using some other question about combining json key and value into a single array and passing it to the search filter but it didn't work.
text = data.filter(info => {
return Object.keys(info).map(function(key) {
var singleOne = JSON.stringify(info[key]);
console.log(info, "This is the json one")
}).toLowerCase().match(searchString);
});
Here is a link to a JS Fiddle that I've created with all of my code.
I am trying to set my search bar to use all keys and values for searching and sorting data.
i would suggest you put the filtered data in a seperate key in the state in case you want to revert to the original result,
use the Obeject.values instead of Object.keys and filter the data in the handleChange function,
here's a working code :
class Hello extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading: false,
data: [],
searchString: "",
filtered: []
};
this.handleChange = this.handleChange.bind(this);
}
componentDidMount() {
this.fetchData();
}
handleChange(e) {
var value = e.target.value;
this.setState({
searchString: value,
filtered: this.state.data.filter(e =>
Object.values(e)
.join(" ")
.toLowerCase()
.match(value)
)
});
}
fetchData() {
fetch("https://api.myjson.com/bins/lo3ls")
.then(response => response.json())
.then(json => {
this.setState({
isLoaded: true,
data: json,
filtered: json
});
})
.catch(error => console.log("parsing failed", error));
}
render() {
var { isLoaded, data } = this.state;
const searchString = this.state.searchString.trim().toLowerCase();
let text = this.state.data;
console.log(text);
if (searchString.length > 0) {
text = text.filter(info => {
return info.role.toLowerCase().match(searchString);
});
}
return (
<div>
<input
type="text"
id="searchbar"
value={this.state.searchString}
onChange={this.handleChange}
placeholder="Search"
name="device"
/>
<select className="category-select" name="categories" onChange={this.handleChange}>
{data.map(info => (
<option value={info.role}>{info.role}</option>
))}
</select>
{/* map through the filtered ones*/}
{this.state.filtered.map(info => (
<div className="display">
<span className="role">Role: {info.role}</span>
<span> Name: {info.name}</span>
<span>, Subject: {info.subject}</span>
</div>
))}
</div>
);
}
}
ReactDOM.render(<Hello name="World" />, document.getElementById("container"));
Actually, I read all of your code in Fiddle, But I proffer Fuse to you. Use it inside your code in componentDidMount and implement your search. it is very easy and handy.
const options = {
shouldSort: true,
threshold: 0.6,
location: 0,
distance: 100,
maxPatternLength: 32,
minMatchCharLength: 1,
keys: [
"title",
"author.firstName"
]
};
const fuse = new Fuse(list, options); // "list" is the item array
const result = fuse.search(""); // put your string inside double quotation
The result is your answer.

Using JsonSchemaForm on change to update field's content

I am trying to use JsonSchema-Form component but i ran into a problem while trying to create a form that, after choosing one of the options in the first dropdown a secondary dropdown should appear and give him the user a different set o options to choose depending on what he chose in the first dropdown trough an API call.
The thing is, after reading the documentation and some examples found here and here respectively i still don't know exactly how reference whatever i chose in the first option to affect the second dropdown. Here is an example of what i have right now:
Jsons information that are supposed to be shown in the first and second dropdowns trough api calls:
Groups: [
{id: 1,
name: Group1}
{id: 2,
name: Group2}
]
User: [User1.1,User1.2,User2.1,User2.2,User3.1,User3.2, ....]
If the user selects group one then i must use the following api call to get the user types, which gets me the the USER json.
Component That calls JSonChemaForm
render(){
return(
<JsonSchemaForm
schema={someSchema(GroupOptions)}
formData={this.state.formData}
onChange={{}}
uiSchema={someUiSchema()}
onError={() => {}}
showErrorList={false}
noHtml5Validate
liveValidate
>
)
}
SchemaFile content:
export const someSchema = GroupOptions => ({
type: 'object',
required: [
'groups', 'users',
],
properties: {
groups: {
title: 'Group',
enum: GroupOptions.map(i=> i.id),
enumNames: GroupOptions.map(n => n.name),
},
users: {
title: 'Type',
enum: [],
enumNames: [],
},
},
});
export const someUISchema = () => ({
groups: {
'ui:autofocus': true,
'ui:options': {
size: {
lg: 15,
},
},
},
types: {
'ui:options': {
size: {
lg: 15,
},
},
},
});
I am not really sure how to proceed with this and hwo to use the Onchange method to do what i want.
I find a solution for your problem.There is a similar demo that can solve it in react-jsonschema-form-layout.
1. define the LayoutField,this is part of the demo in react-jsonschema-form-layout.To make it easier for you,I post the code here.
Create the layoutField.js.:
import React from 'react'
import ObjectField from 'react-jsonschema-form/lib/components/fields/ObjectField'
import { retrieveSchema } from 'react-jsonschema-form/lib/utils'
import { Col } from 'react-bootstrap'
export default class GridField extends ObjectField {
state = { firstName: 'hasldf' }
render() {
const {
uiSchema,
errorSchema,
idSchema,
required,
disabled,
readonly,
onBlur,
formData
} = this.props
const { definitions, fields, formContext } = this.props.registry
const { SchemaField, TitleField, DescriptionField } = fields
const schema = retrieveSchema(this.props.schema, definitions)
const title = (schema.title === undefined) ? '' : schema.title
const layout = uiSchema['ui:layout']
return (
<fieldset>
{title ? <TitleField
id={`${idSchema.$id}__title`}
title={title}
required={required}
formContext={formContext}/> : null}
{schema.description ?
<DescriptionField
id={`${idSchema.$id}__description`}
description={schema.description}
formContext={formContext}/> : null}
{
layout.map((row, index) => {
return (
<div className="row" key={index}>
{
Object.keys(row).map((name, index) => {
const { doShow, ...rowProps } = row[name]
let style = {}
if (doShow && !doShow({ formData })) {
style = { display: 'none' }
}
if (schema.properties[name]) {
return (
<Col {...rowProps} key={index} style={style}>
<SchemaField
name={name}
required={this.isRequired(name)}
schema={schema.properties[name]}
uiSchema={uiSchema[name]}
errorSchema={errorSchema[name]}
idSchema={idSchema[name]}
formData={formData[name]}
onChange={this.onPropertyChange(name)}
onBlur={onBlur}
registry={this.props.registry}
disabled={disabled}
readonly={readonly}/>
</Col>
)
} else {
const { render, ...rowProps } = row[name]
let UIComponent = () => null
if (render) {
UIComponent = render
}
return (
<Col {...rowProps} key={index} style={style}>
<UIComponent
name={name}
formData={formData}
errorSchema={errorSchema}
uiSchema={uiSchema}
schema={schema}
registry={this.props.registry}
/>
</Col>
)
}
})
}
</div>
)
})
}</fieldset>
)
}
}
in the file, you can define doShow property to define whether to show another component.
Next.Define the isFilled function in JsonChemaForm
const isFilled = (fieldName) => ({ formData }) => (formData[fieldName] && formData[fieldName].length) ? true : false
Third,after you choose the first dropdown ,the second dropdown will show up
import LayoutField from './layoutField.js'
const fields={
layout: LayoutField
}
const uiSchema={
"ui:field": 'layout',
'ui:layout': [
{
groups: {
'ui:autofocus': true,
'ui:options': {
size: {
lg: 15,
},
},
}
},
{
users: {
'ui:options': {
size: {
lg: 15,
},
},
doShow: isFilled('groups')
}
}
]
}
...
render() {
return (
<div>
<Form
schema={schema}
uiSchema={uiSchema}
fields={fields}
/>
</div>
)
}

React to do list, how do i get this delete button to work?

I have a simple to do app that is working fine, except for the ability to delete items from the list. I have already added the button to each of the list items. I know I want to use the .filter() method to pass the state a new array that doesn't have the deleted to-do but I'm not sure how to do something like this.
Here is the App's main component:
class App extends Component {
constructor(props){
super(props);
this.state = {
todos: [
{ description: 'Walk the cat', isCompleted: true },
{ description: 'Throw the dishes away', isCompleted: false },
{ description: 'Buy new dishes', isCompleted: false }
],
newTodoDescription: ''
};
}
deleteTodo(e) {
this.setState({ })
}
handleChange(e) {
this.setState({ newTodoDescription: e.target.value })
}
handleSubmit(e) {
e.preventDefault();
if (!this.state.newTodoDescription) { return }
const newTodo = { description: this.state.newTodoDescription,
isCompleted: false };
this.setState({ todos: [...this.state.todos, newTodo],
newTodoDescription: '' });
}
toggleComplete(index) {
const todos = this.state.todos.slice();
const todo = todos[index];
todo.isCompleted = todo.isCompleted ? false : true;
this.setState({ todos: todos });
}
render() {
return (
<div className="App">
<ul>
{ this.state.todos.map( (todo, index) =>
<ToDo key={ index } description={ todo.description }
isCompleted={ todo.isCompleted } toggleComplete={ () =>
this.toggleComplete(index) } />
)}
</ul>
<form onSubmit={ (e) => this.handleSubmit(e) }>
<input type="text" value={ this.state.newTodoDescription }
onChange={ (e) => this.handleChange(e) } />
<input type="submit" />
</form>
</div>
);
}
}
And then here is the To-Do's component:
class ToDo extends Component {
render() {
return (
<li>
<input type="checkbox" checked={ this.props.isCompleted }
onChange={ this.props.toggleComplete } />
<button>Destroy!</button>
<span>{ this.props.description }</span>
</li>
);
}
}
Event handlers to the rescue:
You can send onDelete prop to each ToDo:
const Todo = ({ description, id, isCompleted, toggleComplete, onDelete }) =>
<li>
<input
type="checkbox"
checked={isCompleted}
onChange={toggleComplete}
/>
<button onClick={() => onDelete(id)}>Destroy!</button>
<span>{description}</span>
</li>
And from App:
<ToDo
// other props here
onDelete={this.deleteTodo}
/>
As pointed by #Dakota, using index as key while mapping through a list is not a good pattern.
Maybe just change your initialState and set an id to each one of them:
this.state = {
todos: [
{ id: 1, description: 'Walk the cat', isCompleted: true },
{ id: 2, description: 'Throw the dishes away', isCompleted: false },
{ id: 3, description: 'Buy new dishes', isCompleted: false }
],
newTodoDescription: '',
}
This also makes life easier to delete an item from the array:
deleteTodo(id) {
this.setState((prevState) => ({
items: prevState.items.filter(item => item.id !== id),
}))
}
Before you get any further you should never use a list index as the key for your React Elements. Give your ToDo an id and use that as the key. Sometimes you can get away with this but when you are deleting things it will almost always cause issues.
https://medium.com/#robinpokorny/index-as-a-key-is-an-anti-pattern-e0349aece318
If you don't want to read the article, just know this
Let me explain, a key is the only thing React uses to identify DOM
elements. What happens if you push an item to the list or remove
something in the middle? If the key is same as before React assumes
that the DOM element represents the same component as before. But that
is no longer true.
On another note, add an onClick to your button and pass the function you want it to run as a prop from App.
<button onClick={() => this.props.handleClick(this.props.id)} />
and App.js
...
constructor(props) {
...
this.handleClick = this.handleClick.bind(this);
}
handleClick(id) {
// Do stuff
}
<ToDo
...
handleClick={this.handleClick}
/>

React, update array's total count state when new item is added to it

I have my array where you can add a "player" to it and it updates just fine. I also have a total number of players that looks at the array and gives the total sum. It works but it does not factor in the new items.
I know I need to pass it the new updated state of the array (players) but I'm just stuck on how to do that.
I've provided snips of my code as follows
Array
const players = [
{
name: 'Jabba',
score: 10,
id: 11
},
{
name: 'Han',
id: 1
},
{
name: 'Luke',
id: 2
},
Function to add to the array
handleChange = e => {
this.setState({
newPlayerName: e.target.value
});
};
handleAddPlayer = e => {
this.setState(prevState => ({
players: [
...prevState.players,
{
name: this.state.newPlayerName,
id: getRandomInt(20, 155)
}
]
}));
};
my state
this.state = {
id: players.id,
totalScore: 0,
totalPlayers: players,
countInfo: [],
evilName: '',
color: '#6E68C5',
scoreColor: '#74D8FF',
fontAwe: 'score-icon',
incrementcolor: '',
scoreNameColor: 'white',
glow: '',
buttonStyle: 'count-button-start',
newPlayerName: '',
max_chars: 15,
chars_left: '',
players
};
And finally the function that should update the total but doesn't and where I'm stuck
function Stats(props) {
const totalPlayers = props.players.length;
const totalScore = props.players.reduce(
function(total, player) {
return props.totalScore + props.totalScore;
},
0
);
return (
<div style={{ width: '100%' }}>
<PlayerNumb>Number of players: <b>{totalPlayers}</b></PlayerNumb>
</div>
);
}
the const totalPlayers = props.players.length; is where I seemed to be tripped up. Any help would be greatly appreciated and apologies if I gave any confusing information as React is still new to me.
Also an example of how I called stats:
<Stats
style={{ width: '100%' }}
totalScore={this.state.totalScore}
players={players}
/>
And the complete render code of how Players is created
render() {
const listPlayers = this.state.players.map(player => (
<Counter
key={player.id}
player={player}
playersArray={this.state.players}
name={player.name}
sortableGroupDecorator={this.sortableGroupDecorator}
decrementCountTotal={this.decrementCountTotal}
incrementCountTotal={this.incrementCountTotal}
removePlayer={this.removePlayer.bind(this)} //bind this to stay in the context of the parent component
handleClick={player}
/>
));
return (
<ContainLeft style={{ alignItems: 'center' }}>
<ProjectTitle>Score Keeper</ProjectTitle>
<Copy>
A sortable list of players that with adjustable scores. Warning, don't go negative!
</Copy>
<GroupHolder>
<Stats
style={{ width: '100%' }}
totalScore={this.state.totalScore}
players={players}
/>
<div
style={{ width: '100%' }}
className="container"
ref={this.sortableContainersDecorator}
>
<div className="group">
<div className="group-list" ref={this.sortableGroupDecorator}>
{listPlayers}
</div>
</div>
</div>
<HandleForm>
<form
style={{ width: '100%' }}
onSubmit={this.handleClick.bind(this)}
>
<p className="sort-player">Add A Player</p>
<InputText
type="text"
maxLength="15"
max="4"
name="usr"
placeholder="Enter a new name"
value={this.state.newPlayerName}
onChange={this.handleChange}
/>
<PlayerButton onClick={this.handleAddPlayer}>
Press to Add Player
</PlayerButton>
<CharacterCount>
Characters left
{' '}
{this.state.max_chars - this.state.newPlayerName.length}
</CharacterCount>
</form>
</HandleForm>
</GroupHolder>
</ContainLeft>
);
}
}
export default Container;
You're calling your render function with an undefined variable called players
players={players}
You either need to pass this.state.players or define it with const players = this.state.players;
You should always be developing locally with an eslint plugin for your editor, which will highlight problems like these as syntax errors and save you hours of time.

Categories