I am a beginner in React. When I try to pass props from children to parent, the whole app is refreshed and the state recovery to initial. Is there any problem on my code? I have no idea how to solve it.
(ps: The following sentence is just for the number of words. Please don't see it. Why I have to add more details. If I have the ability to know every detail, I already solved it by myself)
Parent:
class App extends Component {
constructor(props) {
super(props);
this.state = {
stops: [],
legs: [],
driver: null,
finishedSign: false,
stopsSign: false,
legsSign: false,
driverSign: false
};
}
componentDidMount() {
console.log("-DID");
this.getStops();
this.getLegs();
this.getDriver();
}
// garentee all of data have received
checkFinished() {
const { stopsSign, legsSign, driverSign } = this.state;
const mark = stopsSign && legsSign && driverSign;
if (mark)
this.setState({
finishedSign: mark
});
}
// GET/STOPS API
getStops() {
fetch("/api/stops")
.then(res => res.json())
.then(stops => {
this.setState({ stops: stops, stopsSign: true }, () =>
console.log("stops fetched !", stops)
);
this.checkFinished();
});
}
// GET/LEGS API
getLegs() {
fetch("/api/legs")
.then(res => res.json())
.then(legs => {
this.setState({ legs: legs, legsSign: true }, () =>
console.log("driver fetched !", legs)
);
this.checkFinished();
});
}
// GET/Driver API
getDriver() {
console.log("-DRIVER");
fetch("/api/driver")
.then(res => {
return res.json();
})
.then(driver => {
this.setState(
{
driver: driver,
driverSign: true
},
() => console.log("driver fetched !", driver)
);
this.checkFinished();
});
}
// passing func
updateDriver(driver) {
console.log("update app!");
alert(driver);
}
renderMaps() {
return (
<Maps
stops={this.state.stops}
legs={this.state.legs}
driver={this.state.driver}
/>
);
}
renderDriverController() {
return (
<DiverController
legs={this.state.legs}
driver={this.state.driver}
update={this.updateDriver}
/>
);
}
render() {
return (
<div className="container">
<div className="row">
<div className="col-sm-3 col-md-3">
{this.state.finishedSign && this.renderDriverController()}
</div>
<div className="col-sm-8 col-md-8">
{
//this.state.finishedSign && this.renderMaps()
}
</div>
</div>
</div>
);
}
}
export default App;
children:
class DriverController extends Component {
constructor(props) {
super(props);
this.state = {
items: this.props.legs,
driver: this.props.driver
};
}
handleUpdate = e => {
const driver = null;
driver.activeLegID = this.refs.selectedLeg.value;
driver.legProgress = this.refs.selectedProgress.value;
if (driver.legProgress >= 0 && driver.legProgress <= 100)
this.props.update("test");
else alert("out of range!");
};
render() {
const { items, driver } = this.state;
console.log("items:", items);
return (
<form>
<hr />
<label>Driver Location:</label>
<div className="form-group">
<select
id="inputState"
className="form-control"
defaultValue={driver.activeLegID}
ref="selectedLeg"
>
{items.map(item => (
<option key={item.legID}>{item.legID}</option>
))}
</select>
<div className="input-group input-group-sm mb-3">
<div className="input-group-prepend">
<span className="input-group-text" id="inputGroup-sizing-sm">
Percentage:
</span>
</div>
<input
type="number"
className="form-control"
defaultValue={driver.legProgress}
ref="selectedProgress"
/>
</div>
<button onClick={this.handleUpdate} className="btn btn-primary">
Submit
</button>
<hr />
</div>
</form>
);
}
}
export default DriverController;
Try to use
onClick={() => this.handleUpdate}
You should not pass props from a child to its parent. Thats an anti-pattern.
You could pass a function from parent to child which will be triggered in
the child and hence updating the required state in the parent.
Refresh issue:
I think cause the child is wrapped inside a form.
Add
e.preventDefault() to your handleSubmit function to prevent the refresh
handleUpdate = e => {
e.preventDefault()
Related
I have this working stateful component in React:
import React, {Component} from "react";
class MyComponent extends Component {
constructor() {
super();
this.myRef = React.createRef();
this.state = {
array: [],
noresults: false
};
}
loadData = () => {
let data = this.myRef.current.value;
let url = "someurl="+data;
if(!data) {
return;
}
fetch(url)
.then((res) => res.json())
.then((json) => {
if(json.data.length > 0) {
this.setState({
array: json.data,
noresults: false
});
} else {
this.setState({
noresults: true
});
}
})
}
render() {
const { array, noresults } = this.state;
return (
<div>
<section>
<input ref={this.myRef} type="number"/>
<button onClick={this.loadData}>Click Here</button>
</section>
<ul>
{
array.map((e) => (
<li key = { e.key } >
{ e.data }
</li>
))
}
</ul>
{noresults && <div>No Records</div>}
</div>
);
}
}
export default MyComponent
I want to convert this to stateless like this:
function MyComponent() {
return (
<div>
<section>
<input type="number"/>
<button>Click Here</button>
</section>
<ul>
<li></li>
</ul>
<div>No Records</div>
</div>
);
}
export default MyComponent
Now how can I pass data of input to my method to make API call. Also how to pass the response from API to display as ul and li elements?
Just pass it as component props:
const MyComponent = ({array = [], loadData = () => {}}) => {
const [inputValue, setInputValue] = useState();
const handleInputChange = (evt) => {
setInputValue(evt.target.value);
};
return (
<div>
<section>
<input type="number" onChange={handleInputChange} />
<button onClick={e => loadData(e, inputValue)}>Click Here</button>
</section>
<ul>
{array.map((e) => (<li key={e.key}>{e.data}</li>))}
</ul>
{array.length === 0 && <div>No Records</div>}
</div>
);
}
For input, I created a local state which is updated on input change and passed it to loadData function. You can access the current value by parametrizing loadData function:
loadData = (e, currentInputValue) => { ... };
I am trying to create a textbox with an autocomplete feature that pulls suggestions from an API but having trouble updating the state after I receive the array from the API. I am modifying code from here: https://blog.bitsrc.io/building-a-react-autocomplete-component-from-scratch-b78105324f4c
I think I have to use ComponentDidMount() but I am not sure how to apply it to an onChange Function.
class App extends React.Component {
render() {
return (
<div className="App">
<Autocomplete/>
</div>
);
}
}
class Autocomplete extends React.Component{
state = {
activeOption: 0,
filteredOptions: [],
showOptions: false,
userInput: ''
};
onChange = (e) => {
const userInput = e.currentTarget.value;
fetch("/places", {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(userInput)
}).
then(response => response.json())
.then(data => data.filter(element =>
element.PlaceName.toLowerCase().indexOf(userInput.toLowerCase()) > -1))
.then(filteredOptions => this.setState(
{
activeOption: 0,
filteredOptions: filteredOptions,
showOptions: true,
userInput: e.currentTarget.value
}));
};
.
.
.
.
render() {
const {
onChange,
onClick,
onKeyDown,
state: { activeOption, filteredOptions, showOptions, userInput }
} = this;
let optionList;
if (showOptions && userInput) {
console.log(filteredOptions)
if (filteredOptions.length) {
optionList = (
<ul className="options">
{filteredOptions.map((optionName, index) => {
let className;
if (index === activeOption) {
className = 'option-active';
}
return (
<li className={className} key={optionName} onClick={onClick}>
{optionName}
</li>
);
})}
</ul>
);
} else {
optionList = (
<div className="no-options">
<em>No Option!</em>
</div>
);
}
}
return (
<React.Fragment>
<div className="search">
<input
type="text"
className="search-box"
onChange={onChange}
onKeyDown={onKeyDown}
value={userInput}
/>
<input type="submit" value="" className="search-btn" />
</div>
{optionList}
</React.Fragment>
);
}
}
ReactDOM.render(<App />, document.querySelector("#app"));
Once I try and run this, I get two errors: one for a synthetic event being reused for performance issues and one for a component changing an controlled input
You could put a setTimeout in the onChange method. And if the user doesn't type, you make the request, in other wise, you can't make the request
It looks like you were anticipating the filteredOptions variable to be a list of strings. It is actually a list of objects, which was causing React to throw the "object not allowed as children" error. Also, you can't use the event object to set state as it is already released. However, you were already storing the value in a variable which you could use, userInput. I updated your code with some very minor tweaks, and it appears to work. Take a look at a working example:
import React from "react";
import "./styles.css";
class App extends React.Component {
render() {
return (
<div className="App">
<Autocomplete />
</div>
);
}
}
class Autocomplete extends React.Component {
state = {
activeOption: 0,
filteredOptions: [],
showOptions: false,
userInput: ""
};
onChange = (e) => {
const userInput = e.currentTarget.value;
// Mock out the API call and JSON
Promise.resolve()
.then(() => {
const data = [
{ PlaceName: "Place 1" },
{ PlaceName: "Place 2" },
{ PlaceName: "Another Place 1" },
{ PlaceName: "Another Place 2" }
];
return data.filter(
(element) =>
element.PlaceName.toLowerCase().indexOf(userInput.toLowerCase()) >
-1
);
})
.then((filteredOptions) =>
this.setState({
activeOption: 0,
filteredOptions: filteredOptions,
showOptions: true,
userInput: userInput
})
);
};
render() {
const {
onChange,
onClick,
onKeyDown,
state: { activeOption, filteredOptions, showOptions, userInput }
} = this;
let optionList;
if (showOptions && userInput) {
if (filteredOptions.length) {
optionList = (
<ul className="options">
{filteredOptions.map((option, index) => {
let className;
if (index === activeOption) {
className = "option-active";
}
return (
<li
className={className}
key={option.PlaceName}
onClick={onClick}
>
{option.PlaceName}
</li>
);
})}
</ul>
);
} else {
optionList = (
<div className="no-options">
<em>No Option!</em>
</div>
);
}
}
return (
<React.Fragment>
<div className="search">
<input
type="text"
className="search-box"
onChange={onChange}
onKeyDown={onKeyDown}
value={userInput}
/>
<input type="submit" value="" className="search-btn" />
</div>
{optionList}
</React.Fragment>
);
}
}
export default App;
I have five Users in the array.
The code below displays each users info from the arrays when pop up button is clicked and everything works fine.
Now I have created a form to update each user's age based on their respective person Id on form submission via call to nodejs
backend. Am actually getting the result from nodejs backend..
Here is my issue.
Each time I entered age in the input and click on submission button Eg. for user 1. Instead of the age result to
appear near that very user 's name in the space provided in the button, it will appears on the body of the page as can be seen from
screenshots provided.
If call it as props For instance {this.props.messages.personAge}
as per below
<button
onClick={() => this.open(this.props.data.id, this.props.data.name)}
>
(Age should Appear Here-- ({this.props.messages.personAge})--)
{this.props.data.name}
</button>
It shows error
TypeError: Cannot read property 'personAge' of undefined
at User.render
Here is how am getting the response from nodejs server
componentDidMount(){
this.socket = io('http://localhost:8080');
this.socket.on('response message', function(data){
addAge(data);
});
const addAge = data => {
console.log(data);
//this.setState({messages: [...this.state.messages, data]});
this.setState({messages: [data]});
};
}
below is how am displaying the age result for each unique user
{this.state.messages.map((message, i) => {
//if (message.personId == this.props.data.id) {
//if (message.personId == person.id) {
if (message.personId) {
return (
<div key={i}>
<div>
({message.personAge}--years)
</div>
</div>
)
}
})}
</ul>
Here is the Entire Code
import React, { Component, Fragment } from "react";
import { render } from "react-dom";
import { Link } from 'react-router-dom';
import axios from 'axios';
import io from "socket.io-client";
class User extends React.Component {
open = () => this.props.open(this.props.data.id, this.props.data.name);
render() {
return (
<React.Fragment>
<div key={this.props.data.id}>
<button
onClick={() => this.open(this.props.data.id, this.props.data.name)}
>
(Age should Appear Here-- ({this.props.messages})--)
{this.props.data.name}
</button>
</div>
</React.Fragment>
);
}
}
class OpenedUser extends React.Component {
constructor(props) {
super(props);
this.state = {
hidden: false,
personId: '',
personAge: '',
};
}
componentDidMount(){
this.socket = io('http://localhost:8080');
var userId= this.props.data.id;
}
sendPost = (personId,personAge) => {
alert(personId);
alert(personAge);
this.socket.emit('messageUpdate', {
personId: personId,
personAge: personAge,
});
this.setState({personId: ''});
this.setState({personAge: ''});
}
toggleHidden = () =>
this.setState(prevState => ({ hidden: !prevState.hidden }));
close = () => this.props.close(this.props.data.id);
render() {
return (
<div key={this.props.data.id} style={{ display: "inline-block" }}>
<div className="wrap_head">
<button onClick={this.close}>close</button>
<div>user {this.props.data.id}</div>
<div>name {this.props.data.name}</div>
{this.state.hidden ? null : (
<div className="wrap">
<div className="wrap_body">Update Age Info</div>
<div> </div>
<div>
<label></label>
<input type="text" placeholder="personAge" value={this.state.personAge} onChange={ev => this.setState({personAge: ev.target.value})}/>
<br/>
<span onClick={ () => this.sendPost(this.props.data.id, this.state.personAge)} className="btn btn-primary">Update Age</span>
</div>
</div>
)}
</div>
</div>
);
}
}
class App extends React.Component {
constructor() {
super();
this.state = {
showingAlert_UserTyping: false,
shown: true,
activeIds: [],
messages: [],
data: [
{ id: 1, name: "user 1" },
{ id: 2, name: "user 2" },
{ id: 3, name: "user 3" },
{ id: 4, name: "user 4" },
{ id: 5, name: "user 5" }
]
};
}
componentDidMount(){
this.socket = io('http://localhost:8080');
this.socket.on('response message', function(data){
addAge(data);
console.log(' am add message' +data);
});
const addAge = data => {
console.log(data);
//this.setState({messages: [...this.state.messages, data]});
this.setState({messages: [data]});
};
} // close component didmount
toggle() {
this.setState({
shown: !this.state.shown
});
}
open = (id,name) => {
this.setState(prevState => ({
activeIds: prevState.activeIds.find(user => user === id)
? prevState.activeIds
: [...prevState.activeIds, id]
}));
};
close = id => {
this.setState(prevState => ({
activeIds: prevState.activeIds.filter(user => user !== id)
}));
};
renderUser = id => {
const user = this.state.data.find(user => user.id === id);
if (!user) {
return null;
}
return (
<OpenedUser messages={this.state.messages}
key={user.id}
data={user}
close={this.close}
/>
);
};
renderActiveUser = () => {
return (
<div style={{ position: "fixed", bottom: 0, right: 0 }}>
{this.state.activeIds.map(id => this.renderUser(id))}
</div>
);
};
render() {
return (
<div>
<ul>
{this.state.messages.map((message, i) => {
//if (message.personId == this.props.data.id) {
//if (message.personId == person.id) {
if (message.personId) {
return (
<div key={i}>
<div>
({message.personAge}--years)
</div>
</div>
)
}
})}
</ul>
{this.state.data.map(person => {
return (
<User key={person.id} data={person} open={this.open} />
);
})}
{this.state.activeIds.length !== 0 && this.renderActiveUser()}
</div>
);
}
}
Here is how I solved the issue:
I created a const resultdata and using map() and Filter() function.
Here is how I initialized the the variable resultdata and then pass it within state.data.map() method
const resultdata = this.state.messages.filter(res => res.personId == person.id).map(res => res.personAge));
I have a dynamic todo list I would like to add a "highlight" feature to. Each list item renders with markup for the highlight that should show only for the list item clicked.
export class Todo extends Component {
constructor(props) {
super(props);
this.state = {input: '', todos: this.getOldTodo()};
this.selectItem = this.selectItem.bind(this);
}
//shortened
selectItem(i) {
this.setState({selected: i});
if (this.state.selected == i) {
// --- this is the code that needs to change the right list items child's class
???.props.childen[2].className = "active";
// ---
console.log("true")
}
console.log(i);
}
render() {
//markup also shortened
this.state.todos.map((todos, i) => {
return (
//What do I pass to the method here?
<li key={todos.key} className="todo-li-item" onClick={this.selectItem.bind(this, i)}>
<span className="todo-item">{todos.text}</span>
<span onClick={this.deleteItem.bind(this, i)} className="delet-todo">✕</span>
// --- This is the child that needs its class changed when it's parent is clicked
<div id="todo-select" className={"hidden"}>
<span id="todo-select-top"></span>
<span id="todo-select-left"></span>
</div>
</li>
);
})
</ul>
</div>
);
}
}
This is painfully simple and yet so un-obvious as to what I use to do this in react, but hey I'm still learning. Thanks for your time.
You've been quite close. Here's my implementation.
Key takeaway: Don't mutate the state object.
selectItem(idx) {
this.setState(state => {
const todos = [
state.todos.slice(0, idx),
{ ...state.todos[idx], selected: ! state.todos[idx].selected },
state.todos.slice(idx + 1, state.todos.length),
]
return {
...state,
todos,
}
})
}
deleteItem(idx) {
this.setState(state => {
const todos = [...state.todos]
todos.splice(idx, 1)
return {
...state,
todos,
}
})
}
render() {
return (
<div>
<ul>
{this.state.todos.map((todo, idx) => (
<li
key={todo.key}
className={'todo-li-item'}
onClick={this.selectItem.bind(this, idx)}
>
<span className="todo-item">{todo.text}</span>
<span
onClick={this.deleteItem.bind(this, idx)}
className="delete-todo"
>
✕
</span>
<div id="todo-select" className={todo.selected && 'active'}>
<span id="todo-select-top" />
<span id="todo-select-left" />
</div>
</li>
))}
</ul>
</div>
)
}
The list item can be a stateless component, so the onSelect and onDelete become callback functions.
Deleting item with index may get you in trouble, since React will not re-render the entire list every time.
I don't know what's inside getOldTodo, but custructor cannot wait. So it will be null initially, if it's an async function.
There is an implementation using ES6 syntax.
Each list item is stateless:
const ListItem = props => {
const { todo, deleteItem, selectItem } = props;
return (
<li key={todo.key} className="todo-li-item" onClick={selectItem}>
<span className="todo-item">{todos.text}</span>
<span onClick={deleteItem} className="delet-todo">
✕
</span>
clicked
<div id="todo-select" className={'hidden'}>
<span id="todo-select-top" />
<span id="todo-select-left" />
</div>
</li>
);
};
All events are handled by a stateful component:
export class Todo extends Component {
state = {
input: '',
todos: [],
};
async componentDidMount() {
const todos = await this.getOldTodo();
this.setState({ todos });
}
render() {
return (
<div>
{this.state.todos.map(todo => (
<ListItem
todo={todo}
key={todo.key}
selectItem={() => {
this.selectItem(todo);
}}
deleteItem={() => {
this.deleteItem(todo);
}}
/>
))}
</div>
);
}
selectItem = todo => {
const idx = this.state.todos.findIndex(i => i.key === todo.key);
const todos = this.state.todos.slice();
const todo = { ...this.state.todos[idx] };
// change
todos[idx] = todo;
this.setState({
todos
});
}
deleteItem = todo => {
const idx = this.state.todos.findIndex(i => i.key === todo.key);
const todos = this.state.todos.splice(idx, 1);
this.setState({
todos
});
}
getOldTodo = async () => {
//...
}
}
Does this make sense to you?
I have a text box and on typing a name i get a list of options via an api call. I then populate a list and on click of a list item i am trying to fill the text in the input box. Firstly when i add a value prop to the input element i am unable to to type anything in the text box. Also on clicking the list item the value of the text doesnt update. Can someone tell me what im doing wrong here
class AutoCompleteSearch extends Component {
constructor(props) {
super(props);
this.state = {
value: '',
suggestions: []
}
}
autoSearchInputChange(e) {
let searchValue = e.target.value;
if (!searchValue.trim()) {
this.setState({ value : '', suggestions: [] })
return ;
}
if (searchValue.length >= 3) {
setTimeout(() => {
searchDoctorByName(searchValue).then((response) => {
this.setState({ value : searchValue, suggestions: response.data })
})}, 1000);
}
}
selectItemFromList(doctorObject) {
this.setState({
value: doctorObject.name ,
suggestions: [doctorObject]
});
console.log(this.state);
}
render() {
let renderItems = () => {
let listItems = this.state.suggestions.map((suggestion, index) => {
let doctorObject = suggestion;
return (
<li onClick={() => this.selectItemFromList(doctorObject)} key={index}>
{doctorObject.name}
</li>
);
});
return (
<div>
<ul className="doctor-list">
{listItems}
</ul>
</div>
);
}
return (
<div className="form-group">
<label className="control-label form-label col-md-4" htmlFor="auto-complete-search">Doctor Name</label>
<div className="col-md-20">
<input className="custom-input"type="text" ref="test" id="auto-complete-search" required
placeholder="Enter Doctor name"
onChange={(e) => this.autoSearchInputChange(e)}
/>
{this.state.suggestions.length > 0 ? renderItems() : null}
</div>
</div>
);
}
}
export default AutoCompleteSearch;