In the attached picture, I want a different component to be added below the 'Choose Round Type' option based on the round type selected.
Adding to a single round is no issue. But when I add a new round and try to do the same for it, the components are essentially the same thing but repeated.
As you can see here, both of the forms are either visible or not.
I am following a wrong approach wherein I just add the form component to the mapped round and because the form is same for every round, it connects to all of the forms of the other rounds.
What I want is that I should be able to select different round types and get the corresponding input fields in every round.
As visible, once I click on a different round type in the second round, the first one also changes.
The Add a Round functionality is made using AntDesign Components.
What I tried to do:
import React, { useState } from "react";
import { useStore } from "react-redux";
import { Input, Radio, Form, Button, Space } from 'antd';
import { MinusCircleOutlined, PlusOutlined } from '#ant-
design/icons';
import "./createQuizPage.css";
const RoundTypeScheme = ({ type, count }) =>
{
console.log(type, count);
try
{
switch(type)
{
case "Pounce":
return (
<div id = {count}>
<label>Marks for correct answer (Direct)
</label>
<Input size = "small" />
<label>Marks for incorrect answer (Direct)
</label>
<Input size = "small" />
<label>Marks for correct answer (Pounce)
</label>
<Input size = "small" />
<label>Marks for incorrect answer (Pounce)
</label>
<Input size = "small" />
<hr />
</div>
);
case "Pounce + Bounce":
case "Differential":
case "Buzzer":
case "Long Visual Connect":
default:
return (<></>)
}
}
catch(err)
{
console.log(err);
}
}
const CreateQuizPage = () =>
{
const [type, setType] = useState("");
const [count, setCount] = useState(0);
const store = useStore();
let quiz_name = store.getState().quiz;
const quiz_rounds = ["Preliminary", "Main"]
const quiz_sub_rounds = ["Pounce", "Pounce + Bounce", "Buzzer", "Differential", "Long Visual Connect"]
const roundScores = (e) =>
{
setType(e.target.value);
}
const addRound = () =>
{
setCount(prev => (prev + 1));
}
return (
<div id = "createQuizPage">
<Form
name="basic"
className = "createQuizPage__form"
>
<h1>Name: {quiz_name}</h1>
<Form.Item>
<label>Choose Quiz Type </label>
<Radio.Group
options = {quiz_rounds}
optionType = "button"
buttonStyle = "solid"
className = "createQuizPage__type-button"
></Radio.Group>
</Form.Item>
<Form.List name="users">
{(fields, { add, remove }) => (
<>
{fields.map(({ key, name, fieldKey, ...restField }) => (
<Space key={key} align="baseline" className = "createQuizPage__quiz-round">
<Form.Item
{...restField}
name={[name, 'round_type']}
fieldKey={[fieldKey, 'round_type']}
rules={[{ required: true, message: 'Missing Quiz Round' }]}
>
<label>Choose Round Type </label>
<Radio.Group
options = {quiz_sub_rounds}
optionType = "button"
buttonStyle = "solid"
onChange = {(e) => roundScores(e)}
></Radio.Group>
<RoundTypeScheme type = {type} count = {count}/>
</Form.Item>
<MinusCircleOutlined onClick={() => remove(name)} />
</Space>
))}
<Form.Item>
<Button onClick={() => {add(); addRound();}} icon={<PlusOutlined />}>
Add a Round
</Button>
</Form.Item>
</>
)}
</Form.List>
</Form>
</div>
)
}
export default CreateQuizPage;
The issue you facing is caused because you pass the type and view dependencies as props to the actual component and they're still reactive.
You need to either:
Save the initial config of the round component and then if the props will change, it won't change the UI of the round.
Hold in the container component an array of the round configs and then loop over them and pass each round config to the round component.
I think #2 would be better in terms of architecture and code readability
Related
While working with the controlled input components if we set the value of the controlled component to null or undefined the previous value is still displayed on the UI instead of changing it and the state holding that input value changes to null or undefined. I have created a sandbox for better understanding
https://codesandbox.io/s/black-architecture-0wqw1
Thank you
If the data type is null or undefined react automatically supress that value and log nothing.
If you want to see the type of that particular value, write {typeof data}, then you'll get your answer.
...
setData(null)
typeof data // object
setData("hi")
typeof data // string
setData(undefined)
typeof data // undefined
...
here is quick fix, it never changes value variable, if data then put data else empty string, that how it works
<input
type="text"
onChange={(e) => setData(e.target.value)}
value={data ? data:""}
/>
i hope this will solve your problem,
here is complete fix,
https://codesandbox.io/embed/optimistic-currying-snn8t?fontsize=14&hidenavigation=1&theme=dark
You can use ref tout change the value of your input anywhere outside your input component, see bellow :
import React, { useState, useEffect } from "react";
import "./styles.css";
export default function App() {
const [data, setData] = useState(null);
const inputRef = React.useRef(null);
useEffect(() => {
console.log(data);
}, [data]);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<input
ref={inputRef}
type="text"
onChange={(e) => setData(e.target.value)}
value={data}
/>
<div style={{ marginTop: "10px" }}>
<button onClick={() => {
setData(null);
inputRef.current.value = ''
}}>SET DATA NULL</button>
</div>
<div style={{ marginTop: "10px" }}>
<button onClick={() => {
setData(undefined)
inputRef.current.value = ''
}}>
SET DATA UNDEFINED
</button>
</div>
<div style={{ marginTop: "10px" }}>INPUT VALUE {data}</div>
</div>
);
}
So, I have an input element connected to the React Context API - updating the value onChange works when the element is not nested within a component. Just under the input, I render a different component that returns an input field. This input field is also connected to the context API, but the input loses focus onChange.
I understand that I could add a "key" and even an "id", but none of these solutions seems to work.
Why is this happening, and what is the best way to fix is?
import React, { useContext } from "react";
import { Context } from "../../context";
import { set_employee_action } from "../../context/actions";
const DashBody = () => {
const { state, dispatch } = useContext(Context);
const DashboardBody = () => {
return (
<div key={"table"}>
{/* THIS IS NOT THE ACTUAL PLACE FOR THIS INPUT - BUT THIS IS WHERE IT BREAKS */}
<div key={"LABEL_TWO"}>
<label htmlFor={"LABEL_TWO"}>{"LABEL_TWO"}:</label>
<input
type="text"
id={"LABEL_TWO"}
key={"LABEL_TWO"}
name={"LABEL_TWO"}
value={
state.dash.employee_form["LABEL_TWO"]
? state.dash.employee_form["LABEL_TWO"]
: ""
}
onChange={(e) => dispatch(set_employee_action(e))}
></input>
</div>
{/* THIS IS NOT THE ACTUAL PLACE FOR THIS INPUT - BUT THIS IS WHERE IT BREAKS */}
</div>
);
};
return (
<div className="dash_body_container" key={"dash_body_container"}>
{/* THIS IS NOT THE ACTUAL PLACE FOR THIS INPUT - BUT THIS IS WHERE IT WORKS */}
<div key={"LABEL_ONE"}>
<label htmlFor={"LABEL_ONE"}>{"LABEL_ONE"}:</label>
<input
type="text"
id={"LABEL_ONE"}
key={"LABEL_ONE"}
name={"LABEL_ONE"}
value={
state.dash.employee_form["LABEL_ONE"]
? state.dash.employee_form["LABEL_ONE"]
: ""
}
onChange={(e) => dispatch(set_employee_action(e))}
></input>
</div>
{/* THIS IS NOT THE ACTUAL PLACE FOR THIS INPUT - BUT THIS IS WHERE IT WORKS */}
<DashboardBody></DashboardBody>
</div>
);
};
export default DashBody;
It looks like you're redefining the DashboardBody component each time DashBody is rendered. You're losing the focus inside DashboardBody's input because you're using dispatch from DashBody so that each time the dispatch is called, DashBody component is re-rendered and is rendering a different DashboardBody component. You can extract DashboardBody and define it outside DashBody but make sure you use useContext(Context) in DashboardBody:
const DashboardBody = () => {
const { state, dispatch } = useContext(Context); // make sure you have your own dispatch method
return (
<div key={"table"}>
{/* THIS IS NOT THE ACTUAL PLACE FOR THIS INPUT - BUT THIS IS WHERE IT BREAKS */}
<div key={"LABEL_TWO"}>
<label htmlFor={"LABEL_TWO"}>{"LABEL_TWO"}:</label>
<input
type="text"
id={"LABEL_TWO"}
key={"LABEL_TWO"}
name={"LABEL_TWO"}
value={
state.dash.employee_form["LABEL_TWO"]
? state.dash.employee_form["LABEL_TWO"]
: ""
}
onChange={(e) => dispatch(set_employee_action(e))}
></input>
</div>
{/* THIS IS NOT THE ACTUAL PLACE FOR THIS INPUT - BUT THIS IS WHERE IT BREAKS */}
</div>
);
};
I have a json that consists of a parent name and kids that the parent has.
I have a code that grabs the name of the parent and the kids of the json . Based on the amount of kids, input boxes are created so you can enter the name of each kid. But I am stuck. When I click the button how would I print a message that starts with the parent name and combines the values from each input box from the values of the json and the boxes that allow me to name the child
for instance when I click 'jim' from the dropdown, 2 input boxes shows up that says 'child1' and 'child2' and two boxes shows up next to them because he has two children. if i name child1 "james" and child2 "mary" and push the button how can I print a message in the final input box that says
"Jim -- child1=james -- child2=mary"
import React from 'react';
class AppEX extends React.Component {
constructor() {
super();
this.state = {
kids: null,
parentname: null,
parent: [
{ name: 'will', kids: ['child1', 'child2'] },
{ name: 'kia', kids: ['child1'] },
{ name: 'jim', kids: ['child1', 'child2'] }
]
};
}
handleParentChoice = e => {
e.persist();
this.setState({
parentname: e.target.value
});
};
render() {
const namelist = [];
this.state.parent.forEach(e => {
namelist.push({ value: e.name, label: e.name });
});
return (
<div>
<select name="select" onChange={this.handleParentChoice}>
{namelist.map(n => (
<option key={n.value} value={n.value}>
{n.label}
</option>
))}
</select>
<br />
{this.state.parentname &&
this.state.parent
.find(p => p.name === this.state.parentname)
.kids.map(k => (
<div>
<input
key={k}
type="text"
value={k}
disabled={true}
/>
<input key={k} type="text" />
</div>
))}
<br />
<button type="button" value="render" onClick={this.print} />
<input type="text" disabled={true} />
</div>
);
}
}
export default AppEX;
I created an example here, which does what you need.
Note that I had to add/modify some key attributes at places, because there were some console errors.
Also you need to synchronize input values to state at some point. In this example I do it on each keystroke, but you must be aware of the performance implications of this. You may choose different timing for the sync (on blur, with some debounce, etc.), it's up to you.
I modified inputs' name attributes so that each one has unique one, and can be recognized in the function which syncs to state (onChildNameChange).
And btw there are some nice libraries which can spare you the boilerplate of syncing, like react-hook-form (functional components, only) and formik.
As for printing the output string, I used Array.reduce to build it.
I want to make a form . which consist of a shop creation where 3 input fileds are plan name, descriptions, cuisuine . after that three input fileds which i want to add dynamically for duration of plan, maximum duration and price. i can add n numbers of duration and after that i also want to dynamically add all the input fields on a button clicked
plan name,
description
cuisuine
duration
I didn't quite understand the question, but in the following way you can generate a set of input fields dynamically. I hope it helps :)
import React, { useState } from "react";
export default function Example() {
const model = { name: "", desc: "", cuisuine: "" };
const [durations, setDurations] = useState([model]);
const fields = ["name", "desc", "cuisuine"];
function handleChange(field, index, value) {
let newDurations = durations;
newDurations[index][field] = value;
setDurations(newDurations);
}
return (
<div style={{ padding: "20px" }}>
{durations.map((v, i) => (
<div
style={{ border: "1px solid #ccc", padding: "20px" }}
key={`duration-${i}`}>
{fields.map(field => (
<input
key={`duration-${i}-${field}`}
type="text"
name={field}
placeholder={field}
onChange={({ target }) => handleChange(field, i, target.value)}
/>
))}
</div>
))}
<button onClick={() => setDurations([...durations, model])}>
Add field
</button>
</div>
);
}
How to model dynamic forms as a React Component?
For example I want to create a form shown in an image below:
How can I model this as a React component?
How can I add dynamicity to that component? For example, clicking on "+ Add" button creates another empty textbox and puts it right below the other already rendered textboxes (as shown in an image below).
Can someone help me with the code for the Form below?
In tags I see redux so I can suggest redux-form. Here you have an example of dynamic forms with redux-form.
The difference is in the fact, that beyond the state of form values, we also need to handle the state of form shape/structure.
If you render the inputs by traversing some state object, that is representing the shape of the form, than new input is just a new entry in this state object. You can easily add or remove input fields on the form by managing that state object. E.g. you can write something like this (pseudo react code):
// inputs state of math and algorithms
const state = { math: [obj1, obj2], algorithms: [obj1, obj2] } // obj ~= {id, value}
// render math inputs
const mathMarkup = state.math.map(obj => <input value={obj.value} onChange={...} />)
// add handler for math field
const addMath = () => setState(prevState => ({ math: [...prevState.math, newObj]}))
Here is the example of such form - codesandbox. It's not 100% as on your screen, but the idea should be understandable. Since there are some unclear requirements on your form, I implemented only first two sections, so you can grasp the idea. And, there are no styles :shrug:
Also, you can extract renderXyz methods to separate components, and improve state shape to meet your needs.
I can help you with a reduced way
import React , {Component} from 'react'
import { connect }from 'react-redux'
class main extends Component{
render(){
return(
<div>
<BaselineMath/>
<Algorithms />
</div>
)
}
}
const mapStateToProps = ({}) => {
return{}
}
export default connect (mapStateToProps,{})(main)
class BaselineMath extends Component{
constructor(props){
super(props);
this.state={rows:[1]}
}
_getRows{
return this.state.rows.map((res,key)=>{
return <input placeholder="etc..."/>
})
}
onClickAdd(){
let rows = this.state.rows
rows.push(1)
this.setState({
rows
})
}
render(){
return(
<div>
<Button onClick={this.onClickAdd.bind(this)}>ADD row</Button>
{this._getRows()}
</div>
)
}
}
export default (BaselineMath)
class Algorithms extends Component{
constructor(props){
super(props);
this.state={rows:[1]}
}
_getRows{
return this.state.rows.map((res,key)=>{
return <input placeholder="etc..."/>
})
}
onClickAdd(){
let rows = this.state.rows
rows.push(1)
this.setState({
rows
})
}
render(){
return(
<div>
<Button onClick={this.onClickAdd.bind(this)}>ADD row</Button>
{this._getRows()}
</div>
)
}
}
export default (Algorithms)
you can do the algorithm with anything you want