Related
I'm trying to select an arrayfield inside an array. Following code is inserted in my useStore:
const useStore = create(
persist(set => ({
projectszustand: [
{
id: nanoid(),
name: 'Projekt Final 1',
notes: 'Hier sind ein paar Notizen',
begin: '01/01/2001',
end: '02/01/2001',
icon: 'https://www.skopos.de/wp-content/uploads/2021/04/Element-5.svg',
color: 'blue',
edit: false,
selected: true,
feature: [
{
id: nanoid(),
name: 'Feature Final 1',
begin: '01/01/2001',
end: '02/01/2001',
isChecked: false,
edit: false,
selected: false,
todo: [
{...and so on
So, I'm trying to go with forEach and set all selected fields in the feature array to false:
selectFeature: index => {
set(
produce(draft => {
draft.projectszustand[index].feature.forEach(element => {
element.selected = false;
});
draft.projectszustand[index].feature[index].selected =
!draft.projectszustand[index].feature[index].selected;
})
);
},
This no works. Error message is: TypeError: can't access property "feature", draft.projectszustand[index] is undefined
Has somebody an easy solution for this?
Thanks very much for helping.
I have some nested state in class component that I want change it to functional component, but I don't know how can I use nested state in functional component with hook and useState and also useEffect? can you help me please?
state = {
products: null,
totalPrice: 0,
addCounter: 0,
purchased: false,
loading: false,
form: {
name: {
elementType: 'input',
elmentConfig: {
type: 'text',
palceholder: 'Name...',
},
value: '',
validation: {
required: true,
},
valid: false,
used: false,
},
password: {
elementType: 'input',
elmentConfig: {
type: 'password',
palceholder: 'Password...',
},
value: '',
validation: {
required: true,
},
valid: false,
used: false,
},
email: {
elementType: 'input',
elmentConfig: {
type: 'text',
palceholder: 'Email...',
},
value: '',
validation: {
required: true,
},
valid: false,
used: false,
},
},
};
Convert your state with useState, however I would recommend separating your state in smaller 'states' to make it a little more manageable and not have deeply nested values.
eg.
const [checkout, setCheckout] = useState({
products: null,
totalPrice: 0,
addCounter: 0,
purchased: false,
loading: false
});
const [userForm, setUserForm] = useState({
name: {
elementType: 'input',
elementConfig: {
type: 'text',
placeholder: 'Name...'
},
value: '',
validation: {
required: true
},
valid: false,
used: false,
},
password: {
elementType: 'input',
elementConfig: {
type: 'password',
placeholder: 'Password...'
},
value: '',
validation: {
required: true
},
valid: false,
used: false,
},
email: {
elementType: 'input',
elementConfig: {
type: 'text',
palceholder: 'Email...'
},
value: '',
validation: {
required: true
},
valid: false,
used: false,
}
});
nb. you can get your state from the useState hook simply by calling the state name once it has been initialised.
setting your state can become complex when working with deeply nested states so the 'shallower' the better for each state imho.
see documentation here: https://reactjs.org/docs/hooks-state.html with equivalent class examples
You can try:
const [formElements, setFormElements] = useState({
products: null,
totalPrice: 0,
addCounter: 0,
purchased: false,
loading: false,
form: {
name: {
elementType: 'input',
elmentConfig: {
type: 'text',
palceholder: 'Name...',
},
value: '',
validation: {
required: true,
},
valid: false,
used: false,
},
password: {
elementType: 'input',
elmentConfig: {
type: 'password',
palceholder: 'Password...',
},
value: '',
validation: {
required: true,
},
valid: false,
used: false,
},
email: {
elementType: 'input',
elmentConfig: {
type: 'text',
palceholder: 'Email...',
},
value: '',
validation: {
required: true,
},
valid: false,
used: false,
},
},
});
By using this, the initial state can be available in formElements and to update it you can use setFormElements function.
Reference
There is a nice looking schema-based Form component on fluentui
import React from 'react';
import { Form, Button } from '#fluentui/react-northstar';
const fields = [
{
label: 'First name',
name: 'firstName',
id: 'first-name-shorthand',
key: 'first-name',
required: true,
},
{
label: 'Last name',
name: 'lastName',
id: 'last-name-shorthand',
key: 'last-name',
required: true,
},
{
label: 'I agree to the Terms and Conditions',
control: {
as: 'input',
},
type: 'checkbox',
id: 'conditions-shorthand',
key: 'conditions',
},
{
control: {
as: Button,
content: 'Submit',
},
key: 'submit',
},
];
const FormExample = () => (
<Form
onSubmit={() => {
alert('Form submitted');
}}
fields={fields}
/>
);
export default FormExample;
But they don't offer any method/example to collect the data from what I can tell. (at least not in the documentation).
I can collect most values from the onSubmit event but it get's hacky because not all html components are necessarily input elements that have the value attribute. I also don't think this is the intended way to do it. Anyone can enlighten me please? I think you must be able to feed the onChange function to it somehow. Or am i supposed to add the onChange function in each field-object?
I ended up combing through the library components (Forms Input and Checkbox) to see what makes them tick.
This is what I ended up with. Please feel free to improve on it should anyone else stumble on this in the future.
Note the use of the attributes defaultValue and defaultChecked to set the initial value of the Input and Checkbox components respectively. As well as the onChange event passing the name and value parameters for the Input component and name and checked for the Checkbox component.
The Checkbox label must be inside the Control if you wish it to appear next to the checkbox, otherwise it will appear above the checkbox.
import React, { Component } from 'react';
import { Form, Button, Checkbox, Input } from '#fluentui/react-northstar';
class Login extends Component {
constructor(props) {
super(props);
this.handleSubmit.bind(this)
}
state = {
email: "",
password: "",
remember_me: true
}
fields = [
{
label: 'Email',
name: 'email',
id: 'email-inline-shorthand',
key: 'email',
required: true,
inline: true,
type: 'email',
control: {
as: Input,
defaultValue: this.state.email,
onChange: (e, { name, value }) => this.setState({ ...this.state, [name]: value })
}
},
{
label: 'Password',
name: 'password',
id: 'password-inline-shorthand',
key: 'password',
required: true,
inline: true,
type: 'password',
control: {
defaultValue: this.state.password,
onChange: (e, { name, value }) => this.setState({ ...this.state, [name]: value }),
as: Input,
}
},
{
name: "remember_me",
key: 'remember_me',
id: 'remember_me-inline-shorthand',
type: 'boolean',
control: {
label: 'Remember me',
as: Checkbox,
defaultChecked: !!this.state.remember_me,
onChange: (e, { name, checked }) => { this.setState({ ...this.state, [name]: checked }) }
},
},
{
control: {
as: Button,
content: 'Submit',
},
key: 'submit',
},
]
handleSubmit = (e) => {
console.log("submitting these values", this.state)
}
render() {
return (
<Form
onSubmit={this.handleSubmit}
fields={this.fields}
/>
)
}
};
export default Login;
I have the following array. I am using this array to dynamically produce checkboxes on my UI. This is being used to save user config as to what they will be able to see in a nav menu.
accessLevels: any = [
{
description: "Properties",
type: '1',
selected: false
},
{
description: "Equipment",
type: '2',
selected: false
},
{
description: "Jobs",
type: '3',
selected: false
},
{
description: "Calender",
type: '4',
selected: false
}
]
I am making a call to an API which returns me an array of the users config. So what I will get is an array of the pages and their type like this:
{
description: "Equipment",
type: '2'
},
{
description: "Jobs",
type: '3'
}
In the array returned from the API I am just getting the values that should appear checked on the check boxes so what I want to do is loop through the returned array and check if any of the types match any types in the checkbox array if they do I want to set 'selected' to true. Thus checking the checkbox.
Here is what I have so far:
async loadLandlordConfig(key: string) {
const result = await this.userService.loadLandlordConfig(key);
//let accessLevels = [];
this.selectedApiValues = result[0].accessLevels;
this.selectedApiValues.forEach((selectedValue)=> {
});
}
Im not sure how to cross check the values and then change selected to true.
Hope I have made everything clear enough.
Any questions please ask. All help appreciated.
const accessLevels: any[] = [
{
description: 'Properties',
type: '1',
selected: false
},
{
description: 'Equipment',
type: '2',
selected: false
},
{
description: 'Jobs',
type: '3',
selected: false
},
{
description: 'Calender',
type: '4',
selected: false
}];
const results: any[] = [
{
description: 'Equipment',
type: '2'
},
{
description: 'Jobs',
type: '3'
}];
accessLevels.forEach(accessLevel => {
accessLevel.selected = results.some(x => x.type === accessLevel.type); });
For small arrays
You can check for the existence within a filter using some:
const intersect = this.array1.filter(a1 =>
this.array2.some(a2 => a1.type === a2.type));
The problem with this is that you are doing multiple loops through array 2.
For larger arrays
To keep your loops to a constant number, you could create a map of one of your arrays, and check that within a filter of the other array:
const map2 = new Map(this.array2.map(x => [x.type, s]));
const intersect = this.array1.filter(a1 =>
map.has(a1.type));
This adds a little bit of complexity, but is more efficient for all but the simplest cases.
You can achieve what you want with the following simple example :
let accessLevels = [
{
description: "Properties",
type: '1',
selected: false
},
{
description: "Equipment",
type: '2',
selected: false
},
{
description: "Jobs",
type: '3',
selected: false
},
{
description: "Calender",
value: '4',
selected: false
}
]
let api = [
{
description: "Equipment",
type: '2'
},
{
description: "Jobs",
value: '3'
}
];
for(var i = 0; i < accessLevels.length; i++) {
accessLevels[i].selected = api.find(e => e.description === accessLevels[i].description) ? true : false;
}
console.log(accessLevels);
You can use Map collection to have O(1) while mapping the second array:
const unique = new Map(received.map(s => [s.description, s]));
const result = accessLevels.map(({ description, type})=>
({selected: (unique.get(description) ? true : false), type, description}))
An example:
let accessLevels = [
{
description: "Properties",
type: '1',
selected: false
},
{
description: "Equipment",
type: '2',
selected: false
},
{
description: "Jobs",
type: '3',
selected: false
},
{
description: "Calender",
type: '4',
selected: false
}
]
const received = [
{
description: "Equipment",
type: '2'
},
{
description: "Jobs",
value: '3'
}
];
const unique = new Map(received.map(s => [s.description, s]));
const result = accessLevels.map(({ description, type})=>
({selected: (unique.get(description) ? true : false), type, description}))
console.log(result);
I've made some changes to your code. Here's how it looks like:
async loadLandlordConfig(key: string) {
const result = await this.userService.loadLandlordConfig(key);
// assuming there's always a result.
// ACCESS_LEVELS is the list of your config.
accessLevels = result[0].accessLevels;
this.selectedApiValues = ACCESS_LEVELS.map((al: any) => {
const selected = accessLevels.findIndex(a => a.type === al.type) > -1;
return { ...al, selected }
})
}
This will give you with the value
this.selectedApiValues = [
{
description: "Properties",
type: '1',
selected: false
},
{
description: "Equipment",
type: '2',
selected: true
},
{
description: "Jobs",
type: '3',
selected: true
},
{
description: "Calender",
type: '4',
selected: false
}
]
So I've done some reading on here and saw that most people recommended against modifying props. Thus, I was wondering if there's a way I can duplicate the prop?
Essentially, I want to duplicate the prop and set it to the state. More specifically, I'm creating a table and one of the props that is passed in is the headers which is an array of objects
headers={[
{id: "Name" , numeric: false, disablePadding: false, label: "Name"},
{ id: 'Weight', numeric: true, disablePadding: false, label: 'Weight' },
{ id: 'Height', numeric: true, disablePadding: false, label: 'Height' }
}]
I want to add a default column s.t. it'll look like
headers={[
{id: "Name" , numeric: false, disablePadding: false, label: "Name"},
{ id: 'Weight', numeric: true, disablePadding: false, label: 'Weight' },
{ id: 'Height', numeric: true, disablePadding: false, label: 'Height' },
{id: "Modify" , numeric: false, disablePadding: false, label: "Modify"}
]}
Thanks for your help! :-)
There are a few techniques you could use without using additional libraries
1. Set the initial state properly
this.state = { headers: this.props.headers.slice(0) }
When modifying the state use the callback technique
this.setState((previousState) => {
// mutate the state a return a new one.
});
How to use slice
Using setState properly
Array object is passed by reference. Instead you can create a new array and then dump the data into the state.
this.state = {
headers: (() => {
return [...this.props.headers, {id: "Modify" , numeric: false, disablePadding: false, label: "Modify"}]
})()
}
Create the component which you have to add.
let newObject = { "id": "Modify" , "numeric": false, "disablePadding": false, "label": "Modify" }
Now, create a duplicate along with the newObject which was created.
const headers = [...this.props.headers, newObject]
Now, set that headers to state variable header.
this.setState({
header: headers
})
I hope this will work for you.
Do this in constructor:
this.state: {fooCopy: this.props.foo};
if you want to store modified prop in state, then make a copy in a local var (try Object.assign or JSON.parse(JSON.stringify(a))), modify it, and then store in state.