populate cards and modals via same json file using react(-bootstrap)? - react-hooks

The data from workData fills <Card></Card> correctly.
The <Modal></Modal> only fills with the last entry of workData (e.g. Test4, Modal4, test text 4...)
my goal is to generate cards and respective modals (for each card) using the data from the json, in the same file.
why is the modal only being filled by the last properties in the json? how do i get it to populate with the entire array? if possible please explain why this does not work the way it is.
if it's not obvious im super new, i am, any responses would be super appreciated. ty
cards good
after clicking "Read1" bad, should say Test1, test text 1
in App.js: import { Works } from "./Works";
in Works.js: import { workData } from "./data";
also in Work.js:
export const Works = () => {
const [show, setShow] = React.useState(false);
const onClick = () => setShow(true);
return (
<>
<div className="work-container">
<Row xs={1} md={2} lg={4} className="g-4">
{workData.map((data, key) => {
return (
<div key={key}>
<Col>
<Card>
<Card.Img variant="top" src={data.projectImage} />
<Card.Body>
<Card.Title>{data.projectTitle}</Card.Title>
<Card.Text>with {data.projectTeam}</Card.Text>
<Button variant="link" onClick={onClick}>
{data.readMore}
</Button>
</Card.Body>
<Card.Footer>{data.tags}</Card.Footer>
</Card>
</Col>
<Modal
show={show}
onHide={() => setShow(false)}
dialogClassName="modal-95w"
>
<Modal.Header closeButton>
<Modal.Title>{data.projectTitle}</Modal.Title>
</Modal.Header>
<Modal.Body>
<Image src={data.projectImage}></Image>
<p>
{data.modalText}
</p>
<Image src={data.modalImage}></Image>
</Modal.Body>
</Modal>
</div>
);
})}
</Row>
</div>
</>
);
}
in data.js:
export const workData = [
{
projectTitle: "Test1",
modalTitle: "Modal1",
modalText: "test text 1",
modalImage: "image",
readMore: "Read1",
projectImage: "image",
projectTeam: "Test1",
year: "2022",
link1: "link",
link2: "link2",
tags: [
"#tag1 ",
"#tag2 "
]
},
...
The data from workData fills <Card></Card> correctly.
The <Modal></Modal> only fills with the last entry of workData (e.g. Test4, Modal4, test text 4...)
my goal is to generate cards and respective modals (for each card) using the data from the json, in the same file.
why is the modal only being filled by the last properties in the json? how do i get it to populate with the entire array? if possible please explain why this does not work the way it is.
cards good
after clicking "Read1" bad, should say Test1, test text 1

You iterate over workData for Cards and Modals, but you use only one single state for everything. What you need to do, is to also create a state for every Modal. Usually you create an array with unique id as key and boolean value. I assumed projectTitle is unique:
{
Test1: false,
Test2: false,
Test3: false
}
Because you don't know the length of your data, you just iterate over the array, as you have done for Cards und Modals:
const initialShowState = Object.fromEntries(
workData.map((data) => [data.projectTitle, false])
);
const [show, setShow] = React.useState(initialShowState);
Then you need to create a generic callback function, which takes the id of the Card and shows the appropriate Modal. I simplified the logic and created a toggle function:
const toggleShow = (id) =>
setShow((prev) => {
return { ...prev, [id]: !prev[id] };
});
Finally, when you render UI and iterate over workData, you need to apply the callback function to Button onClick and Modal onHide event handlers and set the show property of Modal:
<Button variant="link" onClick={() => toggleShow(data.projectTitle)}>
...
<Modal
show={show[data.projectTitle]}
onHide={() => toggleShow(data.projectTitle)}
dialogClassName="modal-95w"
>
That's it. Here is the working sandbox: https://codesandbox.io/s/hungry-sunset-t865t3
Some general tips:
You don't need the outer Fragment in Works as you only have one upper most element
If you use JSX syntax in your file, your extension should be .jsx and not.js (Works.jsx)
Using index as key in the list is bad practice. Find some unique id in your data

Related

Calling a huge object inside v-for causing massive lag?

recently I'm working on a project showing sport events data
While v-for through the events array (which is around 500~2000 length), I'll call a method in each to show their participants name
<div v-for="event in eventArr" :key="event.id">
<template v-if="mappingObj[event.id]">
<div class="participantName">
{{ getCorrespondParticipants (
mappingInfo[event.id][mappingId],
mappingInfo[event.id][sportId])
}}
</div>
</template>
</div>
what it looks like in template, mappingObj is a object collecting all event data with event id as key, its length is around 500~2000(same as the origin events array)
getCorrespondParticipants (pidArr, sportId) {
this.participantListObj[sportId];
});
// this part was supposed to get corresponded participants data from the object, returning this.participantListObj[sportId][pid in pidArr]
return pArr;
},
The method was trying to get the participants info, tho even if I remove all the lines just left 'this.participantListObj[sportId]' there(so it basically did nth but calling the obj), it still causing massive lag result to slow performance for my page, not to mention there's another method to get the league info
participantListObj () {
let cloneObj = JSON.parse(
JSON.stringify(this.totalParticipantList)
);
for (let sportId in cloneObj) {
cloneObj[sportId] = cloneObj[sportId].reduce(
(pObj, item) =>
Object.assign(pObj, { [item.participant_id]: item }),
{}
);
}
return cloneObj;
},
which would looks like...
participantListObj :{
1: {
123:{
participant_id: 123
name_EN: 'Player',
name_CN: '玩家'
},
...(18000+)
}
}
The obj is participants list object under each sport Id, made from a participants array list which length could be around 18000, in this case the page has only one sport that is soccer. Did I did anything wrong?

Duplicate Dynamic Select Box using Angular 6+ Form Array

Select Box 1 is "Path".
Select Box 2 is "SkillSet".
I'm trying to duplicate group of select boxes(Path and SkillSet) based on a button click. So when I click Add button the select box(Path and SkillSet) form element will be duplicated. The catch here is the select box "Skillset" element's options are dynamic because its depends on the select box "Path".
Issue Below:
Step1: Choosing Path as BackEnd and Skill will be populated based on the Path. In the second select box selected as Java8.
Step2: Clicking Add button, so the select box Path and Skill is duplicated. Now choosing select box Path as FrontEnd.
Step3: After choosing Path as FrontEnd in the second row, the selected Skill in first row's are reseted to empty. (In the image I have added two Path's)
StackBlitz Demo for the Issue:
https://stackblitz.com/edit/angular-duplicate-dynamic-select-box?file=null
Expectation is : I have to select each Path and respective Skill. Like If I choose 3 different path, then I have to choose 3 different skills in the 3 different row of select boxes.
I have tried many solutions. Nothing is working out. Can someone help in this case.?
Sorry for my English and bad formatting. Appreciate your help !!!
You can Push the skillsets for the selected path into an array and access them in the HTML file using the index.
In .ts File
import { Component } from '#angular/core';
import { FormGroup, FormArray, FormBuilder, Validators } from '#angular/forms';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
skillSetObj;
majorPathObj;
skillForm: FormGroup;
skillList: FormArray;
choosenPath;
skillsForSelectedPath:any = []; // <--- Declare a new array for the skillsets to be pushed
constructor(private fb:FormBuilder) {
}
ngOnInit() {
this.skillSetObj = {
"BackEnd": ["Java8", "Node JS", "Python", "Dotnet"],
"FrontEnd": ["Javascript ", "Angular ", "React", "Vue"],
"Database": ["Oracle", "Postgres", "Mysql"]
};
this.majorPathObj = ["BackEnd", "FrontEnd", "Database"];
this.skillForm = this.fb.group({
skillFormArray: this.fb.array([this.createSkills()])
});
this.skillList = this.skillForm.get('skillFormArray') as FormArray;
}
createSkills(): FormGroup {
return this.fb.group({
majorPath: ['', Validators.compose([Validators.required])],
skillSet: ['', Validators.compose([Validators.required])]
});
}
getSkillFormGroup(index): FormGroup {
const formGroup = this.skillList.controls[index] as FormGroup;
return formGroup;
}
get skillFormGroup() {
return this.skillForm.get('skillFormArray') as FormArray;
}
addNewSkill() {
this.skillList.push(this.createSkills());
}
removeSkill(skillRowIndex) {
this.skillList.removeAt(skillRowIndex);
}
prepareSkillSet(event, i) {
this.skillsForSelectedPath[i]=this.skillSetObj[event.value]; // <--- Push the skills for the selected majorPath into the new array
const formGroup = this.getSkillFormGroup(i);
const choosenPath = formGroup.controls['majorPath'].value;
this.choosenPath = choosenPath;
}
}
** In HTML file **
<form [formGroup]="skillForm">
<div formArrayName="skillFormArray">
<div *ngFor="let skillArray of skillFormGroup.controls; let i=index">
<div [formGroupName]="i">
<div >
<mat-form-field appearance="outline">
<mat-select formControlName="majorPath"
(selectionChange)="prepareSkillSet($event, i)">
<mat-option *ngFor="let major of majorPathObj" value={{major}}>
{{major}}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-select formControlName="skillSet">
<mat-option *ngFor="let skill of skillsForSelectedPath[i]" [value]="skill"> <!-- display the skills for the selected majorPath using the index of the newly created variable -->
{{skill}}
</mat-option>
</mat-select>
</mat-form-field>
<button *ngIf="i===0" mat-fab color="accent" class="add-file-button mt-5"
(click)="addNewSkill()" aria-label="Add Skill">
<mat-icon>add</mat-icon>
</button>
<button *ngIf="i!==0" mat-fab color="warn" class="add-file-button"
(click)="removeSkill(i)" aria-label="Remove Skill">
<mat-icon>remove</mat-icon>
</button>
</div>
</div>
</div>
</div>
</form>
So everytime the majorPath is the skills object is also updated and you can select the corresponding skills for the newly selected majorPath.
The output looks like below

Redux-form validation breaks when using multiple components with the same form name

I run into the validation issue using multiple components decorated with same form name.
Let's say we have SimpleForm1 and SimpleForm2. When rendering Only SimpleForm1 with the name field validation works as expected, as well as when rendering SimpleForm2 with the surname field. But when rendering them both on a single page validation for SimpleForm1 is broken.
The question is how to avoid such behaviour and make both validation functions work.
Here is a fiddle which illustrates my problem
It's not a good idea to use same names for multiple forms.
As i understand you need to dynamically add form inputs(SimpleForm2 in your example) and have possibility to submit both forms with one button.
If yes, so you can add just an input to first form, you don't need second form.
Form:
const SimpleFormComponent1 = props => {
const {handleSubmit, pristine, reset, submitting, renderBoth} = props;
const onSubmit = (values) => {
alert(JSON.stringify(values));
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
...
{
renderBoth &&
<Field
name="surname"
type="text"
component={renderField}
label="Surname"
validate={validateSurname}
/>
}
...
</form>
)
};
Render function:
render() {
const {renderBoth} = this.state;
return (
<div>
<div className='forms'>
<SimpleForm renderBoth={renderBoth}/>
</div>
<button
type='button'
onClick={this.handleClick}>
{renderBoth ? 'Show Single' : 'Show Both'}
</button>
</div>
);
}
Updated example
SOLUTION 1
I was having interference with invalid and valid props of the Redux Form because I wrote the validate function on ReduxForm() in every file of my main form component.
You haven't to change the form name to solve this. You have to put the validation function on the parent component.
Here is an example:
[CONTEXT]
I have 3 Components:
1. Main component to put the form ('editUserForm') elements (, , ...)
2. Field1 of the form ('editUserForm') that changes user's complete name.
3. Field2 of the form ('editUserForm') that changes user's email.
[SOLUTION]
MAIN COMPONENT:
Inside the main frame, you call reduxForm() (it creates a decorator with which you use redux-form to connect your form component to Redux. More info here Redux Form docs). Your code would be like this:
import...
class MainFrame ... {
...
<form ...>
<Field1 />
<Field2 />
</form>
...
}
const validate ({ name, email }, props) => {
errors={}
// Validation around Field1
if (name === ...) errors.name = "Name error passed to Field1 component";
// Validation around Field2
if (email === ...) errors.email= "Email error passed to Field2 component";
return errors;
}
...
export default reduxForm({
form: 'editUserForm',
validate // <--------- IMPORTANT: Only put this function on the parent component.
})(MainComponent);
FIELD1 & FIELD2 COMPONENTS:
This code is for the 2 children components. IMPORTANT: You call reduxForm() without validate Redux Form docs synchronous function.
import...
class MainFrame ... {
...
const { invalid } = this.props;
...
<inputs
error={invalid}
>
...
</input>
...
}
// IMPORTANT: Don't put the validation function in Field1 and Field2 components, because their local validations will interfere with the validation of the other Field component.
reduxForm({
form: 'editUserForm'
})
Now, the props: valid and invalid will work perfectly inside the children components (Field1 and Field2).
SOLUTION 2
User Redux Form FormSection (docs) to split forms into smaller components that are reusable across multiple forms.

Output user first name

I want to get the name of the user to put it on an h1.
What dies this line stand for?
#select="option => selected = option">
I'm using Buefy for the vue components.
<template>
<section>
<div class="field">
<b-switch v-model="keepFirst">
Keep-first <small>(will always have first option pre-selected)</small>
</b-switch>
</div>
<p class="content"><b>Selected:</b> {{ selected }}</p>
<b-field label="Find a name">
<b-autocomplete
v-model="name"
placeholder="e.g. Anne"
:keep-first="keepFirst"
:data="filteredDataObj"
field="user.first_name"
#select="option => selected = option">
</b-autocomplete>
</b-field>
</section>
</template>
<script>
import data from '#/assets/data_test.json'
// Data example
// [{"id":1,"user":{"first_name":"Jesse","last_name":"Simmons"},"date":"2016-10-15 13:43:27","gender":"Male"},
// {"id":2,"user":{"first_name":"John","last_name":"Jacobs"},"date":"2016-12-15 06:00:53","gender":"Male"},
// {"id":3,"user":{"first_name":"Tina","last_name":"Gilbert"},"date":"2016-04-26 06:26:28","gender":"Female"},
// {"id":4,"user":{"first_name":"Clarence","last_name":"Flores"},"date":"2016-04-10 10:28:46","gender":"Male"},
// {"id":5,"user":{"first_name":"Anne","last_name":"Lee"},"date":"2016-12-06 14:38:38","gender":"Female"}]
export default {
data() {
return {
data,
keepFirst: false,
name: '',
selected: null
}
},
computed: {
filteredDataObj() {
return this.data.filter((option) => {
return option.user.first_name
.toString()
.toLowerCase()
.indexOf(this.name.toLowerCase()) >= 0
})
}
}
}
</script>
# is shorthand for v-on:, so it's handling a select event with a function that receives option as a parameter and assigns it to selected.
Since v-model is bound to name, you should be able to do <h1>{{name}}</h1> to have the same value show up in an H1.
The data section has the main variables for your object. name is there. There is also a computed (named filteredDataObj) that should return an array (length of zero or one) with the matching test data. If you want other fields (like id) you would need to look there. Something like
{{filteredDataObj.length ? filteredDataObj.id : ''}}
would give the id if name matched anything in the data set.

What is the good way to have a multi input field in Redux Form?

In my project we are building a form with React and Redux-Form. We have a single information that is composed by the value of two inputs. But the values of each input is combined and validated together.
The first implementation try was by connecting each component with Field. It let us update the state properly but we couldn't validate all the values together with the validate prop.
The second Try was using Fieldscomponent but it does not have a validate prop. We thought in create a Pull Request for it but the API for it isn't clear yet, since what we want to validate the combination of the two values and the behavior of the Fields props (such as parse and format) is different, executing the function for each input inside Fields component separately.
I know it is possible to create a component and use Field to connect with the application state, but I didn't want to manage things as the touched prop, or the callbacks to update the state, or other things that I even have noticed, since Redux-Form has all of it done.
The fact is that I end up with an implementation but it didn't looked very elegant. I'd like you to take a look at the implementation and give your opinion, sugest other solutions and even if this solution is not implemented in Redux-Form yet we could maybe open a pull request for that.
Here is an example implementation
Simple form container
import SimpleForm from 'app/simpleForm/components/simpleForm'
import { reduxForm } from 'redux-form'
export default reduxForm({
form: 'simpleForm'
})(SimpleForm)
Simple form component
import React from 'react'
import { Field } from 'redux-form'
import MultiInputText from 'app/simpleForm/components/multiInputText'
const onSubmit = values => alert(JSON.stringify(values))
const validateAddress = (value) => {
if (!value) return 'Address is empty'
if (!value.street) return 'Street is empty'
if (!value.number) return 'Number is empty'
return null
}
const SimpleForm = ({ handleSubmit }) => {
return (
<form onSubmit={ handleSubmit(onSubmit) }>
<Field label="Home Address" name="home" component={MultiInputText} type="text" validate={validateAddress}/>
<Field label="Work Address" name="work" component={MultiInputText} type="text" validate={validateAddress}/>
<button type="submit">Submit</button>
</form>
)
}
export default SimpleForm
MultiInputText component
import React from 'react'
import { Fields, FormSection } from 'redux-form'
const renderAddreessInputs = ({ street, number, error }) => (<div>
<input {...street.input} type="text" />
<input {...number.input} type="text" />
{ street.meta.touched && number.meta.touched && error && <span className="error">{error}</span> }
</div>)
const MultiInputText = (props) => {
const { input: { name }, label, meta: { error }} = props
const names = [
'street',
'number'
]
return (<div>
<label htmlFor={name}>{label}</label>
<FormSection name={name}>
<Fields names={names} component={renderAddreessInputs} error={error}/>
</FormSection>
</div>)
}
export default MultiInputText
I see two options:
1) Use record-level validation.
reduxForm({
form: 'addressForm',
validate: values => {
const errors = {}
if(!home) {
errors.home = 'Address is empty'
}
// etc, etc. Could reuse same code for home and work
return errors
}
})
2) Create a single input that handles a complex value.
<Field name="home" component="AddressInput"/>
...
const AddressInput = ({ input, meta }) =>
<div>
<input
name={`${input.name}.street`}
value={(input.value && input.value.street) || ''}
onChange={event => input.onChange({
...input.value,
street: event.target.value
})}/>
...other inputs here...
</div>
That's total pseudocode, but I hope it gets the point across: a single input can edit a whole object structure.
Personally, I'd choose Option 1, but I prefer record-level validation over field-level validation in general. The nice thing about Option 2 is that a single AddressInput could be reused across the application. The downside is that you don't get specific field-level focus/blur/dirty/pristine state.
Hope that helps...?

Resources