My code generates an input field that allows a user to enter a value to search for. Then when they click the Submit button, it causes displayMap to be true, so that when the MapDisplay component renders, it will trigger an API search via the Map component and return values that are then displayed on the map.
The problem is that this process only works once. When I click the button again, it does do something, I confirmed that it is getting the new value in the input box, but I can't seem to figure out how to get the map to be rendered again.
I've tried setting other variables in the this.setState to try to get it to know that it needs to render the component again, but I guess I'm missing something, because nothing works.
I'm fairly new to React, so any help you can offer would be greatly appreciated.
This is the MainSearchBar.js, where most of the work as described above is happening:
import Map from './newMap.js';
function MapDisplay(props) {
if (props.displayMap) {
return <Map toSearch = {props.searchTerm}></Map>;
} else {
return "";
}
}
class MainSearchBar extends React.Component {
constructor(props) {
super(props);
this.state = {
displayMap: false,
value: '',
searchTerm: '',
isOpened: false
};
//this.handleClick = this.handleClick.bind(this);
this.handleChange = this.handleChange.bind(this);
}
handleClick = () => {
this.setState({
displayMap: true,
isOpened: !this.state.isOpened,
searchTerm: this.state.value
});
console.log(this.state.value);
}
handleChange(event) {
this.setState({value: event.target.value});
}
render() {
const displayMap = this.state.displayMap;
return (
<div class="homepage-search-bar">
<input
type="text" name="search" value={this.state.value} onChange={this.handleChange} className="main-search-bar" placeholder="Search hashtags">
</input>
<button onClick={this.handleClick}>Search</button>
<MapDisplay displayMap={displayMap} searchTerm={this.state.value} />
</div>
)
}
}
export default MainSearchBar;
This is where MainSearchBar is being called from
import Top20Box from '../components/getTop20Comp2.js';
import Header from '../components/Header.js';
import MainIntro from '../components/MainIntro.js';
import MainSearchBar from '../components/MainSearchBar.js';
import MainCTA from '../components/MainCTA.js';
import Footer from '../components/Footer.js';
export default class Home extends Component {
state = {
}
render () {
return (
<React.Fragment>
<Header>
</Header>
<MainIntro />
<MainSearchBar />
<div className="top20-text">
Top 20 trending hashtags
</div>
<Top20Box />
<MainCTA />
<Footer />
</React.Fragment>
)
}
}
And this is the Map component itself, in case you need it:
import React from 'react';
import ReactMapGL, {Marker, Popup} from 'react-map-gl';
import axios from 'axios';
//for the loading animation function
import FadeIn from "react-fade-in";
import Lottie from "react-lottie";
import * as loadingData from "../assets/loading.json";
var locationCoordinates = [];
var locationToSearch = "";
var returnedKeywordSearch = [];
var newArray = [];
const defaultOptions = {
loop: true,
autoplay: true,
animationData: loadingData.default,
rendererSettings: {
preserveAspectRatio: "xMidYMid slice"
}
};
export default class Map extends React.Component {
//sets components for the map, how big the box is and where the map is centered when it opens
state = {
viewport: {
width: "75vw",
height: "50vh",
latitude: 40.4168,
longitude: 3.7038,
zoom: .5
},
tweetSpots: null, //data from the API
selectedSpot: null,
done: undefined, //for loading function
};
async componentDidMount() {
//searches the api for the hashtag that the user entered
await axios.get(`https://laffy.herokuapp.com/search/${this.props.toSearch}`).then(function(response) {
returnedKeywordSearch = response.data;
}) //if the api call returns an error, ignore it
.catch(function(err) {
return null;
});
//goes through the list of locations sent from the api above and finds the latitude/longitude for each
var count = 0;
while (count < returnedKeywordSearch.length) {
locationToSearch = returnedKeywordSearch[count].location;
if (locationToSearch !== undefined) {
var locationList = await axios.get(`https://api.mapbox.com/geocoding/v5/mapbox.places/${locationToSearch}.json?access_token=pk.eyJ1IjoibGF1bmRyeXNuYWlsIiwiYSI6ImNrODlhem95aDAzNGkzZmw5Z2lhcjIxY2UifQ.Aw4J8uxMSY2h4K9qVJp4lg`)
.catch(function(err) {
return null;
});
if (locationList !== null) {
if (Array.isArray(locationList.data.features) && locationList.data.features.length)
{
locationCoordinates.push(locationList.data.features[0].center);
if (returnedKeywordSearch[count].location!== null && returnedKeywordSearch[count].location!==""
&& locationList.data.features[0].center !== undefined)
{newArray.push({
id: returnedKeywordSearch[count].id,
createdAt: returnedKeywordSearch[count].createdAt,
text: returnedKeywordSearch[count].text,
name: returnedKeywordSearch[count].name,
location: returnedKeywordSearch[count].location,
coordinates: locationList.data.features[0].center
});
}
}
}
}
count++;
}
this.setState({tweetSpots: newArray});
this.setState({ done: true}); //sets done to true so that loading animation goes away and map displays
}
//is triggered when a marker on the map is hovered over
setSelectedSpot = object => {
this.setState({
selectedSpot: object
});
};
//creates markers that display on the map, using location latitude and longitude
loadMarkers = () => {
return this.state.tweetSpots.map((item,index) => {
return (
<Marker
key={index}
latitude={item.coordinates[1]}
longitude={item.coordinates[0]}
>
<img class="mapMarker"
onMouseOver={() => {
this.setSelectedSpot(item);
}}
src="/images/yellow7_dot.png" alt="" />
</Marker>
);
});
};
//closes popup when close is clicked
closePopup = () => {
this.setState({
selectedSpot: null
});
};
//renders map component and loading animation
render() {
return (
<div className="App">
<div className="map">
{!this.state.done ? (
<FadeIn>
<div class="d-flex justify-content-center align-items-center">
<Lottie options={defaultOptions} width={400} />
</div>
</FadeIn>
) : (
<ReactMapGL {...this.state.viewport} mapStyle="mapbox://styles/mapbox/outdoors-v11"
onViewportChange={(viewport => this.setState({viewport}))}
mapboxApiAccessToken="pk.eyJ1IjoibGF1bmRyeXNuYWlsIiwiYSI6ImNrODlhem95aDAzNGkzZmw5Z2lhcjIxY2UifQ.Aw4J8uxMSY2h4K9qVJp4lg">
{this.loadMarkers()}
{this.state.selectedSpot !== null ? (
<Popup
key={this.state.selectedSpot.id}
tipSize={5}
latitude={this.state.selectedSpot.coordinates[1]}
longitude={this.state.selectedSpot.coordinates[0]}
closeButton={true}
closeOnClick={false}
onClose={this.closePopup}
>
<div className="mapPopup">
<div className="header"> Tweet </div>
<div className="content">
{" "}
<p>
<b>Name:</b> {this.state.selectedSpot.name}
</p>
<p>
<b>Tweet:</b> {this.state.selectedSpot.text}</p>
<p>View Tweet in Twitter
</p>
</div>
</div>
</Popup>
) : null}
</ReactMapGL>
)}
</div>
</div>
);
}
}
Update: 4/28, per the answer I received, I update the render of the MainSearchBar.js to look like this:
render() {
const displayMap = this.state.displayMap;
return (
<div class="homepage-search-bar">
<input
type="text" name="search" value={this.state.value} onChange={this.handleChange} className="main-search-bar" placeholder="Search hashtags">
</input>
<button onClick={this.handleClick}>Search</button>
{this.state.displayMap && <Map toSearch = {this.searchTerm}></Map>}
</div>
)
}
When you click the button again, the state of MainSearchBar.js updates but the functional component MapDisplay does not and thus the Map does not update as well.
There are many ways to resolve this. Looking at the code, it looks like MapDisplay doesn't do much so you can consider replacing it with conditional rendering.
MainSearchBar.js
render() {
const displayMap = this.state.displayMap;
return (
<div class="homepage-search-bar">
<input
type="text" name="search" value={this.state.value} onChange={this.handleChange} className="main-search-bar" placeholder="Search hashtags">
</input>
<button onClick={this.handleClick}>Search</button>
{this.state.displayMap && <Map toSearch = {props.searchTerm}></Map>}
</div>
)
}
Then in your Map component, add a componentDidUpdate lifecycle method to detect updates to the prop which does the same thing as componentDidMount when the props are updated.
async componentDidMount(prevProps) {
if (props.toSearch != prevProps.toSearch) {
await axios.get(`https://laffy.herokuapp.com/search/${this.props.toSearch}`).then(function(response) {
returnedKeywordSearch = response.data;
}) //if the api call returns an error, ignore it
.catch(function(err) {
return null;
});
//goes through the list of locations sent from the api above and finds the latitude/longitude for each
var count = 0;
while (count < returnedKeywordSearch.length) {
locationToSearch = returnedKeywordSearch[count].location;
if (locationToSearch !== undefined) {
var locationList = await axios.get(`https://api.mapbox.com/geocoding/v5/mapbox.places/${locationToSearch}.json?access_token=pk.eyJ1IjoibGF1bmRyeXNuYWlsIiwiYSI6ImNrODlhem95aDAzNGkzZmw5Z2lhcjIxY2UifQ.Aw4J8uxMSY2h4K9qVJp4lg`)
.catch(function(err) {
return null;
});
if (locationList !== null) {
if (Array.isArray(locationList.data.features) && locationList.data.features.length)
{
locationCoordinates.push(locationList.data.features[0].center);
if (returnedKeywordSearch[count].location!== null && returnedKeywordSearch[count].location!==""
&& locationList.data.features[0].center !== undefined)
{newArray.push({
id: returnedKeywordSearch[count].id,
createdAt: returnedKeywordSearch[count].createdAt,
text: returnedKeywordSearch[count].text,
name: returnedKeywordSearch[count].name,
location: returnedKeywordSearch[count].location,
coordinates: locationList.data.features[0].center
});
}
}
}
}
count++;
}
this.setState({tweetSpots: newArray});
this.setState({ done: true}); //sets done to true so that loading animation goes away and map displays
}
}
#wxker Thanks for all your help! You certainly got me pointed in the right direction.
I changed render in MainSearchBar.js back to what it was originally.
And I added a ComponentDidUpdate to the Map component, as follows below:
async componentDidUpdate(prevProps) {
//searches the api for the hashtag that the user entered
if (this.props.toSearch !== prevProps.toSearch) {
and then the rest was the same as the original componentDidMount.
Related
I am trying to implement an onChange method that when the user type something it gets updated in real time and displayed in the div. The component that I am talking about is at the end of the code and it's called and it is an input that will be rendered 4 times on the dom. For a reason no value get shown on the div I mean {this.state.stake}. Could anyone help me in fixing that? Thanks
import React, { Component } from 'react';
import Stake from './stake';
class FetchRandomBet extends Component {
constructor(props) {
super(props);
this.state = {
loading: true,
bet: null,
value: this.props.value,
stake: ''
};
}
async componentDidMount() {
const url = "http://localhost:4000/";
const response = await fetch(url);
const data = await response.json();
this.setState({
loading: false,
bet: data.bets,
});
}
changeStake = (e) => {
this.setState({
stake: [e.target.value]
})
}
render() {
const { valueProp: value } = this.props;
const { bet, loading } = this.state;
if (loading) {
return <div>loading..</div>;
}
if (!bet) {
return <div>did not get data</div>;
}
return (
< div >
{
loading || !bet ? (
<div>loading..</div>
) : value === 0 ? (
<div className="bet-list">
<ol>
<p>NAME</p>
{
bet.map(post => (
<li key={post.id}>
{post.name}
</li>
))
}
</ol>
<ul>
<p>ODDS</p>
{
bet.map(post => (
<li key={post.id}>
{post.odds[4].oddsDecimal}
<div className="stake-margin">
<Stake
onChange={this.changeStake} />
{this.state.stake}
</div>
</li>
))
}
</ul>
</div>
Pass this.state.stake as a prop of Stake component.
<Stake
onChange={this.changeStake}
stake={this.state.stake}
/>
Then inside of the Stake component assign stake prop to value on an the input. It would look something like this.
const Stake =({stake, onChange})=>{
return <input value={stake} onChange={onChange} />
}
Good day so I have a question about firebase and perhaps my code as well I wrote some code in JSX and React linked to Firebase and the Button that I'm using to delete is not working properly.
I'm using Parent Child props to pass the function into the page that is needed to be deleted but there is no functionality. I need help thanks!
this is the parent where the function is located :
import React from 'react';
import fire from '../config/firebase';
import Modal from 'react-modal';
// import "firebase/database";
// import 'firebase/auth';
import NotesCard from './note-card';
Modal.setAppElement('#root');
export default class Notes extends React.Component {
_isMounted = false;
constructor(props) {
super(props);
this.state = {
notes: [],
showModal: false,
loggedin: false
};
this.handleOpenModal = this.handleOpenModal.bind(this);
this.handleCloseModal = this.handleCloseModal.bind(this);
this.handleAddNote = this.handleAddNote.bind(this);
this.handleRemoveNote = this.handleRemoveNote.bind(this);
}
componentDidMount() {
this._isMounted = true;
fire.auth().onAuthStateChanged((user) => {
if(user){
// call firebase from import fire
// grab userData and push it to the dataArray
fire.database().ref(`users/${user.uid}/notes`).on('value', (res) => {
const userData = res.val()
const dataArray = []
for(let objKey in userData) {
userData[objKey].key = objKey
dataArray.push(userData[objKey])
}
// set in the state
if(this._isMounted){
this.setState({
notes: dataArray,
loggedin: true
})
}
});
}else {
this.setState({loggedin: false})
}
});
};
componentWillUnmount() {
this._isMounted = false;
}
handleAddNote (e) {
e.preventDefault()
const note = {
title: this.noteTitle.value,
text: this.noteText.value
}
// reference where we can push it
const userId = fire.auth().currentUser.uid;
const dbRef = fire.database().ref(`users/${userId}/notes`);
dbRef.push(note)
this.noteTitle.value = ''
this.noteText.value = ''
this.handleCloseModal()
}
handleRemoveNote(key) {
const userId = fire.auth().currentUser.uid;
const dbRef = fire.database().ref(`users/${userId}/notes/${key}`);
dbRef.remove();
}
handleOpenModal (e) {
e.preventDefault();
this.setState({
showModal: true
});
}
handleCloseModal () {
this.setState({
showModal: false
});
}
render() {
return (
<div>
<button onClick={this.handleOpenModal}>create Note</button>
<section className='notes'>
{
this.state.notes.map((note, indx) => {
return (
<NotesCard
note={note}
key={`note-${indx}`}
handleRemoveNote={this.handleRemoveNote}
/>
)
}).reverse()
}
</section>
<Modal
isOpen={this.state.showModal}
onRequestClose={this.handleCloseModal}
shouldCloseOnOverlayClick={false}
style={
{
overlay: {
backgroundColor: '#9494b8'
},
content: {
color: '#669999'
}
}
}
>
<form onSubmit={this.handleAddNote}>
<h3>Add New Note</h3>
<label htmlFor='note-title'>Title:</label>
<input type='text' name='note-title' ref={ref => this.noteTitle = ref} />
<label htmlFor='note-text'>Note</label>
<textarea name='note-text' ref={ref => this.noteText = ref} placeholder='type notes here...' />
<input type='submit' onClick={this.handleAddNote} />
<button onClick={this.handleCloseModal}>close</button>
</form>
</Modal>
</div>
)
}
}
and this is where the function is being called :
import React from 'react';
import fire from '../config/firebase';
export default class NotesCard extends React.Component {
constructor(props) {
super(props);
this.state = {
editing: false,
note: {}
}
this.handleEditNote = this.handleEditNote.bind(this);
this.handleSaveNote = this.handleSaveNote.bind(this);
}
handleEditNote() {
this.setState({
editing: true
})
}
handleSaveNote(e) {
e.preventDefault()
const userId = fire.auth().currentUser.uid;
const dbRef = fire.database().ref(`users/${userId}/notes/${this.props.note.key}`);
dbRef.update({
title: this.noteTitle.value,
text: this.noteText.value
})
this.setState({
editing: false
})
}
render() {
let editingTemp = (
<span>
<h4>{this.props.note.title}</h4>
<p>{this.props.note.text}</p>
</span>
)
if(this.state.editing) {
editingTemp = (
<form onSubmit={this.handleSaveNote}>
<div>
<input
type='text'
defaultValue={this.props.note.title}
name='title'
ref={ref => this.noteTitle = ref}
/>
</div>
<div>
<input
type='text'
defaultValue={this.props.note.text}
name='text'
ref ={ref => this.noteText = ref}
/>
</div>
<input type='submit' value='done editing' />
</form>
)
}
return (
<div>
<button onClick={this.handleEditNote}>edit</button>
<button onClick={this.props.handleRemoveNote(this.state.note.key)}>delete</button>
{editingTemp}
</div>
)
}
}
Thank you in advance for taking a look at this code.
Second iteration answer
Working sandbox
Problem
looking at https://codesandbox.io/s/trusting-knuth-2og8e?file=/src/components/note-card.js:1621-1708
I see that you have this line
<button onClick={()=> this.props.handleRemoveNote(this.state.note.key)}>delete
Yet your state.note declared as an empty map in the constructor:
this.state = {
editing: false,
note: {}
}
But never assigned a value using this.setState in the component
Solution
Change it to:
<button onClick={()=> this.props.handleRemoveNote(**this.props.note.key**)}>delete</button>
First iteration answer
NotesCard's buttons is firing the onClick callback on render instead on click event.
This is because you have executed the function instead of passing a callback to the onClick handler
Change
<button onClick={this.props.handleRemoveNote(this.state.note.key)}>delete</button>
To
<button onClick={()=> this.props.handleRemoveNote(this.state.note.key)}>delete</button>
I am trying to setState of the address and coordinates as given by the "react-places-autocomplete".
I have already stored those components by only issue is in updating it
However, the changes are not updating and even though the program compiles as soon as I select a place it crashes.
Especially for getLatLng there isn't enough documentation for me to understand if I have to access another component within that.
How do I update the state of the address and coordinates via handleSelect?. Thanks!
import React, { Component } from "react";
import PlacesAutocomplete, {
geocodeByAddress,
getLatLng
} from "react-places-autocomplete";
export default class Itinerary extends Component {
constructor(props) {
super(props);
this.state = {
address:"",
coordinates:{lat: null,lng: null}
};
}
Create2DArray(rows,columns) {
var x = new Array(rows);
for (var i = 0; i < rows; i++) {
x[i] = new Array(columns);
}
return x;
}
handleChange = address => {
this.setState({ address });
};
handleSelect = address => {
const results = geocodeByAddress(address);
const latLng = getLatLng(results[0]);
this.setState({ coordinates: latLng });
};
render() {
return (
<div className="container">
<div className="row">
<div className="col-md-6 mt-5 mx-auto">
<PlacesAutocomplete
value={this.state.address}
onChange={this.handleChange}
onSelect={this.handleSelect}
>
{({ getInputProps, suggestions, getSuggestionItemProps, loading }) => (
<div>
<h1>Latitude: {this.state.coordinates.lat}</h1>
<h1>Longitude: {this.state.coordinates.lng}</h1>
<input size="50" height="40"{...getInputProps({ placeholder: "Type address" })} />
<div>
{loading ? <div>...loading</div> : null}
{suggestions.map(suggestion => {
const style = {
backgroundColor: suggestion.active ? "#41b6e6" : "#fff"
};
return (
<div {...getSuggestionItemProps(suggestion, { style })}>
{suggestion.description}
</div>
);
})}
</div>
</div>
)}
</PlacesAutocomplete>
</div>
</div>
</div>
);
}
}
geocodeByAddress and getLatLng are asynchronous functions so you have to wait these function until receiving data (read more about geocodeByAddress here). In handleSelect, results[0] is undefined when selecting, so that why your component is crashed. You should try this:
export default class Itinerary extends Component {
...
handleSelect = async address => {
const results = await geocodeByAddress(address);
const latLng = await getLatLng(results[0]);
this.setState({ coordinates: latLng });
};
...
}
I have 3 components: App, Map and ListPlaces. In ListPlaces component, when a user types something in the input element, I want to change the state(markers's state) in App.js to show only related markers on the map.
Edit: When I edit my typo, the error was disappeared. However, I think the logic is still wrong. Because when I write something in the input element, markers array would be 0 immediately. And of course, all markers are disappeared.
More Explanation:
After componentDidMount, my markers array holds 7 items. And Map component takes this markers array and render markers on the map. However, I need to control my markers from ListPlaces component according to value of input element. So I put this: onChange={e => {this.updateQuery(e.target.value); changeMarkersHandler(e.target.value)}} in onChange attribute of input element. (Omit the this.updateQuery, for now, you can focus on only changeMarkersHandler).
This changeMarkersHandler runs changeMarkers function in App.js, but I don't know why my marker arrays would be 0 immediately while changeMarkers function is working.
Note: I am using react-google-maps and I've omitted some code blocks which aren't related to question.
App.js
class App extends Component {
constructor(props) {
super(props);
this.state = {
places: [],
markers: [],
markerID: -1,
newMarkers: []
};
this.changeMarkers = this.changeMarkers.bind(this);
}
componentDidMount() {
fetch("api_url")
.then(response => response.json())
.then(data => {
this.setState({
places: data.response.venues,
markers: data.response.venues
});
})
.catch(error => {
console.log("Someting went wrong ", error);
});
}
changeMarkers(value) {
const newMarkers = this.state.markers.filter(
place => place.name === value
);
this.setState({
newMarkers : newMarkers,
markers: newMarkers
})
}
render() {
return (
<div className="App">
<Map role="application"
places={this.state.places}
markers={this.state.markers}
openInfoHandler={this.openInfo}
closeInfoHandler={this.closeInfo}
markerID={this.state.markerID}
googleMapURL="url_here" />
<ListPlaces changeMarkersHandler={this.changeMarkers} />
</div>
);
}
}
ListPlaces.js
import React, { Component } from "react";
import escapeRegExp from "escape-string-regexp";
class ListPlaces extends Component {
state = {
searchQuery: ""
};
updateQuery = query => {
this.setState({ searchQuery: query});
};
render() {
const { toggleListHandler, locations, openInfoHandler, changeMarkersHandler} = this.props;
let showLocations;
if (this.state.searchQuery) {
const match = new RegExp(escapeRegExp(this.state.searchQuery), "i");
showLocations = locations.filter(location =>match.test(location.name));
} else {
showLocations = locations;
}
return (
<div>
<aside>
<h2>Restaurants</h2>
<nav>
<div className="search-area">
<input
className="search-input"
type="text"
placeholder="Search Restaurant"
value={this.state.searchQuery}
onChange={e => {this.updateQuery(e.target.value); changeMarkersHandler(e.target.value)}}
/>
</div>
<ul>
{showLocations.map(location => {
return (
<li
key={location.id}
onClick={e =>
openInfoHandler(e, location.id)
}
>
{location.name}
</li>
);
})}
</ul>
</nav>
<p>some text</p>
</aside>
<a
onClick={toggleListHandler}
id="nav-toggle"
className="position"
>
<span />
</a>
</div>
);
}
}
export default ListPlaces;
You have a typo in you constructor.
this.changeMarkers(this.changeMarkers.bind(this));
should be
this.changeMarkers = this.changeMarkers.bind(this);
Ok, so the problem is pretty simple, but hard to explain.
I'm making an InputGenerator component, which generates a list of inputs.
Each generated input has a corresponding "remove" button next to it. The two elements (the input and the button) are wrapped in a div inside a map function. The div has a unique key prop. It looks like this (this is the whole component's jsx):
<div style={[InputGenerator.style.wrapper]}>
<div className="container" style={InputGenerator.style.container}>
{this.state.inputs.map((input, idx) => {
return (
<div key={idx} style={[
InputGenerator.style.row,
InputGenerator.count > 1 && idx > 0 ? InputGenerator.style.input.pushTop : {},
]}>
<InputText
id={input.id}
name={input.name}
placeholder={input.placeholder}
style={input.style}
/>
<Button
style={InputGenerator.style.remove}
type={Button.TYPES.BASIC}
icon="ion-close-round"
onClick={this.remove.bind(this, input.id)}
/>
</div>
);
})}
</div>
<div className="controls" style={InputGenerator.style.controls}>
<Button icon="ion-plus" type={Button.TYPES.PRIMARY} title="Add ingredient" onClick={this.add.bind(this)}/>
</div>
</div>
As you may see, all the inputs are kept in the this.state object and each one is given an unique id.
Here are the are add and remove methods:
add():
add() {
InputGenerator.count++;
const newInput = {
id: this.id,
name: this.props.name,
placeholder: this.props.placeholder,
style: this.style,
value: '',
};
const inputs = this.state.inputs;
inputs.push(newInput);
this.setState({ inputs });
}
remove():
remove(id) {
this.setState({
inputs: this.state.inputs.filter(i => i.id !== id),
});
}
The problem is:
I generate three inputs (using the add button)
I put random values in the inputs (e.g: 1, 2, 3)
I click on the remove button, corresponding to the first element (with value 1)
Result: Two input items with values 1 and 2
Expected: Two input items with values 2 and 3
The problem: I suggest that the key prop on the wrapping div is not enough for react to keep track of the input's values.
So, I'm open for ideas and suggestions how to proceed.
Here's an isolated sandbox to play around with my component and see the "bug" in action: https://codesandbox.io/s/5985AKxRB
Thanks in advance! :)
The issue you facing is because you are not handling state properly. You need to update state when you change input value.
handleChange(index,event) {
let inputs = this.state.inputs;
inputs[index].value = event.target.value;
this.setState({inputs:inputs})
}
DEMO : DEMO
Here is the updated code:
import React, { Component } from 'react';
import { render } from 'react-dom';
import Hello from './Hello';
const styles = {
fontFamily: 'sans-serif',
textAlign: 'center',
};
const App = () =>
<div style={styles}>
<InputGenerator />
</div>;
class InputGenerator extends Component {
constructor() {
super();
this.state = {
inputs: [],
};
}
componentWillMount() {
this.add();
}
handleChange(index,event) {
let inputs = this.state.inputs;
inputs[index].value = event.target.value;
this.setState({inputs:inputs})
}
add() {
InputGenerator.count++;
const newInput = {
id: this.id,
name: this.props.name,
placeholder: this.props.placeholder,
style: this.style,
value: '',
};
const inputs = this.state.inputs;
inputs.push(newInput);
this.setState({ inputs });
}
get id() {
if (this.props.id) {
return `${this.props.id}-${InputGenerator.count}`;
}
return `InputGeneratorItem-${InputGenerator.count}`;
}
get style() {
return [];
}
remove(id) {
var state = this.state;
state.inputs = state.inputs.filter(i => i.id !== id);
this.setState({
inputs: state.inputs
});
}
render() {
return (
<div>
<div className="container">
{this.state.inputs.map((input, idx) => {
return (
<div key={idx}>
<input
id={input.id}
name={input.name}
value = {input.value}
onChange={this.handleChange.bind(this,idx)}
placeholder={input.placeholder}
/>
<button onClick={this.remove.bind(this, input.id)}>
Remove
</button>
</div>
);
})}
</div>
<div className="controls">
<button onClick={this.add.bind(this)}>Add</button>
</div>
</div>
);
}
}
InputGenerator.count = 0;
render(<App />, document.getElementById('root'));