I have contactNumber with the data of array, and Im using native-base to view the data.
this.state = {
leadProfile: {
contactNumber: [
{
"lead_contact_number": "0912 312 412312",
"lead_contact_number_type": {
"lead_contact_number_type": "Mobile"
}
},
{
"lead_contact_number": "1231234rqdasd",
"lead_contact_number_type": {
"lead_contact_number_type": "Mobile"
}
},
{
"lead_contact_number": "0325 658 58996",
"lead_contact_number_type": {
"lead_contact_number_type": "Mobile"
}
}
]
},
contactNumber1: '',
contactNumber2: '',
contactNumber3: '',
contactNumber4: '',
contactNumber5: '',
};
}
the contactNumber1,2,3,4,5, this are all the containers when the data is change I want also get the data on the specific field
This is my function and also renderedData ....
arrayOfContacts is my array of data, sorry for the code I know not good work around but this what I think to be coded, feel free if there's good workaround..
The goal in here is to display and change the value of lead_contact_number
renderContactForm = () => {
let arrayOfContacts = _.map(this.state.leadProfile.contactNumber)
if (_.isEqual(this.state.leadProfile.contactNumber.length, 0) || _.isEqual(this.state.leadProfile.contactNumber.length, 1)) {
return (
...
)
} else if (_.isEqual(this.state.leadProfile.contactNumber.length, 2)) {
return (
....
)
} else if (_.isEqual(this.state.leadProfile.contactNumber.length, 3)) {
return (
<View>
<Form>
<Item floatingLabel style={{ paddingLeft: 4 }}>
<Label style={{ fontSize: 15, color: '#a0a0a0', paddingLeft: 4 }}>
{arrayOfContacts[0].lead_contact_number_type.lead_contact_number_type}
</Label>
<Input
autoCapitalize="number"
underlineColorAndroid='transparent'
onChangeText={(text) =>
this.setState({
...this.state,
leadProfile: {
...this.state.leadProfile.contactNumber[0],
lead_contact_number: text
},
contactNumber1: text
})}
value={this.state.leadProfile.contactNumber[0].lead_contact_number} />
</Item>
</Form>
<Form>
<Item floatingLabel style={{ paddingLeft: 4 }}>
<Label style={{ fontSize: 15, color: '#a0a0a0', paddingLeft: 4 }}>
{arrayOfContacts[1].lead_contact_number_type.lead_contact_number_type}
</Label>
<Input
autoCapitalize="number"
underlineColorAndroid='transparent'
onChangeText={(text) => this.setState({
...this.state,
leadProfile: {
...this.state.leadProfile.contactNumber[1],
lead_contact_number: text
},
contactNumber2: text
})}
value={this.state.leadProfile.contactNumber[1].lead_contact_number} />
</Item>
</Form>
<Form>
<Item floatingLabel style={{ paddingLeft: 4 }}>
<Label style={{ fontSize: 15, color: '#a0a0a0', paddingLeft: 4 }}>
{arrayOfContacts[2].lead_contact_number_type.lead_contact_number_type}
</Label>
<Input
autoCapitalize="number"
underlineColorAndroid='transparent'
onChangeText={(text) => this.setState({
...this.state,
leadProfile: {
...this.state.leadProfile.contactNumber[2],
lead_contact_number: text
},
contactNumber3: text
})}
value={this.state.leadProfile.contactNumber[2].lead_contact_number} />
</Item>
</Form>
</View>
)
} else if (_.isEqual(this.state.leadProfile.contactNumber.length, 4)) {
return (
...
)
}
}
The data will be true on _.isEqual(this.state.leadProfile.contactNumber.length, 3)
When I'm trying edit the text field the data is change and back to the default number.
That is a lot of markup to condense. Let me show you a simpler way to render your form and update the corresponding values. See this sandbox for action: https://codesandbox.io/s/boring-hill-i3prc
class NumberForm extends Component {
constructor(props) {
super(props);
this.state = {
leadProfile: {
contactNumber: [
{
lead_contact_number: "0912 312 412312",
lead_contact_number_type: {
lead_contact_number_type: "Mobile"
}
},
{
lead_contact_number: "1231234rqdasd",
lead_contact_number_type: {
lead_contact_number_type: "Mobile"
}
},
{
lead_contact_number: "0325 658 58996",
lead_contact_number_type: {
lead_contact_number_type: "Mobile"
}
}
]
}
};
}
handleChange = (e, numberIndex) => {
const { contactNumber } = this.state.leadProfile;
const updatedNumbers = contactNumber.map((number, index) => {
if (index === numberIndex) {
return {
...number,
lead_contact_number: e.target.value
};
} else {
return {
...number
};
}
});
this.setState({
leadProfile: {
...this.state.leadProfile,
contactNumber: updatedNumbers
}
});
};
createForm = () => {
const { contactNumber } = this.state.leadProfile;
return contactNumber.map((number, numberIndex) => {
return (
<div>
<input
value={number.lead_contact_number}
onChange={e => this.handleChange(e, numberIndex)}
/>
<label>
{number.lead_contact_number_type.lead_contact_number_type}
</label>
</div>
);
});
};
render() {
return <div>{this.createForm()}</div>;
}
}
Highlevel Points:
We will use the contact-array to create the inputs like you did
initially. But we will use the index value provided by .map() as
well.
We define an event-handler function to update our array-state
whenever the user enters an input, it accepts an index as an
argument.
During the setup of our inputs, we give it an event-listener, pairing
it with handleChange() function, and pass in the index of the
corresponding object we are iterating over in the .map()
This structure lets us effectively update the number for each input
even when the state value is an array of objects.
Keep your structure like this, and you should be able to freely swap out the mark-up like input and label with your choice of components from whatever library you're using.
Related
I tried a lot of ways to make it works but it didn't. Probably I don't understand the React idea or I missed something. My child via callback try to modify the global/parent state and then I got the well known error.
index.js:1452 Warning: Can't call setState (or forceUpdate) on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.
I try to use variable _isMounted so before setState I did:
if (this.isMounted()) {
this.setState({...});
}
Unfortunately it doesn't work.
The problem is when the function this.handleChangeAlgorithms is invoked from the child.
My code
class ConfigurationForm extends Component {
constructor(props) {
super(props);
this.state = {
choosenAlgorithm: null,
htmlType: null,
customHtml: null,
isOpenModal: false,
generatedHTMlConfig: {
headers: 1,
paragraphs: 1,
buttons: 1,
links: 1,
inputs: 1,
images: 1,
},
};
}
handleChangeAlgorithms = event => {
let algorithmName = event.target.value;
let newChoosenAlgorithm = this.props.algorithms.filter(
e => e.value === algorithmName,
)[0];
// this.setState({choosenAlgorithm: newChoosenAlgorithm});
this.props.callbackConfigurationForm(algorithmName);
};
handleChangeHtmlType(event) {
let newHtmlType = event.target.value;
console.log(newHtmlType);
if (newHtmlType === "default") {
this.setState({ isOpenModal: true });
} else {
this.setState({ htmlType: newHtmlType });
}
}
handleUserFile(event) {
let file = event.target.files[0];
this.setState({ customHtml: file });
this.props.callbackConfigurationFormPreview(file);
}
handleSubmit(event) {
event.preventDefault();
let htmlContent = null;
let htmltypeKey = Object.keys(HtmlType).find(
key => HtmlType[key] === HtmlType.default,
);
console.log(htmltypeKey);
// if (this.state.htmlType === 'default'){
// htmlContent = this.loadTemplate();
htmlContent = generateHTML(this.state.generatedHTMlConfig);
console.log(htmlContent);
// } else {
// htmlContent=this.state.customHtml;
// }
let config = {
algorithm: this.state.choosenAlgorithm,
html: htmlContent,
};
console.log(config);
this.props.callbackConfigurationForm(config);
}
loadTemplate() {
let loadedTemplate = null;
let file = "html-templates/example.html";
let request = new XMLHttpRequest();
request.open("GET", file, false);
request.send(null);
if (request.status === 200) {
loadedTemplate = request.responseText;
}
return loadedTemplate;
}
renderHtmlTypesList = () => {
let list = [];
for (const htmlKey of Object.keys(HtmlType)) {
list.push(
<option key={htmlKey} value={htmlKey}>
{HtmlType[htmlKey]}
</option>,
);
}
return list;
};
generateHTMLFromPopup() {
this.setState({ isOpenModal: false });
}
changeHeaders(event, type) {
console.log(type);
console.log(event.target.value);
let prevConfig = { ...this.state.generatedHTMlConfig };
switch (type) {
case "Headers":
prevConfig.headers = event.target.value;
this.setState({ generatedHTMlConfig: prevConfig });
break;
case "Paragraphs":
prevConfig.paragraphs = event.target.value;
this.setState({ generatedHTMlConfig: prevConfig });
break;
case "Buttons":
prevConfig.buttons = event.target.value;
this.setState({ generatedHTMlConfig: prevConfig });
break;
case "Links":
prevConfig.links = event.target.value;
this.setState({ generatedHTMlConfig: prevConfig });
break;
case "Inputs":
prevConfig.inputs = event.target.value;
this.setState({ generatedHTMlConfig: prevConfig });
break;
case "Images":
prevConfig.images = event.target.value;
this.setState({ generatedHTMlConfig: prevConfig });
break;
}
}
render() {
return (
<Row>
{/* todo 12.11.2018 extract to another component! */}
<Modal
open={this.state.isOpenModal}
header="Generate Html"
actions={
<div>
<Button
modal="close"
waves="light"
className="red lighten-2"
>
Cancel
</Button>
<Button
modal="close"
waves="light"
className="blue"
onClick={this.generateHTMLFromPopup.bind(this)}
>
<Icon left>build</Icon>Generate
</Button>
</div>
}
>
<p>Choose HTML elements for generated HTML.</p>
<Input
type="number"
label="Headers"
value={this.state.generatedHTMlConfig.headers}
onChange={e => this.changeHeaders(e, "Headers")}
/>
<Input
type="number"
label="Paragraphs"
value={this.state.generatedHTMlConfig.paragraphs}
onChange={e => this.changeHeaders(e, "Paragraphs")}
/>
<Input
type="number"
label="Buttons"
value={this.state.generatedHTMlConfig.buttons}
onChange={e => this.changeHeaders(e, "Buttons")}
/>
<Input
type="number"
label="Links"
value={this.state.generatedHTMlConfig.links}
onChange={e => this.changeHeaders(e, "Links")}
/>
<Input
type="number"
label="Inputs"
value={this.state.generatedHTMlConfig.inputs}
onChange={e => this.changeHeaders(e, "Inputs")}
/>
<Input
type="number"
label="Images"
value={this.state.generatedHTMlConfig.images}
onChange={e => this.changeHeaders(e, "Images")}
/>
</Modal>
<h2>Algorithm</h2>
<Row>
<Input
s={12}
type="select"
label="Select algorithm"
defaultValue=""
onChange={this.handleChangeAlgorithms}
>
<option value="" disabled>
Choose an algorithm
</option>
{this.props.algorithms.map(item => (
<option key={item.value} value={item.value}>
{item.name}
</option>
))}
</Input>
</Row>
{this.state.choosenAlgorithm ? (
<Collapsible popout>
<CollapsibleItem header="Details" icon="notes">
<ol>
{this.state.choosenAlgorithm.details.steps.map(
(step, index) => (
<li key={index}>{step}</li>
),
)}
</ol>
</CollapsibleItem>
</Collapsible>
) : null}
<h2>HTML to obfuscate</h2>
<Row>
<Input
s={12}
type="select"
label="HTML type"
defaultValue=""
onChange={this.handleChangeHtmlType}
>
<option value="" disabled>
Choose HTML
</option>
{this.renderHtmlTypesList()}
</Input>
</Row>
{this.state.htmlType === "custom" ? (
<Row>
<Input
type="file"
label="File"
onChange={this.handleUserFile}
/>
</Row>
) : null}
<div className="center-align">
<Button type={"button"} onClick={this.handleSubmit}>
Process<Icon left>autorenew</Icon>
</Button>
</div>
</Row>
);
}
}
class App extends Component {
_isMounted = false;
constructor(props) {
super(props);
this.state = {
isMounted: false,
//export to enum
algorithms: [
{
name: "Html to Javascript",
value: "1",
details: {
steps: [
"Split HTML file line by line.",
"Replace white characters.",
"Create function which add lines to document using document.write function.",
],
},
},
{
name: "Html to Unicode characters",
value: "2",
details: {
steps: [
"Create js function encoding characters to Unicode characters.",
"Create decoding function.",
"Add output from decoding function to HTML.",
],
},
},
{
name: "Html to escape characters",
value: "3",
details: {
steps: [
"Change endcoding using escape javascript function.",
"Decode using unescape javascript function.",
"Add element to HTML.",
],
},
},
{
name:
"Using own encoding and decoding function. [NOT IMPLEMENTED YET]",
value: "4",
details: {
steps: [
"Encode HTML using own function.",
"Save encoded content into js variable.",
"Decode using own decoding function.",
"Add element to HTML document.",
],
},
},
{
name: "Combine above methods [NOT IMPLEMENTED YET]",
value: "5",
details: {
steps: ["To be done..."],
},
},
],
previewHtml: null,
obfuscationConfig: null,
activeTab: 1,
};
}
componentDidMount() {
this._isMounted = true;
}
componentWillUnmount() {
this._isMounted = false;
}
processDataFromConfigurationForm = config => {
console.info(config);
if (this._isMounted) {
this.setState({
test: Date.now(),
// obfuscationConfig: config,
// doObfuscation: true,
// activeTab:3,
// previewHtml: config.html
});
}
};
render() {
return (
<div>
<header className="App">
<h1>HTML obfuscator</h1>
</header>
<Tabs>
<Tab
title="Configuration"
active={this.state.activeTab === 1}
>
<ConfigurationForm
algorithms={this.state.algorithms}
config={this.state.obfuscationConfig}
callbackConfigurationForm={
this.processDataFromConfigurationForm
}
/>
</Tab>
<Tab
title="HTML Preview"
active={this.state.activeTab === 2}
disabled={!this.state.previewHtml}
>
<HTMLPreview previewHtml={this.state.previewHtml} />
</Tab>
<Tab
title="Result"
active={this.state.activeTab === 3}
disabled={!this.state.obfuscationConfig}
>
{this.state.obfuscationConfig ? (
<ObfuscationOutput
config={this.state.obfuscationConfig}
/>
) : null}
</Tab>
</Tabs>
</div>
);
}
}
export default App;
The error
Try to add a constructor to your parent class, indeed, you can initialize the component state before the first modification:
class App extends Component {
constructor(props) {
super(props);
this.state = {
test1: Date.now()
};
}
// here you can update the state as you want, for example
foobar(event) {
this.setState({test1: event.target.value });
}
}
Hope it can help you...
Setting the state inside of the lifecycle method componentDidMount() should do the trick.
Oh I didn't see that someone already suggested that, glad you got it working
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>
)
}
I'm building a shopping cart application and I ran into a problem where all my inputs have the same state value. Everything works fine but when I type in one input box, it's the same throughout all my other inputs.
I tried adding a name field to the input and setting my initial state to undefined and that works fine but the numbers don't go through.
How do we handle inputs to be different when they have the same state value? Or is this not possible / dumb to do?
class App extends Component {
state = {
items: {
1: {
id: 1, name: 'Yeezys', price: 300, remaining: 5
},
2: {
id: 2, name: 'Github Sweater', price: 50, remaining: 5
},
3: {
id: 3, name: 'Protein Powder', price: 30, remaining: 5
}
},
itemQuantity: 0
},
render() {
return (
<div>
<h1>Shopping Area</h1>
{Object.values(items).map(item => (
<div key={item.id}>
<h2>{item.name}</h2>
<h2>$ {item.price}</h2>
{item.remaining === 0 ? (
<p style={{ 'color': 'red' }}>Sold Out</p>
) : (
<div>
<p>Remaining: {item.remaining}</p>
<input
type="number"
value={ itemQuantity }
onChange={e => this.setState({ itemQuantity: e.target.value})}
placeholder="quantity"
min={1}
max={5}
/>
<button onClick={() => this.addItem(item)}>Add To Cart</button>
</div>
)}
</div>
))}
</div>
)
}
}
If you are using same state key for all input, All input take value from one place and update to one place. To avoid this you have to use separate state. I suppose you are trying to show input for a list of item.
To achive you can create a component for list item and keep state in list item component. As each component have their own state, state value will not conflict.
Here is an example
class CardItem extends Component {
state = {
number: 0
}
render() {
render (
<input type="text" value={this.state.number} onChange={e => this.setState({ number: e.target.value })} />
)
}
}
class Main extends Component {
render () {
const list = [0,1,2,3,4]
return (
list.map(item => <CardItem data={item} />)
)
}
}
This is a solution which the problem is loosely interpreted, but it does work without having to create another component. As you know, you needed to separate the state of each items in the cart. I did this by dynamically initializing and setting the quantity states of each item. You can see the state changes with this example:
class App extends React.Component {
constructor(props) {
super(props);
this.state = { quantities: {} }
}
componentDidMount() {
let itemIDs = ['1', '2', '3', 'XX']; //use your own list of items
itemIDs.forEach(id => {
this.setState({quantities: Object.assign(this.state.quantities, {[id]: 0})});
})
}
render() {
let list = Object.keys(this.state.quantities).map(id => {
return (
<div>
<label for={id}>Item {id}</label>
<input
id={id}
key={id}
type="number"
value={this.state.quantities[id]}
onChange={e => {
this.setState({quantities: Object.assign(this.state.quantities, {[id]: e.target.value})})
}}
/>
</div>
);
})
return (
<div>
{list}
<div>STATE: {JSON.stringify(this.state)}</div>
</div>
);
}
}
ReactDOM.render(<App/>, document.getElementById('root'));
<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>
<div id='root'></div>
You can modify the state structure to your liking.
Here is how I usually handle this scenario. You say that you get an array of items? Each item object should contain a key to store the value (count in my example). You can use a generic onChange handler to update an individual item in the array. So now, your state is managing the list of items instead of each individual input value. This makes your component much more flexible and it will be able to handle any amount of items with no code changes:
const itemData = [
{ id: 0, count: 0, label: 'Number 1' },
{ id: 1, count: 0, label: 'Number 2' },
{ id: 2, count: 0, label: 'Number 3' },
{ id: 3, count: 0, label: 'Number 4' }
];
class App extends React.Component {
state = {
items: itemData
}
handleCountChange = (itemId, e) => {
// Get value from input
const count = e.target.value;
this.setState( prevState => ({
items: prevState.items.map( item => {
// Find matching item by id
if(item.id === itemId) {
// Update item count based on input value
item.count = count;
}
return item;
})
}))
};
renderItems = () => {
// Map through all items and render inputs
return this.state.items.map( item => (
<label key={item.label}>
{item.label}:
<input
type="number"
value={item.count}
onChange={this.handleCountChange.bind(this, item.id)}
/>
</label>
));
};
render() {
return (
<div>
{this.renderItems()}
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('root'));
label {
display: block;
}
<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>
<div id="root"></div>
You can't use the same state for the both inputs. Try to use a different state for each one like that:
class App extends Component {
state = {
number: ""
}
render() {
return (
<div>
<input
type="number"
value={this.state.number}
onChange={e => this.setState({ number: e.target.value })}
/>
<input
type="number"
value={this.state.number2}
onChange={e => this.setState({ number2: e.target.value })}
/>
</div>
)
}
}
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.
Let me preface that I'm in the process of learning React and I'm still pretty green at this.
I'm going to give the necessary parts of the code:
I have built a counter with increment and decrement buttons that are utilized by ways of state, they work just fine until I introduce and array and map over it. Then things start to break down. I know my code is wrong, I know there's something amiss however I'm completely ignorant as to what to even look for.
In my counting.js I have:
const players = [
{
name: "Jon Smith",
score: 10,
id: 1,
},
{
name: "Jon Doe",
score: 40,
id: 2,
},
{
name: "John Ham",
score: 30,
id: 3,
},
];
Which I have mapped in here:
class Counting extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0,
nameof: 'Full Name',
}
this.incrementCount = this.incrementCount.bind(this)
this.decrementCount = this.decrementCount.bind(this)
}
incrementCount(e) {
this.setState({
count: (this.state.count + 1),
})
}
decrementCount(e) {
this.setState({
count: (this.state.count - 1),
})
}
render() {
const listPlayers = players.map((players) =>
<Counter
key={players.id}
incrementCount={this.incrementCount}
decrementCount={this.decrementCount}
nameof={players.name}
count={players.score}
/>
);
return (
<div className="wrap">
<Titles header="Counter" subhead="A setState() project" subtitle="this will change" />
<h3>This doesn't work correctly</h3>
<ul>{listPlayers}</ul>
<ScoreList>
<h3> works</h3>
<li>
<Counter
incrementCount={this.incrementCount}
decrementCount={this.decrementCount}
nameof={this.state.nameof}
count={this.state.count}
/>
</li>
<li>
<Counter
incrementCount={this.incrementCount}
decrementCount={this.decrementCount}
nameof={this.state.nameof}
count={this.state.count}
/>
</li>
</ScoreList>
</div>
)
}
}
I have imported my Counter.jswhich is comprised of:
class Counter extends Component {
render() {
const { count } = this.props
const { decrementCount } = this.props
const { incrementCount } = this.props
const { nameof } = this.props
return (
<div>
<CountCell>
<Row style={{alignItems: 'center'}}>
<Col>
<CountButton
onClick={incrementCount}>
<Icon
name="icon" className="fa fa-plus score-icon"
/>
</CountButton>
</Col>
<Col >
<ScoreName>{nameof}</ScoreName>
</Col>
<Col >
<Score>{count}</Score>
</Col>
<Col>
<CountButton
onClick={decrementCount}>
<Icon
name="icon" className="fa fa-minus score-icon"
/>
</CountButton>
</Col>
</Row>
</CountCell>
</div>
)
}
}
So the increment and decrement buttons are only working globally and only for my static <li>, not my ones generated from the array. If I'm making any sense at all, how do I individually map my inc/dec buttons to each <li> and not globally?
Thank you!
You need to keep the state also be an array of objects, each storing data for a corresponding user
class Counting extends React.Component {
constructor(props) {
super(props);
this.state = {
countInfo: []
}
this.incrementCount = this.incrementCount.bind(this)
this.decrementCount = this.decrementCount.bind(this)
}
incrementCount(index) {
var countInfo = [...this.state.countInfo];
if(countInfo[index]) {
countInfo[index].count = countInfo[index].count + 1
countInfo[index].nameOf = players[index].name
}
else {
countInfo[index] = {count: 1, nameOf: players[index].name}
}
this.setState({
countInfo
})
}
decrementCount(index) {
var countInfo = [...this.state.countInfo];
if(countInfo[index]) {
countInfo[index].count = countInfo[index].count - 1
countInfo[index].nameOf = players[index].name
}
else {
countInfo[index] = {count: -1, nameOf: players[index].name}
}
this.setState({
countInfo
})
}
render() {
const listPlayers = players.map((players, index) =>
<Counter
key={players.id}
incrementCount={() => this.incrementCount(index)}
decrementCount={() => this.decrementCount(index)}
nameof={players.name}
count={players.score}
/>
);
return (
<div className="wrap">
<Titles header="Counter" subhead="A setState() project" subtitle="this will change" />
<h3>This doesn't work correctly</h3>
<ul>{listPlayers}</ul>