Im doing a unit test for my component. It consists of 6 <input> elements, where the first input is set to be auto focused. I am trying to test this.
Here is my test code:
import React from 'react';
import { configure, shallow, ShallowWrapper } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import HCPinCode from './HCPinCode';
configure({ adapter: new Adapter() });
describe('<HCPinCode>', () => {
let wrapper: ShallowWrapper;
beforeEach(() => {
wrapper = shallow(
<HCPinCode
length={6}
hasFailed={false}
codeChangeHandler={(_: string) => {}}
></HCPinCode>
);
});
it('mounts without crashing', () => {
wrapper.unmount();
});
it('should be rendered and first input element should be auto-focused', () => {
const inputElement = wrapper.find('input:first-child').getElement();
expect(document.activeElement).toBe(inputElement);
});
});
When I run the unit test I get failure:
● <HCPinCode> › should be rendered and first input element should be auto-focused
expect(received).toBe(expected) // Object.is equality
- Expected - 7
+ Received + 1
- <input
- autoFocus={true}
- className="field"
- onKeyDown={[Function onKeyDown]}
- onPaste={[Function onPaste]}
- value=""
- />
+ <body />
basically it recognized the focused one as the body.
This is the component itself:
return (
<div className={containerClasses} ref={containerRef}>
{codeState.map((codeValue, index) => (
<input
key={index}
autoFocus={index === 0}
className={fieldClasses}
value={codeValue}
onKeyDown={({ key }) => onKeyDown(key, index)}
onPaste={(e) => onPaste(e, index)}
/>
))}
</div>
codeState is just an array of numbers.
Also I console log the HTML tree built:
it('should be rendered and first input element should be auto-focused', () => {
const inputElement = wrapper.find('input:first-child').getElement();
console.log(wrapper.html());
expect(document.activeElement).toBe(inputElement);
});
The log I get in console:
<div class="container"><input autofocus="" class="field" value=""/><input class="field" value=""/><input class="field" value=""/><input class="field" value=""/><input class="field" value=""/><input class="field" value=""/></div>
Indeed my first element has autofocus attribute.
Related
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
I am trying to display some text beside the checkbox in React Typescript and with the value attribute its not working. If i try to set any innerHTML then it throws me an error input is a void element tag and must neither have children nor use dangerouslySetInnerHTML.
How should I go about it?
interface LetterOptionsProps{
letterOptions:any
}
export default class LetterOptions extends Component<LetterOptionsProps>{
render(){
if(this.props.letterOptions.length>0){
return(
this.props.letterOptions.map((a:any, i:any) => {
return (
<React.Fragment>
<input key={i} className="letter-option" type="checkbox" value={a} />
</React.Fragment>
);
}
)
);
}else{
return(<div></div>);
}
}
}
Sounds like you want the input to have a label. As the error points out, inputs are void returns and can't have any children.
Also, FYI, the React key should be placed on the outer-most element being wrapped, i.e. the React.Fragment if you still use that, or the label as below.
this.props.letterOptions.map((a:any, i:any) => (
<label key={i}>
{a}
<input className="letter-option" type="checkbox" />
</label>
))
Overall you can also simplify your component code with some applied conditional rendering.
export default class LetterOptions extends Component<LetterOptionsProps>{
render() {
const { letterOptions } = this.props;
return letterOptions.length ? letterOptions.map((a:any, i:any) => (
<label key={i}>
{a}
<input className="letter-option" type="checkbox" />
</label>
)) : null
}
}
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'm doing a CRUD with vue-cli and nodejs on the server side. So I have a form like this
<template>
<div id="formRoot">
<div class="form" >
<form #submit.prevent="sendToTable" action="/create" method="post">
Name
<input type="text" v-model="row.name" name="name" />
Price
<input type="number" name="price" v-model="row.price" />
Description
<textarea v-model="row.description" rows="3" name="desc"/>
<input type="submit" id="button" value="SAVE" />
</form>
</div>
<form class="" action="create" method="post">
<input type="text" name="input">
<input type="submit" value="send">
</form>
</div>
</template>
<script>
export default{
data(){
return{
row: {
name: '',
price: '',
description: ''
}
}
},
methods: {
sendToTable() {
console.log(this.row);
this.$parent.addToTable(this.row);
}
}
}
</script>
the #submit.prevent is for avoid the page refreshing and of course I have a method named sendToTable.
in node I have this:
const path = require('path');
const morgan = require('morgan');
const app = express();
//middlewares
app.use(express.urlencoded({extended: false}));
app.use(express.static(path.resolve(__dirname, '../dist')));
app.use(morgan());
app.post('/create', (req, res) => {
console.log(req.body);
});
const port = 3000;
app.listen(port, () => {
console.log('server listening on port ' + port);
});
the problem is that the server cant get the post request, I think is because the #prevent.default property.
I tried sending a post request with postman and it works, so I'm sure the problem is in the frontend.
What should i do? How are actually coded those single page web apps that can send data to the server?
You need to actually post your form data via an HTTP request. You can use a library like Axios (very popular) or fetch (check the supported browsers list).
Another thing you appear to be doing is calling a method on this component's parent. That goes against Vue's one-way data flow and isn't optimal. The better solution is to have your component emit an event with the attached data.
For example (using fetch)
<form #submit.prevent="sendToTable" method="post" action="/create">
methods: {
async sendToTable ($event) {
const form = $event.target
// post form as a regular "submit" would
let res = await fetch(form.action, {
method: form.method,
body: new URLSearchParams(new FormData(form))
})
if (res.ok) {
// emit the "row-added" event to the parent
this.$emit('row-added', { ...this.row }) // using spread syntax to break references
} else {
// you might want to do something else here in case of an error
console.error(res)
}
}
}
and in your parent component (assuming the child component is named RowAdder)
<RowAdder #row-added="addToTable"/>