This is created another objects dynamically when you click a button. But I want to create new object inside questions Array when I click a button.
const [inputFields, setInputFields] = useState([
{
sectionName: "",
sectionDesc: "",
questions: [{ questionType: "", questionText: "" }],
},
]);
const handleChange = (index: any, event: any) => {
const values = [...inputFields];
// #ts-ignore
values[index][event.target.name] = event.target.value;
setInputFields(values);
console.log(index, event.target.name);
};
const handleAddFields = () => {
setInputFields([
...inputFields,
{
sectionName: "",
sectionDesc: "",
questions: [{ questionType: "", questionText: "" }],
},
]);
};
First off, you are mutating state in your handleChange handler. You can avoid this using map()
const handleChange = (index: number, event: React.ChangeEvent<HTMLInputElement>) => {
setInputFields(prev => prev.map((p, i) => (
i === index
? {
...p,
[event.target.name]: event.target.value
}
: p
)));
};
As for adding to the questions array, you will need an index for which object you want to add to, then use the same technique as above to spread into that object.
const handleAddFields = (index: number) => {
setInputFields(prev => prev.map((p, i) => (
i === index
? {
...p,
questions: [...p.questions, { questionType: "", questionText: "" }]
}
: p)
));
};
Note: Using any for all parameters defeats the purpose of typing, try to use relevant types to take advantage of the type system.
Related
i have a issue, i have two inputs then and i can't permit the user save this edit if the two values is equals.
my state who contains a data from db
const [doctorRegisters, setDoctorResgisters] = useState<any>([]);
inside this array i contain this
[{"__typename": "DoctorMedicalRegister", "counsil": "CRM", "id": "141", "isMainRegister": true, "register": "1234567/RS"}, {"__typename": "DoctorMedicalRegister", "counsil": "CRM", "id": "153", "isMainRegister": false, "register": "1234567/RS"}]
and i need compare two register who if is equal, i cant permit user save he save just is different
here is a code who i try fix this
const isEquals = () => {
doctorRegisters.map((item: any) => {
if (item.register) {
doctorRegisters.map((item2: any) => {
if (item2.register) {
if (item.register === item2.register) {
console.log('iguais')
}
}
});
}
});
};
but this work only for dont edited values i need verify when this value is changed in input in this case i only verify in db this values is equals
here is my handle change
const handleEditRegisterCrm = (crm: string, id: number) => {
setDoctorResgisters(
doctorRegisters.map((item: any) => {
if (item && Number(item.id) == id) {
item.register = `${crm}/${item.register?.split('/')[1] || ''}`;
}
return item;
}),
);
};
You could do something like:
const handleEditRegisterCrm = (crm: string, id: number) => {
if (!doctorRegisters.some((doctorRegister) => doctorRegister.register.includes(registerToCompare)) {
setDoctorRegisters(
doctorRegisters.map((item: any) => {
if (item && Number(item.id) == id) {
item.register = `${crm}/${item.register?.split('/')[1] || ''}`;
}
return item;
}),
);
}
};
Remember you should keep track of the registerToCompare in order to find if it's already inserted in the doctorRegisters list. I'm assuming you can obtain that value from the your handleChange function.
I have array of objects where i need to an key value
useState :
const [row, setRow] = useState([{ nameofthework: "", schedulerefNo: "", unitprice: "", qty: "", uom: "", gst: "", total: "" }]);
The form input change function
const handleInputChange = (e, index) => {
const { name, value } = e.target;
const list = [...row];
list[index][name] = value;
setRow(list);
console.log(row); // Prints out the each row object into and array of objects
};
const handleQty = (e) => {
const scheduleNumberForQuantity = e.target.value;
projectScheduleNumber?.filter((v) => {
if(v["Schedule No"] === scheduleNumberForQuantity ) {
setScheduleQuantity(v["LOA Qty"]); // return only integer example 1000
}
})
}
How to add handleQty value to row state ?
I think that you need declare the state like a function instead of array of objects key value. It will allow setRow to be managed as function in any moment inside of the function component.
const [row, setRow] = useState<any>();
With <any> allows save anything. So you could apply a strong type like:
const [row, setRow] = useState<()=>void>();
or in you case
const [row, setRow] = useState<(e: any)=>void>();
I hope it is useful for you.
I'm trying to create a set of reducers in order to change an attribute of all objects in a nested list.
The input payload looks like the following:
const payload = [
{
name: "Peter",
children: [
{
name: "Sarah",
children: [
{
name: "Sophie",
children: [
{
name: "Chris"
}
]
}
]
}
]
}
];
I now want to change the name attribute of all elements and child elements.
const mapJustNickname = elem => {
return {
...elem,
nickname: elem.name + "y"
};
};
How do I use this map function recursively on all child elements?
I found a way to do this by putting the the recursion within the same mapping function.
const mapToNickname = (elem) => {
return {
nickname: elem.name +'y',
children: elem.children && elem.children.map(mapToNickname)
}
}
console.log(payload.map(mapToNickname));
But I'd like to have the mapping of the name separated from the recursion (for reasons of keeping the mapping functions as simple as possible) and being able to chain them later. Is it somehow possible to do this with two reducers and then chaining them together?
Let's start by rigorously defining the data structures:
data Person = Person { name :: String, nickname :: Maybe String }
data Tree a = Tree { value :: a, children :: Forest a }
type Forest a = [Tree a]
type FamilyTree = Tree Person
type FamilyForest = Forest Person
Now, we can create mapTree and mapForest functions:
const mapTree = (mapping, { children=[], ...value }) => ({
...mapping(value),
children: mapForest(mapping, children)
});
const mapForest = (mapping, forest) => forest.map(tree => mapTree(mapping, tree));
// Usage:
const payload = [
{
name: "Peter",
children: [
{
name: "Sarah",
children: [
{
name: "Sophie",
children: [
{
name: "Chris"
}
]
}
]
}
]
}
];
const mapping = ({ name }) => ({ name, nickname: name + "y" });
const result = mapForest(mapping, payload);
console.log(result);
Hope that helps.
Create a recursive map function that maps an item, and it's children (if exists). Now you can supply the recursiveMap with a ever transformer function you want, and the transformer doesn't need to handle the recursive nature of the tree.
const recursiveMap = childrenKey => transformer => arr => {
const inner = (arr = []) =>
arr.map(({ [childrenKey]: children, ...rest }) => ({
...transformer(rest),
...children && { [childrenKey]: inner(children) }
}));
return inner(arr);
};
const mapNickname = recursiveMap('children')(({ name, ...rest }) => ({
name,
nickname: `${name}y`,
...rest
}));
const payload = [{"name":"Peter","children":[{"name":"Sarah","children":[{"name":"Sophie","children":[{"name":"Chris"}]}]}]}];
const result = mapNickname(payload);
console.log(result)
I've got these Interfaces:
export interface QueryObject {
id: ID;
path: string[];
filters: Filters;
}
export interface Filters {
state: string;
localeID: string;
role: string;
}
And trying to come up with a Functional Programming solution in order to just copy the properties that exist and have a value from a tmp object to the existing data model. Now,.. obviously this doesnt work. Filters will be completely overwritten while losing the properties localeID and role.
let queryObject: QueryObject;
let filters: Filters = { state: 'GB'}; // this obviously gives an error on Interface implementation
queryObject.filters = filters;
Right now I'am taking the original object, traversing to the property and overwriting it with the updated value.
const queryObject: QueryObject = _.cloneDeep(this.queryObject);
queryObject.filters.state = state; // 'GB'
this.portareService.update(queryObject, this.portareQuery.getActiveId());
Would be nice to solve this with a Object.assign or spread ... solution like:
{
return ...createQueryObject, updatedQueryObject
}
I know how to do this with a function using loop(s), but looking for a Functional Programming approach to this.
You could implement a concat method for both QueryObject and Filters. In the concat, you define what "merge logic" you want to use. The QueryObject calls the Filters' concat method internally.
Within the concat methods, you can use the spread syntax or any other logic to ensure new objects are created and you're not mutating anything.
By adding an empty constructor, you can easily start using those concatenators inside a reduce or other automated merge.
I found this blog post on Semigroups by Tom Harding super inspiring. This post about Monoids has some info about the empty part in it.
const QueryObject = ({id = null, path = null, filters = Filters.empty() })=> ({
id,
path,
filters,
concat: other => QueryObject({
id: other.id || id,
path: other.path || path,
filters: filters.concat(other.filters)
}),
toString: () => `QueryObject(${id}, ${path}, ${filters.toString()})`
});
QueryObject.empty = () => QueryObject({});
QueryObject.merge = (x, y) => x.concat(y);
const Filters = ({ state = null, localeID = null, role = null }) => ({
state,
localeID,
role,
concat: other => Filters({
state: other.state || state,
localeID: other.localeID || localeID,
role: other.role || role
}),
toString: () => `Filters(${state}, ${localeID}, ${role})`
});
Filters.empty = () => Filters({});
Filters.merge = (x, y) => x.concat(y);
const userFilter = Filters({ role: "User" });
const gbFilter = Filters({ localeID: "GB" });
const filterSettings = [userFilter, gbFilter];
const mergedFilter = filterSettings.reduce(Filters.merge, Filters.empty());
console.log(
"Merged Filter:",
mergedFilter.toString()
);
// Some base query
const accountQuery = QueryObject({ id: "CUSTOM_Q_1", path: "/accounts" });
// Derived queries
const userQuery = accountQuery.concat(QueryObject({ filters: userFilter }));
const gbQuery = accountQuery.concat(QueryObject({ filters: gbFilter }));
console.log(
"User Query:",
userQuery.toString()
);
console.log(
"Brittish Users Query",
userQuery.concat(gbQuery).toString()
);
Edit:
Of course, without the "theory", there's also the more generic:
const uniques = xs => Array.from(new Set(xs));
const nullMergeStrategy = (obj1, obj2) =>
uniques(
Object.keys(obj1)
.concat(Object.keys(obj2))
).reduce(
(acc, k) => Object.assign(acc, { [k]: obj2[k] || obj1[k] }),
{}
);
const Filter = ({ state = null, localeID = null, role = null }) =>
({ state, localeID, role });
const userFilter = Filter({ role: "User" });
const gbFilter = Filter({ localeID: "GB" });
console.log(
nullMergeStrategy(userFilter, gbFilter)
)
I have a piece of code, using cycle.js and the reactive xstream library, like the one shown below. It consists of a form field whose value is rendered in a p tag on submit. The questions follow:
1. How can the form's input field be reset to the default value when the form is submitted?
2. Is there a way to reset the input$ and submit$ streams to their initial values after submit?
function formIntent (sourcesDOM) {
const fNameInput$ = sourcesDOM
.select('form#user .fname')
.events('input')
.compose(debounce(1000))
.map((e: {target: any}) => e.target.value)
const submit$ = sourcesDOM
.select('form#user')
.events('submit')
.map((e) => e.preventDefault())
.mapTo({type: 'USER'})
const actions$ = xs.combine(fNameInput$, submit$)
const initState = {fNameInput: ''}
return actions$
.map(([fNameInput, {type}]) => {
return type === 'USER' ? {fNameInput} : initState
})
.startWith(initState)
}
function formView (state$) {
return state$
.map(({fNameInput}) =>
div([
form('#user', [
label('Name'),
hr(),
input('.fname', {attrs: {id: 'first-name', type: 'text', placeholder: 'First name'}}),
hr(),
input('.sub', {attrs: {type: 'submit', value: 'Save'}})
]),
div([
h2('Submitted value'),
p(fNameInput),
hr()
])
])
)
}
Is there a better way to create a form like this?
P.S. the output of formIntent function is fed into the input of the formView function.
This is what I ended up doing. It does what I wanted. I will still be interested to know a better way of achieving the same goal.
function formIntent (sourcesDOM) {
const fNameInput$ = sourcesDOM
.select('form#user .fname')
.events('input')
.compose(debounce(1000))
.filter((e: {target: any}) => e.target.value.length > 2)
.map((e: {target: any}) => e.target)
const submit$ = sourcesDOM
.select('form#user')
.events('submit')
.map(e => {
e.preventDefault()
console.log('3: Inside intent', e)
return {user: 'CLICKED'}
})
return xs.combine(fNameInput$, submit$)
}
function formModel (actions$) {
const initState = {user: '', fNameInput: {}}
return actions$
.map(([fNameInput, user]) => {
const fName = fNameInput.value
if (user.user === 'CLICKED') {
fNameInput.value = fNameInput.defaultValue
user.user = 'DISPLAY'
return {user: user.user, fNameInput: fName}
} else return initState
})
.startWith(initState)
}
function formView (state$) {
return state$
.map(({user, fNameInput}) =>
div([
form('#user', [
label('Name'),
hr(),
input('.fname', {attrs: {id: 'first-name', type: 'text', placeholder: 'First name'}}),
hr(),
input('.sub', {attrs: {type: 'submit', value: 'Save'}})
]),
(user === 'DISPLAY') && div([
h2('Submitted value'),
p(fNameInput),
hr()
])
])
)
}
function main (sources) {
const actions$ = formIntent(sources.DOM)
const state$ = formModel(actions$)
const vtree$ = formView(state$)
return {
DOM: vtree$
}
}
You can use hook for updating input value when DOM refreshed. So if you use #cycle/dom version >11.0 which uses Snabbdom you can use as this:
input({
hook: {
update: (o, n) => n.elm.value = ''
}
})
Where empty string you can pass default value which you want. It will refresh input value every time when state updates.