I am pretty new to the wonderful world of React.
I have two inputs passing data through from an API that renders a list of options. And I want to send the selected inputs from those options back to the parent in the input fields to display for another search.
I have tried passing state down to them and render them them optionally with both a ternary and an if else statement in the "SearchCityList" component in several ways but I either get both lists rendered and they would have to choose between one list that is doubled to put in each input field or it only puts the selected value in one input. Would appreciate any & all suggestions Thanks!
class Form extends Component {
state = {
showComponent: false,
showComponent2: false,
};
// open/close control over SearchCity component box
openSearch = () => {
this.setState({ showComponent: true });
};
openSearch2 = () => {
this.setState({ showComponent2: true });
};
closeSearch = () => {
this.setState({
showComponent: false,
showComponent2: false
});
};
// Passed down cb function to get selected city search in selectCity component
GoingTo = (flights) => {
this.setState({ GoingTo: [flights] });
};
LeavingFrom = (flights) => {
this.setState({ LeavingFrom: [flights] });
};
render() {
return (
<div>
<form className="form-fields container">
<div className="inputs">
<h1>Search for a flight!</h1>
<div className="depart">
<input
onClick={this.openSearch}
className="flight-search"
placeholder="Leaving From"
value={this.state.LeavingFrom}
></input>
<input type="date"></input>
</div>
<div className="Returning">
<input
onClick={this.openSearch2}
className="flight-search"
placeholder="Going To "
value={this.state.GoingTo}
></input>
<input type="date" placeholder="Returning"></input>
</div>
</div>
<button>Check Flights!</button>
</form>
{this.state.showComponent || this.state.showComponent2 ? (
<SearchCity
openSearch={this.openSearch}
openSearch2={this.openSearch2}
flightSearch={this.state.flightSearch}
closeSearch={this.closeSearch}
GoingTo={this.GoingTo}
LeavingFrom={this.LeavingFrom}
onSearchSubmission={this.onSearchSubmission}
closeSearch={this.closeSearch}
/>
) : null}
</div>
);
}
}
export default Form;
class SearchCity extends Component {
state = {
LeavingFrom: "",
GoingTo: "",
search: "",
flightSearch: [],
};
// Search submission / api call
onSearchSubmission = async (search) => {
const response = await Axios.get(
{
headers: {
"
useQueryString: true,
},
}
);
// set New state with array of searched flight data sent to searchCity component
const flightSearch = this.setState({ flightSearch: response.data.Places });
};
// Callback function to send search/input to parent "Form" component
submitSearch = (e) => {
e.preventDefault();
this.onSearchSubmission(this.state.search);
};
// closeSearch callback function sent from Form component to close pop up search box when X is pressed
closeSearch = () => {
this.props.closeSearch();
};
render() {
return (
<div className="container search-list">
<form onChange={this.submitSearch}>
<i className="fas fa-times close-btn" onClick={this.closeSearch}></i>
<input
onChange={(e) => this.setState({ search: e.target.value })} //query-search api
value={this.state.search}
className="search-input"
type="text"
placeholder="Search Locations"
></input>
<div className="search-scroll">
<SearchCityList
openSearch={this.props.openSearch}
openSearch2={this.props.openSearch2}
LeavingFrom={this.props.LeavingFrom}
GoingTo={this.props.GoingTo}
flightSearch={this.state.flightSearch}
/>
</div>
</form>
</div>
);
}
}
export default SearchCity;
function SearchCityList({ flightSearch, LeavingFrom, GoingTo }) {
const renderList = flightSearch.map((flights) => {
return (
<div>
<SelectCityLeaving LeavingFrom={LeavingFrom} flights={flights} />
<SelectCityGoing GoingTo={GoingTo} flights={flights} />
</div>
);
});
return <div>{renderList}</div>;
}
export default SearchCityList;
First of all, when dealing with state, make sure you initialize in the constructor and also ensure you bind your handlers to this component instance as this will refer to something else in the handlers if you don't and you won't be able to call this.setState().
constructor(props) {
super(props); // important
state = {
// your state
};
// make sure to bind the handlers so `this` refers to the
// component like so
this.openSearch = this.openSearch.bind(this);
}
Related
I am trying to write a delete method in order to delete an element from a list, first of all I am not being able to write it in a setState function so I have it as a direct function call, How can I manage to signal a re-render after the direct function or manage to place the function in the setState method for automatic re-render?
class TASKMANAGER extends Component {
constructor(props){
super(props);
this.state= {
name: "",
description:"",
priority: "urgent",
tasklist: [],
}
this.handleTitleChange= this.handleTitleChange.bind(this);
//this.handleDescriptionChange= this.handleDescriptionChange.bind(this);
//this.handlePriorityChange= this.handleDescriptionChange.bind(this);
this.handleClick= this.handleClick.bind(this);
}
handleTitleChange = event => {
this.setState( {
name: event.target.value
})
};
handleDescriptionChange = event => {
this.setState({
description: event.target.value
})
};
handlePriorityChange = event => {
this.setState({
priority: event.target.value
})
};
handleClick = event => {
this.setState((state) => {
const tasklist = [
...state.tasklist,
[
state.name,
state.description,
state.priority
]
];
return {
tasklist
};
});
//console.log(this.state.tasklist);
};
handleDelete = index => {
this.setState(() => {
this.state.tasklist.splice(index, 1)
});
console.log(this.state.tasklist)
} THIS ONE IS THE FUNCTION I CANNOT SET TO WORK TO TRIGGER THE AUTO RE-RENDER
render() {
const task_item = this.state.tasklist.map((arr, index) => (
<li
key= {index}
className= 'task'>
Task: {arr[0]} <br />
Description: {arr[1]} <br />
Priority: {arr[2]} <br />
<div className='delete-button' onClick={
/*() => {this.state.tasklist.splice(index, 1);}*/ THIS ONE IS THE DIRECT FUNCTION THAT WORKS, BUT DOESN'T TRIGGER THE RE-RENDER, IT SHOWS WHEN I TYPE AGAIN ON THE INPUTS
this.handleDelete
}>delete</div>
</li>
))
return (
<div>
<div className= 'task-form'>
<form>
<div>
<label>Name your task!</label>
<input type= 'text' id='task-title' value={this.state.name} onChange={this.handleTitleChange} />
</div>
<div>
<label>Description?</label>
<textarea id='description' value={this.state.description} onChange={this.handleDescriptionChange}/>
</div>
<div>
<label>Priority?</label>
<select value={this.state.priority} onChange={this.handlePriorityChange}>
<option value='urgent'>Urgent</option>
<option value='regular'>Regular</option>
<option value='Can wait'>Can wait</option>
</select>
</div>
</form>
<button onClick={this.handleClick}>PRESS</button>
</div>
<div className='list-items'>
<ul className='list-render'>
{task_item}
</ul>
</div>
</div>
)
}}
export default TASKMANAGER
You shouldn't be making any mutations to the current state, but instead build a new state from the existing state, generating a new, filtered array along the way
handleDelete = index => {
this.setState((state) => ({
...state,
tasklist: state.taskList.filter((_,i) => i != index)
}));
}
When you map your taskList to JSX below, you will need to avoid using the index of the item as key, because the optimizations react makes using the key value will be operating under broken assumptions. Use a key value that remains constant and unique per item. Perhaps its name, or an identifier that is attached to it when created.
There is no need to assign the list. Just splice it. Use something like this to change the state:
delete={()=>{this.setState({phase:1-this.state.phase});
this.state.list.splice(index,1)}}
I have a this value inside an if statement, nested inside a my handleFormChange function. I've tried to use arrow functions with this function to bind the value of this but im getting the following error message:
TypeError: Cannot set property 'author' of undefined
From my understanding usually you find the this value by looking at where the function containing this is called. However, in my case im struggling to work this out. Can anyone explain to me why it is undefined and how to solve this issue? Here is the code:
class CommentForm extends React.Component{
constructor(props){
super(props)
var comment={author:'', message:''}
}
handleSubmit= (e)=>{
e.preventDefault()
var authorVal = this.comment.author;
var textVal = this.comment.message;
//this stops any comment submittal if anything missing
if (!textVal || !authorVal) {
return;
}
this.props.onCommentSubmit(this.comment);
//reset form values
e.target[0].value = '';
e.target[1].value = '';
return;
}
handleFormChange= (e)=>{
e.preventDefault()
if(e.target.name==='author'){
var author = e.target.value.trim();
this.comment.author = author
}else if(e.target.name==='message'){
var message = e.target.value.trim();
this.comment.message = message
}
}
render() {
return (
<form className = "ui form" method="post" onChange={(e)=>{this.handleFormChange(e)}} onSubmit={(e)=>{this.handleSubmit(e)}}>
<div className="form-group">
<input
className="form-control"
placeholder="user..."
name="author"
type="text"
/>
</div>
<div className="form-group">
<textarea
className="form-control"
placeholder="comment..."
name="message"
/>
</div>
<div className="form-group">
<button disabled={null} className="btn btn-primary">
Comment ➤
</button>
</div>
</form>
);
}
}
export default CommentForm
The first step into learning how to do what you want is to study how React's State works (official docs are great at explaning it).
This example is not complete, but should guide you through the proccess.
class CommentForm extends Component {
constructor(props) {
super(props);
this.state = {
author : '',
message : '',
}
this.onChangeAuthorName = this.onChangeAuthorName.bind(this);
this.onBlurAuthorName = this.onBlurAuthorName.bind(this);
}
onChangeAuthorName(e) {
this.setState({ author: e.target.value });
}
onBlurAuthorName() {
// trim on blur (or when you send to the network, to avoid
// having the user not being able to add empty whitespaces
// while typing
this.setState({ author: this.state.author.trim() })
}
render() {
return (
...
<input value={this.state.author} onChange={this.onChangeAuthorName} onBlur={this.onBlurAuthorName} />
...
);
}
}
Usually, when you want to "set" variables in React, you don't add them as you do to in Javascript classes (this.comment = e.target.value), but instead, use the function setState(). From the docs:
// Wrong
this.state.comment = 'Hello';
Instead, use setState():
// Correct
this.setState({comment: 'Hello'});
(NOTE: Alternatively, this could be done using React Hooks, but I recommend you learn the lifecycle methods firsthand. Good luck!)
I decided to write even if you proposed the correct answer for the simple reason that I think my code is closer to what it published.
import React, { Component } from "react";
import ReactDOM from "react-dom";
class App extends Component {
constructor(props) {
super(props);
this.state = {
comment: {},
some: 1
};
}
handleFormChange = e => {
e.preventDefault();
let { comment } = this.state;
const newCommentState = function() {
let returnObj = { ...comment };
returnObj[this.target.name] = this.target.value.trim();
return returnObj;
}.bind(e)();
this.setState({ comment: newCommentState });
};
handleSubmit = e => {
e.preventDefault();
let { comment } = this.state;
if (!comment.author || !comment.message) return;
this.props.onCommentSubmit(comment);
this.setState({ comment: {} });
e.target[0].value = "";
e.target[1].value = "";
};
render() {
return (
<div>
<form
className="ui form"
method="post"
onChange={e => {
this.handleFormChange(e);
}}
onSubmit={e => {
this.handleSubmit(e);
}}
>
<div className="form-group">
<input
className="form-control"
placeholder="user..."
name="author"
type="text"
/>
</div>
<div className="form-group">
<textarea
className="form-control"
placeholder="comment..."
name="message"
/>
</div>
<div className="form-group">
<button disabled={null} className="btn btn-primary">
Comment ➤
</button>
</div>
</form>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Live example:
I have a component InputArea with state = {input: ''}
Then I map several of these components in a container and write them in state = {inputAreas: []}
Now, how can I get inputs in the container? Logging this.state.inputAreas[0] returns object like this:
{$$typeof: Symbol(react.element), type: ƒ, key: "1", ref: null, props:
{…}, …}
In elements it shows like this:
<input type="text" class="form-control" name="input" value="abc">
Using this.state.prefooterArea[0].value gives undefined.
I also tried passing input from component to container as props, but it says getInput is not a function. From what I understood it has something to do with the fact I used map in the container. I can't use redux in this project.
Code of component
class PrefooterAreaInput extends Component {
state = {
input: ''
}
textChangedHandler = (event) => {
let newState = {};
newState[event.target.name] = event.target.value;
this.setState(newState);
}
render() {
return (
<div>
<input
className="form-control"
type="text"
name="input"
value = {this.state.input}
onChange={this.textChangedHandler}
/>
</div>
)
}
}
Code of container
class DescriptionFrame extends Component {
state = {,
prefooterArea: [<PrefooterAreaInput key={1}/>]
};
addFooterInputHandler = event => {
event.preventDefault();
if (this.state.prefooterArea.length < prefooterInputFieldsMax) {
var newPrefooterArea = this.state.prefooterArea.map(
inputField => inputField
);
newPrefooterArea.push(
<PrefooterAreaInput key={this.state.prefooterArea.length + 1} />
);
this.setState({ prefooterArea: newPrefooterArea });
}
};
removeFooterInputHandler = event => {
event.preventDefault();
if (this.state.prefooterArea.length > 1) {
var newPrefooterArea = this.state.prefooterArea.map(
inputField => inputField
);
newPrefooterArea.splice(newPrefooterArea.length - 1);
this.setState({ prefooterArea: newPrefooterArea });
}
render() {
// want to get this.state.prefooterArea[0]'s value
return (
<div>
{this.state.prefooterArea}
<a
className="nav-link"
href=""
onClick={this.addFooterInputHandler}
>
Add More
</a>
<a
className="nav-link"
href=""
onClick={this.removeFooterInputHandler}
>
Remove Last
</a>
</div>
);
}
}
Figured it out. This caused problem.
prefooterArea: [<PrefooterAreaInput key={1}/>]
I should have added that initial PrefooterAreaInput with lifecycle method instead. With that I was able to pass state just fine.
Are you trying to achieve something like this ?
child component :
export default class InputBox extends React.Component {
render() {
return (
<input onChange={event => this.props.onChange(event.target.value)} />
);
}}
parent component :
import InputBox from './InputBox';
class FilterBar extends React.Component {
constructor(props) {
super(props);
this.state = {
inputs: "" //get input value from state this input
};
this.updateFilters = this.updateFilters.bind(this);
}
updateFilters(i) {
this.setState({ inputs: i }); // this will print whatever input you type
}
render() {
return (
<div>
<InputBox onChange={(i) => this.updateFilters(i)} />
</div>
);
}
}
import reqwest from './public/reqwest.js'
const PAGE_SIZE = 10
class App extends Component {
constructor() {
super()
this.state = {
page: 1,
arr: []
}
}
singleInfo(page) {
reqwest({
url: 'https://cnodejs.org/api/v1/topics',
data: {
limit: PAGE_SIZE,
page: this.state.page
},
success: function (data) {
this.setState({
arr: data.data
})
}.bind(this)
})
}
changeState(newState) {
this.setState(newState)
this.singleInfo(newState.page)
}
render() {
return (
<div>
<Menu val={this.state.page} changeParentState={(state) => this.changeState(state)} />
<List arr={this.state.arr} />
</div>
);
}
}
class Menu extends Component {
handleChange(event) {
if(event.target.value) {
this.props.changeParentState({
page: event.target.value
})
}
}
render() {
console.log(this)
return <input type="text" defaultValue={this.props.val} onChange={(event) => this.handleChange(event)} />
}
}
class List extends Component {
render() {
return <ul>
{
this.props.arr.map((ele) => {
return (
<li key={ ele.id }>
<p className="title">{ ele.title }</p>
<p className="date">{ ele.create_at }</p>
<p className="author">{ ele.author.loginname }</p>
</li>
)
})
}
</ul>
}
}
I can't get the current value of the input by onChange in Menu module.
In my code, the App has two child components - List & Menu.
You can input the page in Menu component, so it will send Ajax() to get the info of the page. But the truth is: After I change the value of input like 1 -> 10, the ajax get the value of 1.
Before this, I know the difference between keyup or keydown and keypress. They have the difference cross browser. But I just want get the current value of the input By React.js.
First, change:
<input type="text" defaultValue={this.props.val} onChange={(event) => this.handleChange(event)} />
To:
<input type="text" value={this.props.val} onChange={(event) => this.handleChange(event)} />
so that your input will update to the correct value on re-render.
Second, remember that setState is often asynchronous. So do not expect the state to be changed right after calling setState.
Your changeState method is good in this respect since it passes newState to singlePageRequest. However singlePageRequest does not use the supplied value and instead uses this.state.page. Change it to use the supplied value and you should be OK:
singleInfo(page) {
reqwest({
url: 'https://cnodejs.org/api/v1/topics',
data: {
limit: PAGE_SIZE,
page: page
},
success: function (data) {
this.setState({
arr: data.data
})
}.bind(this)
})
}
Just started my first app in React and I want to know if there is a React way to work with Radio Buttons, I have a form with 4 radio buttons, I need to take 2 of the options selected and send that info to a backend.
class RadioBtns extends Component {
constructor (props) {
super(props);
this.state = {
greet : '',
hello : '',
};
}
render () {
return (
<div>
<div>
<form>
<input type="radio" value="first" name="greet" onChange={this._onChangeGreet}/> Option 1
<input type="radio" value="second" name="greet" onChange={this._onChangeGreet}/> Option 2
<input type="radio" value="three" name="hello" onChange={this._onChangeHello}/> Option 3
<input type="radio" value="four" name="hello" onChange={this._onChangeHello}/> Option 4
</form>
<hr />
<button type="submit" onClick={this._submitSettings}>YES!</button>
</div>
</div>
);
}
_onChangeGreet = ({ target }) => {
this.setState({
greet : target.value,
});
}
_onChangeHello = ({ target }) => {
this.setState({
hello : target.value,
});
}
_submitSettings = () => {
console.log('submit');
}
}
export default RadioBtns;
how do I send this states with the values to the stores ?
and here I have the action
#createActions(flux)
class RadioBtnsActions {
constructor () {
this.generateActions('optionSelected');
}
}
export default RadioBtnsActions;
and in the Store
import flux from 'flux';
import RadioBtnsActions from 'actions/RadioBtnsActions';
#createStore(flux)
class RadioBtnsStore {
constructor () {
this.state = {
radioSelected : false,
};
}
#bind(RadioBtnsActions.optionSelected)
optionSelected (option) {
this.setState({
radioSelected : option,
});
}
}
export default RadioBtnsStore;
Here's what we did in our project (simplified, use your imagination):
First you create a RadioButton component that renders the actual input:
render(){
<div>
<input id={this.props.id} type="radio"
name={this.props.name} value={this.props.value}
checked={this.props.checked} onChange={this.onChange}/>
<label htmlFor={this.props.id}>{this.props.label}</label>
</div>
},
onChange: function(ev){
this.props.onChange(ev.target.checked, this.props.value);
}
Then you use that to implement a RadioButtonGroup component:
render: function(){
var name = this.name, value = this.props.value, onChange = this.onSingleRadioChange;
var options = _.map(this.props.options, function(option){
var id = name + '-' + option.value;
return <RadioButton key={option.value} id={id} name={name} value={option.value} label={option.label} checked={option.value == value} onChange={onChange} />
});
return <div>{options}</div>
},
onSingleRadioChange: function(checked, value){
if(checked)
this.props.onChange(value);
}
You can use it like this:
<RadioButtonGroup name='greet' options={[{value: 'first', label: 'First'}, {value: 'second', label: 'Second'}]} onChange={val => { Actions.radioGroupChanged('greet', val);}} />
Where Actions.radioGroupChanged is the action that your store is listening on.
Don't forget to use labels for better UX.
Edit: here's a rough draft of the store, although we use Reflux, so it's a different API that what you have:
var store = Reflux.createStore({
radioGroups: {greet: 'first', hello: 'three'}, //state of the radio button groups lives here
init(){
this.listenTo(Actions.radioGroupChanged, this.onRadioGroupChanged);
},
onRadioGroupChanged(group, value){
this.radioGroups[group] = value;
this.trigger(); //this notifies the component that the store changed;
}
});
The component then listens to the store and updates its own state:
componentDidMount(){
this.listenTo(store, () => { this.setState({radios: store.groups}) });
}
render(){
return <RadioButtonGroup name='greet' value={this.state.radios.greet} .../>
}