'Target map()'ed items one by one
I want to create a contact form where the form fields are changed after they are filled. For example when you fill the email field, you press enter or click the Next button it disappears and the next field appears. There will be different form field types (text, email, select, etc). What i want to achieve is based on this form, but it is coded in vanilla js and i want to write it in React from scratch.
For now my basic code is this:
ContactForm.js file:
import { useState, useEffect } from "react";
import Fields from "./Fields";
const ContactForm = ({ fields }) => {
const [current, setCurrent] = useState(0);
const [show, setShow] = useState(true);
return (
<div className='container'>
<div className='fs-form-wrap' id='fs-form-wrap'>
<Fields fields={fields} isCurrent={current} />
<button onClick={() => setCurrent(current + 1)}>Continue</button>
</div>
</div>
);
};
Fields.js file:
import { useState } from "react";
import Field from "./Field";
const Fields = ({ fields, isCurrent }) => {
return (
<form id='myform' className='fs-form fs-form-full'>
<ol className='fs-fields'>
{fields.map((field, index) => {
return (
<Field
placeHolder={field.placeholder}
title={field.title}
type={field.type}
key={index}
isCurrent={isCurrent}
/>
);
})}
</ol>
</form>
);
};
export default Fields;
Field.js file:
import { useState, useEffect } from "react";
import styled from "styled-components";
const Field = ({ placeHolder, type, title, isCurrent }) => {
return (
<TheField isCurrent={isCurrent}>
<label className='fs-field-label fs-anim-upper' htmlFor='q2'>
{title}
</label>
<input
className='fs-anim-lower'
id='q2'
name='q2'
type={type}
placeholder={placeHolder}
required
/>
</TheField>
);
};
export default Field;
const TheField = styled.li`
visibility: ${(props) => (props.isCurrent === 0 ? "visible" : "hidden")};
`;
Based on this code, initially i get my 2 fields which are coming from my dummy-data.json
file but when i click on the button, both of them disappear.
I know that my code is still a mess, but i first want to make them appear one by one, then i think i know the logic for the other parts.
Any help would be appreciated.
EDIT with the solution from @zergski below:
import { useState, useEffect } from "react";
import Field from "./Field";
import { BidirectionalIterator } from "./Iterator";
const ContactForm = ({ fields }) => {
const [current, setCurrent] = useState(0);
const [show, setShow] = useState(true);
const options = { startIndex: 0, loop: false, clamp: true };
const list = new BidirectionalIterator(fields, options);
return (
<div className='container'>
<div className='fs-form-wrap' id='fs-form-wrap'>
<form id='myform' className='fs-form fs-form-full'>
<ol className='fs-fields'>
<li>{list}</li>
{/* {fields.map((field, index) => {
return (
<Field
placeHolder={field.placeholder}
title={field.title}
type={field.type}
key={index}
/>
);
})} */}
</ol>
{/* <Submit clickHandler={submitClickHandler} text={submitText} /> */}
</form>
<button onClick={() => list.next()}>Continue</button>
</div>
</div>
);
};
Solution 1:[1]
you need to create your own iterator... either through the use of generators or a custom class.. here's one I've written
export class BidirectionalIterator {
// TODO: support for other data types
// #iterate_over: string = 'index'
static index: number = 0
static start_index: number = 0
static looped: boolean = false
static clamped: boolean = true
static data: PropertyKey[]
static ct: number = 0
static data_len: number = 0
/** Only supports iterables for now.
* @param data - the object to be iterated
* @param options.startIndex - A negative value of less than 0 sets the index at the end of the iterable
* @param options.loop - loop to the opposite end of iterable (overrides the clamp option setting)
* @param options.clamp - iterator won't finish upon reaching iterable bounds
*
* @caution - DO NOT use a for of/in loop on the iterator if either the loop option is set to true!
*/
constructor(data: any[] = [], { startIndex = 0, loop = false, clamp = true }: BidirectionalIteratorOptions = {}) {
BidirectionalIterator.setData(data)
BidirectionalIterator.start_index = startIndex
BidirectionalIterator.clamped = loop ? false : clamp
BidirectionalIterator.looped = loop
}
static [Symbol.iterator]() {
return this
}
static setData(data: any) {
this.data = data
// TODO: finish support for different collection types
let [ct, data_len] =
data instanceof Array ? [1, data.length]
: data instanceof Map ? [2, data.size]
: data instanceof Set ? [3, data.size]
: [4, -1]
this.ct = ct
this.data_len = data_len
this.resetIndex()
}
static resetIndex() {
this.setIndex(this.start_index < -1 ? this.len : this.start_index)
}
static setIndex(idx: number) {
this.index = idx - 1
}
static get len(): number {
return this.data.length
}
// ! well I'm definitely repeating myself....
static get entry() {
this.index = num_between(this.index, 0, this.len - 1)
return {
index: this.index,
value: this.data[this.index]
}
}
static get next_entry() {
this.index = num_between(this.index, 0, this.len - 1)
return {
index: this.index + 1,
value: this.data[this.index] || (this.looped ? this.data[0] : null)
}
}
static get prev_entry() {
this.index = num_between(this.index, 0, this.len - 1)
return {
index: this.index - 1,
value: this.data[this.index + 1] || (this.looped ? this.data[this.len - 1] : null)
}
}
static next() {
let value, done
(done = this.index >= this.len)
? this.index = this.len
: done = ++this.index >= this.len
// value = this.data[done ? this.len-1 : this.index]
value = this.data[num_between(this.index, 0, this.len)]
if (done)
this.looped
? value = this.data[this.index = 0]
: this.clamped
? value = this.data[this.len - 1] : null
return {
index: this.index,
value,
done
}
}
static prev() {
let value, done
(done = this.index <= -1)
? this.index = -1
: done = --this.index <= -1
// value = this.data[done ? 0 : this.index]
value = this.data[num_between(this.index, 0, this.len)]
if (done)
this.looped
? value = this.data[this.len - 1]
: this.clamped
? value = this.data[0] : null
return {
index: this.index,
value,
done
}
}
}
so to use it just instantiate the class..
const list = new BidirectionalIterator(data_array, options)
// and use with .next() & .prev() methods on mouse input
// e.g this will return the next entry in given array
list.next()
it's written in typescript though, so you need to remove all the type declarations
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 |