I am trying to add a new li element to the ul element in the CommentList return div. It should contain the user input from the <input type="text" /> field.
I am getting this error:
'Cannot read properties of null (reading 'value')'
I've tried a few things, like creating new li elements onSubmit and appending them to the ul element but no luck.
Intended Outcome
On user click, whatever value is in the input text field, it will create a new li element and add it to the ul element.
const CommentList = (props) => {
const [children, setChildren] = useState([]);
setChildren((oldArray) => [
...oldArray,
document.querySelector("input[type='text']").value,
]);
return (<div>
<form>
<input type="text" />
<input onSubmit={setChildren} type="button" value="Post" />
</form>
<ul>
</ul>
</div>);
}
You shouldn't be mixing React with native DOM methods.
This example:
Has one state for the list of items, and one for the current state of the input.
When the value of the input changes the input state is updated.
When the button is clicked the items state is updated with the input state, the input state is reset, and the input element refocused. (useRef)
(Note: using <input> without <form> is valid HTML, so that's why we can use onClick instead of onSubmit.)
const { useState, useRef } = React;
function Example() {
// Create a new reference which will be applied
// to the input element
const ref = useRef(null);
// Initialise the states
const [ items, setItems ] = useState([]);
const [ input, setInput ] = useState('');
// When the input value changes update
// the `input` state
function handleChange(e) {
setInput(e.target.value);
}
// When the button is clicked add the
// `input` state to the `items` state,
// reset the `input` state, and focus on
// the input element
function handleClick() {
setItems([...items, input]);
setInput('');
ref.current.focus();
}
// `map` over the items array to produce an
// array of list items
return (
<div>
<input
ref={ref}
onChange={handleChange}
value={input}
/>
<button onClick={handleClick}>Save</button>
<ul>{items.map(item => <li>{item}</li>)}</ul>
</div>
);
}
ReactDOM.render(
<Example />,
document.getElementById('react')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>
First change your onSubmit function name from setChildren becuase it is the same function use to update the state of children and change onSubmit to onClick. Try this instead:-
export default function App() {
const [children, setChildren] = useState([]);
const handleSetChildren = (e) => {
e.preventDefault();
setChildren((oldArray) => [
...oldArray,
document.querySelector("input[type='text']").value
]);
};
return (
<div>
<form>
<input type="text" />
<input onClick={handleSetChildren} type="submit" value="Post" />
</form>
<ul>
{children.map((child) => (
<li key={child}>{child}</li>
))}
</ul>
</div>
);
}
The onSubmit have to be on the form. The onSubmit handler accepts an event parameter, you cannot use the setChildren setter this way.
You should use state to control the input value (best approach here)
Finally, you have to map your children state to JSX to render it into your return
try this (didn't try the code, but the idea's here):
const CommentList = (props) => {
const [children, setChildren] = useState([]);
const [inputValue, setInputValue] = useState("");
return (<div>
<form onSubmit={(e) => {
e.preventDefault();
setChildren((oldArray) => [
...oldArray,
inputValue
]);
setInputValue("");
}}>
<input type="text" value={inputValue} onChange={e => setInputValue(e.target.value)} />
<input type="button" value="Post" />
</form>
<ul>
{children.map((child, index) => <li key={index}>{child}</li>)}
</ul>
</div>);
}
Go take a look to the react doc:
forms and controlled input
list rendering
Related
I'm new to React. I'm writing simple challanges in React. I've created an input field which then renders the input value in a new paragraph. But when I type in a new text in the input field the first rendered element changes also. How can I stop the first element from changing every time I type something in the input field?
import React, { useState } from 'react';
import './css&sass/main.css';
function Main() {
const [ item, setItem ] = useState("");
const [ showItem, setShowItem ] = useState(false);
const inputTargetValue = (e) => {
setItem(e.target.value)
}
function onSubmit(e) {
e.preventDefault()
console.log(item)
}
const showData = (
<p>
{item}
</p>
)
return (
<form className='main--container' onSubmit={onSubmit}>
<input type="text" className='data--input' placeholder='Text input' value={item} onChange={inputTargetValue}></input>
<button className='data--button' onClick={() => setShowItem(true)}>Add item</button>
{showItem ? showData : true}
</form>
)
};
export default Main;
Based on the below-mentioned assumptions:
User is able to add one or more items
These items are rendered just above the user-input
There is an Add Item button that will add one item to the display
There is a form which when submitted will console.log the items-list
For future extension, the form's submit may be used to send the data elsewhere (may be to backend, etc)
this following code-snippet may be a possible solution:
Code Snippet
const {useState} = React;
// React function to render items and
// accept user-input for new items
const AddItems = () => {
// declare variables that need useState hook
// first, the item that will be rendered to hold the input
const [item, setItem] = useState('');
// a list of items that will be rendered at the top
const [itemsList, setItemsList] = useState([]);
// method to handle change to input
const handleInputChange = e => setItem(e.target.value);
// handle add button click (update items-list,
// reset item to empty, and prevent default)
const handleAddBtnClick = (e) => setItemsList(
prev => ([...prev, item]),
setItem(''), e.preventDefault()
);
// handle form-submit button click event
const handleSubmit = (e) => {
e.preventDefault();
// send the list of items for further processing
console.log(
'form-submit clicked... sending items-list: ',
itemsList.join()
);
};
return (
<div class='outerContainer'>
{itemsList && itemsList.length > 0 && (
<div class='itemsListContainer'>
{itemsList.map(
(itm, idx) => (
<div key={idx} class='itemContainer'>
<p>{itm}</p>
</div>
)
)}
</div>
)}
<form onSubmit={handleSubmit}>
<div class='userInputOuter'>
<input
value={item}
onChange={handleInputChange}
/>
<button onClick={handleAddBtnClick} >
Add Item
</button>
<button>Submit</button>
</div>
</form>
</div>
);
};
ReactDOM.render(
<div>
<h4>DEMO</h4>
<AddItems />
</div>,
document.getElementById('react')
);
.outerContainer { background-color: #BBBBEE }
.itemsListContainer { border: 2px solid #EE6666 }
.userInputOuter {
padding: 10px 25px;
display: flex;
align-items: center;
justify-content: space-evenly;
}
<div id='react'/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
Explanation is provided in-line in the above code-snippets as comments.
Observations perusing the code in OP's question
The showItem (initially set to false) is set to true on button-click & remains the same
This means, once the user clicks the button, the item rendered using showData viz exactly the same as the one within the input will be shown / displayed.
Thus, when user changes the input, the showData's item getting updated in real-time will be displayed
What is required is a way to hold more than one item. In the code-snippet this is achieved by using itemsList.
I'm triyng to remove an object property using spread syntax rendering a React component. I wonder if there is a way to achieve without adding to much extra code. I'm using {reset,...inputName}
I have a custom hook (useField) for every input in my form. useField also has a reset function for my reset button. But I want to remove the reset property only for the inputs element.
custom hook useField
export const useField = (type) => {
const [value, setValue] = useState('')
const onChange = (event) => {
setValue(event.target.value)
}
const reset = () => {
setValue('')
}
return {
type,
value,
onChange,
reset
}
}
react form
const MyForm = (props) => {
const content = useField('text')
const author = useField('text')
const info = useField('text')
const handleClick = () => {
content.reset()
author.reset()
info.reset()
}
return (
<div>
<h2>create a new anecdote</h2>
<form onSubmit={handleSubmit}>
<div>
content
<input {reset,...content} />
</div>
<div>
author
<input {reset,...author} />
</div>
<div>
url for more info
<input {reset,...info} />
</div>
<button>create</button>
<input type="reset" value='reset' onClick={handleClick} />
</form>
</div>
)
}
For future reference, what may work for OP are changes similar to below:
const { reset: resetContent, ...content} = useField('text')
const { reset: resetAuthor, ...author} = useField('text')
const { rest: resetInfo, ...info} = useField('text')
const handleClick = () => {
resetContent();
resetAuthor();
resetInfo();
};
.
.
.
<div>
content
<input {...content} />
</div>
.
.
.
Explanation
the object returned from useField is destructured
the reset prop is separated and renamed as resetContent (or resetAuthor, resetInfo, as required)
the rest of the props go into the content variable (or author, info variables, as required)
when rendering in the JSX, the content is used
Thus, effectively the reset prop from useField was 'removed' (technically, it was just separated, though) in the new content object.
I am trying to somehow pass the data from 2 forms and 2 different components to the parent component and then somehow console.log all of this data with a button that is inside the parent component. Then I will simply send these data to a JSON file or a dummy database.
When I press the submit button of course nothing is triggered right now because I simply don't know how to pass the function from the children to the parent. I have tried many ways, but I would appreciate it if you could show me a way to lift the state and combine the forms.
For the input, in order to pass refs, I have used React.forwardRef()
It would be easy to just have 1 big component with 1 form and then the button inside this component, but since it is a fun project, I want to learn how to implement this functionality in case I will use it in the future. You can find a screenshot on this link:
[]
[1]: https://i.stack.imgur.com/myV0N.jpg
Here we go:
1. Parent component
const BookingComponent = () => {
return (
<div>
<CRContainer className="booking-crcontainer">
<CRColumn>
<PickUpCarComponent />
</CRColumn>
<CRColumn>
<CustomerInfo />
</CRColumn>
</CRContainer>
<CRContainer className="booking">
<Button type="submit" btnText="hello there" />
</CRContainer>
</div>
);
};
export default BookingComponent;
2. Child 1
const CustomerInfo = (props) => {
const firstlRef = useRef();
const lastNameRef = useRef();
const onTrigger = (e) => {
e.preventDefault();
//console.log(first1Ref.current.value)
console.log("heaheaheah");
};
return (
<>
<Subtitle stitle={SubtitleLabels.customerInfo} />
<div className="customer-info-container">
<form onSubmit={onTrigger}>
<div>
<LabeledInput
labelText={CustomerInfoLabels.firstName}
type="text"
inputPlaceholder={GeneralLabels.placeholder}
ref={firstlRef}
></LabeledInput>
<LabeledInput
labelText={CustomerInfoLabels.lastName}
type="text"
inputPlaceholder={GeneralLabels.placeholder}
ref={lastNameRef}
></LabeledInput>
</div> ...................
3. Child 2
Haven't put the refs here yet.
const PickUpCarComponent = () => {
return (
<div>
<Subtitle stitle={SubtitleLabels.pickUp} />
<form>
<div className="booking-inner-container">
<div>
<LabeledInput labelText={"Pick-up date*"} type="date"></LabeledInput>
<LabeledInput labelText={"Pick-up time*"} type="time"></LabeledInput>
</div>
<DropDown type="CarGroup" labeltext="Car Group*" attribute="name" />
<DropDown type="RentalOffice" labeltext="Region*" attribute="region" />
</div>
</form>
</div>
);
};
export default PickUpCarComponent;
4. Input Component
const LabeledInput = React.forwardRef((props, ref) => {
const { labelText, type, inputPlaceholder, onChange, className } = props;
return (
<div className={`input-container ${className}`}>
<label htmlFor="labelText">{labelText}</label>
<input
type={type}
placeholder={inputPlaceholder}
onChange={onChange}
ref={ref}
/>
</div>
);
});
export default LabeledInput;
you can use context to pass form handlers to child component then in the child component you can useContext and get value and handlers of parent form and use them.
const FormContext = React.createContext({});
const BookingComponent = () => {
const [values, setValues] = useState();
const handleChange = useCallback((e) => {
//handle child event in parent and save child state in
//parent to use later in submit button
}, []); //set dependency if it's needed
const contextValue = useMemo(() => ({ handleChange }), [handleChange]);
return (
<FormContext.Provider value={contextValue}>
<div>
<CRContainer className="booking-crcontainer">
<CRColumn>
<PickUpCarComponent />
</CRColumn>
<CRColumn>
<CustomerInfo />
</CRColumn>
</CRContainer>
<CRContainer className="booking">
<Button type="submit" btnText="hello there" />
</CRContainer>
</div>
</FormContext.Provider>
);
};
const LabeledInput = (props) => {
const formContext = useContext(FormContext);
const { labelText, type, inputPlaceholder, className } = props;
return (
<div className={`input-container ${className}`}>
<label htmlFor="labelText">{labelText}</label>
<input
type={type}
placeholder={inputPlaceholder}
onChange={formContext.handleChange}
ref={ref}
/>
</div>
);
};
I have a main react component which appends child components say <Child /> on button click
My Child component is of the format
<form>
<input .... />
<button type="submit">Submit</button>
<form>
Now I need to get the value of these input elements from every Child component which is appended but am not able to figure out a way to do so.
I won't know how many Child components would be added as the user can click the button any number of times to append yet another <Child /> so I can't have a fixed number of variables exported from the Child component to a variable in parent component.
Any suggestions would be highly appreciated.
Edit:
Code to append the child:
The submit function:
const [val, setVal] = useState([]);
const submit = () => {
setVal([...val, <Child />]);
}
Append button:
<Button onClick={submit}>Add Child</Button>
To render:
{val}
Since val is an array, it prints all the components inside it
I took some liberties because I don't know what would be the expected output of all the forms.
What I basically did is to create a map-like structure where I will map each child form with its value/s (depending on your needs could be modified) and I passed a submit function to the child components in order to store the values on the parent.
In that way on a child submit I will be able to get the values passed from the child as well as its index.
Parent component
const Parent = () => {
const [val, setVal] = useState([]);
// Map like structure to store the values (is just an example, you can use whatever you want)
const [mapOfValues, setMapOfValues] = useState({});
// Pass submit function as prop
const submit = () => {
setVal([...val, <Child submit={onChildSubmit} />]);
};
// Submit function that stores the value for the child
const onChildSubmit = (childIndex, value) => {
setMapOfValues({
...mapOfValues,
[childIndex]: value
});
};
return (
<div className="App">
{val.map((value, index) =>
// Key to prevent React warning and childIndex to map the value to the child form
React.cloneElement(value, { key: index, childIndex: index })
)}
<button onClick={submit}>Add</button>
</div>
);
}
Child component
const Child = (props) => {
const [value, setValue] = useState("");
const submitForm = (e) => {
e.preventDefault();
props.submit(props.childIndex, value);
};
return (
<form onSubmit={submitForm}>
<input onChange={(e) => setValue(e.target.value)} value={value} />
<button>Submit</button>
</form>
);
};
Once you are done, and you want to submit from the parent component, you could use a reduce, map or whatever you need to format the values as you want.
The link to the sandbox is this
If you have any other question let me know.
I would like to concat the value that's on a text input onto a property in the state, but I don't know how to get access to that input's value from the click event.
This is the code i'm working with right now.
onCreateClick = () => {
const valueFromInput =
this.setState((prevState) => {
stateProperty: prevState.stateProperty.concat(`${valueFromInput}`)
})
}
Here is an example of how you can do it, I have used functional component and hooks instead of class-based component, but I hope you get the logic behind implementation:
import React, { useState } from "react";
import "./styles.css";
export default function App() {
const [stateProperty, setStateProperty] = useState("");
const [valueFromInput, setValueFromInput] = useState("");
const onCreateClick = () => {
let newString = stateProperty;
setStateProperty(newString.concat(valueFromInput));
setValueFromInput("");
console.log(valueFromInput);
};
return (
<div className="App">
{stateProperty ? stateProperty : "Type Something"}
<input
value={valueFromInput}
onChange={(e) => setValueFromInput(e.target.value)}
/>
<button onClick={onCreateClick}>append</button>
</div>
);
}
Codesandbox Example
You can set a reference on the JSX element whose value you want to obtain. Then you can just reference the DOM Element at your event handler. Once you have a reference to that DOM Element, you can just grab the value of the input with yourElement.value like a normal HTML element.
https://reactjs.org/docs/refs-and-the-dom.html
Copied from the React docs
functional component with Hooks
function CustomTextInput(props) {
// textInput must be declared here so the ref can refer to it
const textInput = useRef(null);
function handleClick() {
textInput.current.focus();
}
return (
<div>
<input
type="text"
ref={textInput} />
<input
type="button"
value="Focus the text input"
onClick={handleClick}
/>
</div>
);
}
class component implementation
class CustomTextInput extends React.Component {
constructor(props) {
super(props);
// create a ref to store the textInput DOM element
this.textInput = React.createRef();
this.focusTextInput = this.focusTextInput.bind(this);
}
focusTextInput() {
// Explicitly focus the text input using the raw DOM API
// Note: we're accessing "current" to get the DOM node
this.textInput.current.focus();
}
render() {
// tell React that we want to associate the <input> ref
// with the `textInput` that we created in the constructor
return (
<div>
<input
type="text"
ref={this.textInput} />
<input
type="button"
value="Focus the text input"
onClick={this.focusTextInput}
/>
</div>
);
}
}```