I have Routes:
export const Routes = (props) => {
return(
//----------
<Switch>
<Route exact path='/:id' render={props => <AdvFormEditContainer/>}/>
<Route path='/' render={props => <Main/>}/>
</Switch>
//----------
)
}
And there is <AdvFormContainer/> inside of <Main/>:
export class AdvFormContainer extends React.Component {
constructor(props) {
super(props);
this.submitForm = this.submitForm.bind(this);
this.onChange = this.onChange.bind(this);
this.state = {
titleError: '',
textError: ''
}
}
componentDidMount() { // Comment it out and no titleError message
this.focusForm.focus();
}
onChange(e) {
this.setState({
[e.target.name]: e.target.value
})
}
submitForm(e) {
e.preventDefault();
const {title, text} = this.state;
const {logged, addAdv} = this.props;
const author = logged[0].name;
if (title && text) {
addAdv(author, title, text);
setTimeout(() => {
this.setState({
title: '',
text: ''
})
}, 2000);
this.resetForm.reset();
} else if (!title) { // Get fired because there is no title yet!
this.setState({
titleError: 'Please, enter title!'
});
setTimeout(() => {
this.setState({
titleError: ''
})
}, 2000);
} else if (!text) {
this.setState({
textError: 'Please, enter the text!'
});
setTimeout(() => {
this.setState({
textError: ''
})
}, 2000);
}
}
render() {
return (
<div>
<AdvForm
{...this.props}
onChange={this.onChange}
submitForm={this.submitForm}
resetRef={el => this.resetForm = el}
focusRef={el => this.focusForm = el}
titleError={titleError}
textError={textError}
/>
</div>
)
}
}
And <AdvForm> component:
export const AdvForm = (props) => {
const {......., resetRef, focusRef} = props;
return (<form action="post"
ref={resetRef}
>
<MuiThemeProvider>
<TextField
ref={focusRef}
//---------------------
/>
</MuiThemeProvider>
//----------------------
</form>)}
Everything works fine if <Main/> and <AdvFormContainer/> mounted the first time.
But if there is a Route change from path="/:id" to path="/", <AdvForm/> inside of
<AdvFormContainer/> get submitted with empty inputs, which triggers Error message.
Such behavior occurs because of
componentDidMount() { // Comment it out and no titleError message
this.focusForm.focus();
}
If there is no focus(), there is no empty submitting.
<Button/> has nothing to do with it. I tried to get out onSubmit from <Button/> and attach it to <form>. The same behavior.
Please, out of curiosity, what's going on?
Related
I have four js file to demonstrate my problem.
home.js
command-input.js
tree-view.js
new-window.js
I want to open a new window from command-input.js file along with the command text. From tree-view.js file it works fine but not from command-input.js file.
In my program I have changed the state based on my tree node name and then call window opener method from child window to get the node name. It works fine but dose not work from command-input.js file(can't get the last command text).
Home.js
let browser = window;
let newWindow = null;
class Home extends React.PureComponent {
constructor(props) {
super(props);
this.state = { SBFCommand: {} };
browser = window.self;
browser.getCompName = () => {
return this.state.SBFCommand;
};
}
openNewWindow =(commandText)=> {
this.setState({
SBFCommand: {
ComponentName: compName,
SBCommand: comndText,
componentTitle: complabel,
},
});
newWindow = browser.open(
"/sbfrontapp/sb-user-component",
Math.random(),
"width=900,height=600,toolbar=no,scrollbars=no,location=no,resizable =no"
);
}
}
command-input.js
class CommanInput extends Component {
keyDownHandler = (event) => {
if (event.key === "Enter") {
if (event.target.value !== "") {
let cmdText = event.target.value.toUpperCase();
this.child.openNewWindow(cmdText);
}
}
};
render() {
return (
<div>
<input
type="text"
className="sb-magic-box"
onKeyDown={(e) => this.keyDownHandler(e)}
/>
<Home onRef={(ref) => (this.child = ref)} />
</div>
);
}
}
tree-view.js
class TreeView extends PureComponent{
onNodeClick = (node) => {
if (node.isLeaf) {
const cmdText = node.value.split("_")[1].toUpperCase();
if (cmdText !== null && cmdText !== "") {
this.child.openNewWindow(cmdText);
}
}
};
render() {
const { expanded, nodeData, loading } = this.state;
return (
<>
<Home onRef={(ref) => (this.child = ref)} />
{loading ? (
<LoadImg />
) : (
<CheckboxTree
expanded={expanded}
iconsClass="fa5"
nodes={nodeData}
onClick={this.onNodeClick}
onExpand={this.onExpand}
expandOnClick="true"
/>
)}
</>
);
}
}
new-window.js
class NewWindow extends React.Component {
state = {
loading: false,
ComponentName: "",
SBCommand: "",
};
componentDidMount() {
if (!window.opener) {
window.close();
}
const cmdDetails = window.opener.getCompName();
window.document.title = cmdDetails.componentTitle;
this.setState({
ComponentName: cmdDetails.ComponentName,
SBCommand: cmdDetails.SBCommand,
});
}
render() {
const { loading, ComponentName, SBCommand } = this.state;
return loading ? (
<LoadImg />
) : (
<React.Suspense fallback={<LoadImg />}>
<div className="sb-document-wrapper">
{SBCommand}
</div>
</React.Suspense>
);
}
}
class App extends Component {
constructor(props) {
super(props);
this.state = {
user: null,
userWorkSpecialIds: false,
};
}
isAuthorized = (user) => {
const { user } = this.state;
if (!user) return false;
else {
// check to see if user has special work permit id
this.hasWorkAccount(user);
return user.some((item) => user.email === item.id);
}
}
isLoaded = () => {
const { user } = this.state;
return user != null;
}
hasWorkAccount = () => {
getCustomerList(function(data) { // service gets users work place from firebase
const checkworkerSpecialIds = data.map((item) => {
return {
id: item.w_id,
name: item.w_name,
special_id: item.wspecial_id,
};
});
// checkworkerSpecialIds returns the user list of Ids correctly
console.log("checking for spacial_id : ", checkworkerSpecialIds);
// *** ERROR: ***
this.setState({ userWorkSpecialIds: !this.userWorkSpecialIds})
console.log("never reachs here when I have this.setState({}) ");
});
}
componentDidMount() {
const DataArray = [];
firebaseAppFirestore
.collection("users")
.get()
.then((snapshot) => {
snapshot.forEach((doc) => {
myDataArray.push({ id: doc.id, role: doc.role });
});
this.setState({ user: DataArray });
});
}
}
render() {
return (
<UserProvider>
<FirestoreProvider firebase={firebase}>
{this.isLoaded() ? (
this.isAuthorized(firebaseAppAuth.currentUser) ? (
<Router history={browserHistory}>
<Routes
users={this.state.users}
userWorkSpecialIds={this.state.userWorkSpecialIds} <----- Not updating after 1st render
/>
</Router>
) : (
<SignInView/>
)
) : (
<CircularProgress />
)}
</FirestoreProvider>
</UserProvider>
);
}
}
export default withFirebaseAuth({
firebaseAppAuth,
})(App);
I'm stuck on *** ERROR: ***, after I retrieve the checkworkerSpecialIds, should the
this.setState({ userWorkSpecialIds: !this.userWorkSpecialIds}), trigger the component and update userWorkSpecialIds.
I want to trigger the state userWorkSpecialIds updates and update the props
Currently the Routs props just receives does userWorkSpecialIds = false, never gets updated.
I have 3 components:
Search.js, Customers.js and Customer.js
In Search.js I have an input field. I want to send whatever value entered in the field over to the Customer.js component. I thought this would be straightforward, but I was wrong ...
I have also a context.js component that stores state for the application (I don't want to use redux because I don't know it yet).
Sorry but this is gonna be a long post as I want to give the background for this specific situation:
context.js
const Context = React.createContext();
const reducer = (state, action) => {
switch (action.type) {
case "SEARCH_CUSTOMERS":
return {
...state,
customer_list: action.payload,
firstName: ''
};
default:
return state;
}
};
export class Provider extends Component {
state = {
customer_list: [],
firstName: "",
dispatch: action => this.setState(state => reducer(state, action))
};
componentDidMount() {
axios
.get("/api")
.then(res => {
console.log(res.data);
this.setState({ customer_list: res.data });
})
.catch(error => console.log(error));
}
render() {
return (
<Context.Provider value={this.state}>
{this.props.children}
</Context.Provider>
);
}
}
export const Consumer = Context.Consumer;
Search.js: the input value I want to send to Customer is 'firstName'
class Search extends Component {
state = {
firstName: ""
};
onChange = e => {
this.setState({ [e.target.name]: e.target.value });
};
findCustomer = (dispatch, e) => {
e.preventDefault();
axios
.get("/api/customers", {
params: {
firstName: this.state.firstName,
}
})
.then(res => {
dispatch({
type: "SEARCH_CUSTOMERS",
payload: res.data
});
this.setState({ firstName: "" });
});
};
return (
<Consumer>
{value => {
const { dispatch } = value;
return (
<form onSubmit={this.findCustomer.bind(this, dispatch)}>
<div className="form-group">
<input
ref={input => {
this.nameInput = input;
}}
type="text"
name="firstName"
value={this.state.firstName}
onChange={this.onChange}
/>
the Customers.js:
class Customers extends Component {
render() {
const key = Date.now();
return (
<Consumer>
{value => {
const { customer_list} = value;
if (customer_list === undefined || customer_list.length === 0) {
return <Spinner />;
} else {
return (
<React.Fragment>
<h3 className="text-center mb-4">{heading}</h3>
<div className="row">
{customer_list.map(item => (
<Customer key={item.key} customer={item} />
))}
</div>
</React.Fragment>
);
}
}}
</Consumer>
);
}
}
export default Customers;
and Finally theCustomer.js: this is where I want the input value to be displayed:
const Customer = props => {
const { customer } = props;
return (
<div className="col-md-12">
<div className="card-body">
<strong>{customer.firstName}</strong> // not working
...
}
the {customer.firstName} does not show the value.
Is is necessary to go through the intermediate Customers.js component to pass the input value?
I would like to keep the architecture as is (with the context.js) and display the value in the Customer.js component.
I am really novice to React and I am stuck with this one.
I want to pass data from NewAction component to its parent NewActionSet.
I dont know what i am missing.
I am developing an on-boarding platform with a lot a components and I aim to send all the data entered into all the components to a server.
React parent Component:
import React from 'react'
import './NewActionSet.css'
import axios from 'axios'
import { Container, Segment, Header, Input } from 'semantic-ui-react'
import NewAction from './NewAction'
import 'bootstrap/dist/css/bootstrap.min.css'
class NewActionSet extends React.Component {
constructor (props) {
super(props)
this.state = {
actions: [],
actionType: '',
actionValue: '',
creationStatus: undefined
}
}
handleActions = value => {
this.setState({
actionsList: value
})
console.log(this.state.actionsList)
}
handleSubmit = event => {
event.preventDefault()
console.log(this.state)
axios
.post(
'/assistant/actions/',
{ ...this.state.values },
{ headers: {
xsrfHeaderName: 'X-CSRFToken',
xsrfCookieName: 'csrftoken'
},
withCredentials: true
}
)
.then(response => {
console.log(response)
this.setState({
creationStatus: true
})
})
.catch(error => {
console.log(error)
this.setState({
creationStatus: false
})
})
}
addNewAction = () => {
let { actions } = this.state
this.setState({
actions: [...actions, <NewAction onNewAction={this.handleActionstoParent} />]
})
}
handleActionstoParent = (action2Value, selectedAction) => {
this.setState({
actionType : selectedAction,
actionValue : action2Value
})
// console.log(this.state.actionType, this.state.actiondValue)
}
renderActions () {
return this.state.actions.map((action, index) => {
return (
<NewAction
key={index}
type={this.props.actionType}
content={action.content}
onNewAction={this.handleActionstoParent}
/>
)
})
}
render () {
let index = 0
return (
<Container>
<Header> Action sets </Header>
<Header color='grey' as='h3'>
SET #{index + 1}
</Header>
{this.renderActions()}
<button onClick={() => this.addNewAction()}> New Action </button>
</Container>
)
}
}
export default NewActionSet
React child component
import React from 'react'
import './NewActionSet.css'
import { Header, Dropdown } from 'semantic-ui-react'
import NewSpeechText from './NewSpeechText'
import NewAddPageURL from './NewAddPageURL'
import 'bootstrap/dist/css/bootstrap.min.css'
class NewAction extends React.Component {
constructor (props) {
super(props)
this.state = {
availableActions: [
{ key: 1, text: 'Navigate to page', value: 'Navigate to page' },
{ key: 2, text: 'Play speech', value: 'Play speech' }
],
selectedAction: '',
actionValue: '',
currentElement: ''
}
}
handleActionURL = (value) => {
this.setState({
actionValue: value
})
console.log(this.state.selectedAction, this.state.actionValue)
}
handleActionSpeech = (value) => {
this.setState({
actionValue: value
})
console.log(this.state.selectedAction, this.state.actionValue)
}
// Props to pass data to parent component --> NewActionSet.js
handleActionstoParent = (selected) => {
var action2Value = this.state.actionValue;
console.log(action2Value)
var action2Type = this.state.actionType
this.props.onNewAction(action2Value, action2Type)
console.log(action2Type)
// console.log(this.state.actionValue, this.state.selectedAction)
}
handleChange = (e, { value }) => {
let element
this.setState({
selectedAction: value
})
if (value === 'Navigate to page') {
element = <NewAddPageURL onNewAddPageURL={this.handleActionURL} onChange={this.handleActionstoParent()} />
} else if (value === 'Play speech') {
element = <NewSpeechText onNewSpeechText={this.handleActionSpeech} onChange={this.handleActionstoParent()} />
}
this.setState({
currentElement: element
})
}
render () {
const { value } = this.state
let index = 0
return (
<div className='action'>
<div className='container'>
<Header color='grey' as='h4'>
ACTION #{index + 1}
</Header>
<div className='row'>
<div className='col-md-4'>
<Dropdown
onChange={this.handleChange}
options={this.state.availableActions}
placeholder='Choose an action'
selection
value={value}
/>
</div>
<div className='col-md-4' />
<div className='col-md-4' />
</div>
<div style={{ marginBottom: '20px' }} />
{this.state.currentElement}
</div>
</div>
)
}
}
export default NewAction
Can you please assist?
Thanks a lot
The handleActionstoParent function in NewAction component is the problem.
When you send data from child to parent, actually the data is not updated data.
// Props to pass data to parent component --> NewActionSet.js
handleActionstoParent = (e) => {
this.setState({ [e.target.name]: e.target.value }, () => {
var action2Value = this.state.actionValue;
var action2Type = this.state.actionType;
this.props.onNewAction(action2Value, action2Type);
});
}
You could pass a function to NewAction, in example below we pass handleDataFlow function to our child component and then use it in our child component to pass data higher:
import React from 'react'
import './NewActionSet.css'
import { Header, Dropdown } from 'semantic-ui-react'
import NewSpeechText from './NewSpeechText'
import NewAddPageURL from './NewAddPageURL'
import 'bootstrap/dist/css/bootstrap.min.css'
class NewAction extends React.Component {
constructor (props) {
super(props)
this.state = {
availableActions: [
{ key: 1, text: 'Navigate to page', value: 'Navigate to page' },
{ key: 2, text: 'Play speech', value: 'Play speech' }
],
selectedAction: '',
actionValue: '',
currentElement: ''
}
}
handleActionURL = (value) => {
this.setState({
actionValue: value
})
console.log(this.state.selectedAction, this.state.actionValue)
}
handleActionSpeech = (value) => {
this.setState({
actionValue: value
})
console.log(this.state.selectedAction, this.state.actionValue)
}
// Props to pass data to parent component --> NewActionSet.js
handleActionstoParent = (selected) => {
var action2Value = this.state.actionValue;
console.log(action2Value)
var action2Type = this.state.actionType
this.props.onNewAction(action2Value, action2Type)
console.log(action2Type)
// console.log(this.state.actionValue, this.state.selectedAction)
}
handleChange = (e, { value }) => {
let element
this.setState({
selectedAction: value
})
this.props.handleDataFlow(value)
if (value === 'Navigate to page') {
element = <NewAddPageURL onNewAddPageURL={this.handleActionURL} onChange={this.handleActionstoParent()} />
} else if (value === 'Play speech') {
element = <NewSpeechText onNewSpeechText={this.handleActionSpeech} onChange={this.handleActionstoParent()} />
}
this.setState({
currentElement: element
})
}
render () {
const { value } = this.state
let index = 0
return (
<div className='action'>
<div className='container'>
<Header color='grey' as='h4'>
ACTION #{index + 1}
</Header>
<div className='row'>
<div className='col-md-4'>
<Dropdown
onChange={this.handleChange}
options={this.state.availableActions}
placeholder='Choose an action'
selection
value={value}
/>
</div>
<div className='col-md-4' />
<div className='col-md-4' />
</div>
<div style={{ marginBottom: '20px' }} />
{this.state.currentElement}
</div>
</div>
)
}
}
export default NewAction
Data flow in React is unidirectional. Data has one, and only one, way to be transferred: from parent to child.
To update parent state from child you have to send action (in props).
<NewAction updateParentState={this.doSmth} />
...
const doSmth = params => { this.setState({ ... })
and in NewAction you can call it in specific case
let parentUpdateState = ....
this.props.updateParentState(parentUpdateState);
I have this structure:
<Filter>
<Departure setDeparture={this.setDeparture} />
<Destination setDestination={this.setDestination} iataDeparture={this.state.departure} />
<DatePicker setDates={this.setDates} />
<SearchButton />
</Filter>
Now, I try to rerender Destination component when I update Departure component. Unfortunatelly my code doesn't work.
I don't use redux because I don't know it yet, so I try solutions without redux.
Please, help me with this problem.
Here goes code for each component:
Filter:
import React, { Component } from 'react';
import axios from 'axios';
import Departure from './Departure';
import Destination from './Destination';
import DatePicker from './DatePicker';
import SearchButton from './SearchButton';
class Filter extends Component {
constructor(props) {
super(props);
this.state = {
departure: '',
destination: '',
startDate: '',
endDate: '',
flights: []
}
}
handleSubmit = event => {
const getFlights = `https://murmuring-ocean-10826.herokuapp.com/en/api/2/flights/from/${this.state.departure}/to/${this.state.destination}/${this.state.startDate}/${this.state.endDate}/250/unique/?limit=15&offset-0`;
event.preventDefault();
console.log(this.state.departure);
console.log(this.state.destination);
console.log(this.state.startDate);
console.log(this.state.endDate);
axios.get(getFlights)
.then(response => {
this.setState({ flights: response.data.flights });
console.log(getFlights);
console.log(this.state.flights);
this.props.passFlights(this.state.flights);
});
}
setDeparture = departure => {
this.setState({ departure: departure });
}
setDestination = destination => {
this.setState({ destination: destination });
}
setDates = (range) => {
this.setState({
startDate: range[0],
endDate: range[1]
});
}
render() {
return (
<section className='filter'>
<form className='filter__form' onSubmit={this.handleSubmit}>
<Departure setDeparture={this.setDeparture} />
<Destination setDestination={this.setDestination} iataDeparture={this.state.departure} />
<DatePicker setDates={this.setDates} />
<SearchButton />
</form>
</section>
);
}
}
export default Filter;
Departure:
import React, { Component } from 'react';
import axios from 'axios';
const url = 'https://murmuring-ocean-10826.herokuapp.com/en/api/2/forms/flight-booking-selector/';
class Departure extends Component {
constructor(props) {
super(props);
this.state = {
airports: [],
value: '',
iataCode: ''
}
}
componentDidMount() {
axios.get(url)
.then(data => {
const airports = data.data.airports;
const updatedAirports = [];
airports.map(airport => {
const singleAirport = [];
singleAirport.push(airport.name);
singleAirport.push(airport.iataCode);
updatedAirports.push(singleAirport);
return singleAirport;
});
this.setState({
airports: updatedAirports,
value: airports[0].name,
iataCode: airports[0].iataCode
});
this.props.setDeparture(this.state.iataCode);
});
}
handleChange = event => {
const nameValue = event.target.value;
const iataCode = this.state.airports.find(airport => {
return airport[0] === nameValue;
});
this.setState({
value: event.target.value,
iataCode: iataCode[1]
});
this.props.setDeparture(iataCode[1]);
}
render() {
const departureNames = this.state.airports;
let departureOptions = departureNames.map((item, index) => {
return (
<option value={item[0]} key={index}>{item[0]}</option>
);
});
return (
<div className='filter__form__select'>
<select value={this.state.value} onChange={this.handleChange}>
{departureOptions}
</select>
</div>
);
}
}
export default Departure;
Destination:
import React, { Component } from 'react';
import axios from 'axios';
const url = 'https://murmuring-ocean-10826.herokuapp.com/en/api/2/forms/flight-booking-selector/';
class Destination extends Component {
constructor(props) {
super(props);
this.state = {
routes: {},
airports: [],
value: '',
iataCode: '',
iataDestinationAirports: '',
options: []
}
}
componentDidMount() {
axios.get(url)
.then(data => {
const routes = data.data.routes;
const airports = data.data.airports;
const updatedAirports = [];
airports.map(airport => {
const singleAirport = [];
singleAirport.push(airport.name);
singleAirport.push(airport.iataCode);
updatedAirports.push(singleAirport);
return singleAirport;
});
this.setState({
routes: routes,
airports: updatedAirports,
});
})
.then(() => {
this.getNamesFromIataCode();
this.props.setDestination(this.state.iataDestinationAirports);
});
}
componentDidUpdate(prevProps) {
if (this.props.iataDeparture !== prevProps.iataDeparture) {
this.setState({ iataCode: this.props.iataDeparture });
() => this.getNamesFromIataCode();
};
}
handleChange = (event) => {
const nameValue = event.target.value;
const iataCode = this.state.airports.find(airport => {
return airport[0] === nameValue;
});
this.setState({
value: event.target.value,
iataDestinationAirports: iataCode[1]
});
this.props.setDestination(iataCode[1]);
}
getNamesFromIataCode = () => {
const iataCode = this.state.iataCode;
console.log(iataCode);
const destinationNames = this.state.routes[iataCode];
let destionationAirports = destinationNames.map(item => {
return this.state.airports.filter(el => {
return el[1] === item;
});
});
let arrayOfOptions = [];
let firstOptionIataCode = '';
let firstOptionName = '';
let destinationOptions = destionationAirports.map((item, index) => {
console.log(item);
arrayOfOptions.push(item[0]);
return (
<option value={item[0][0]} key={index}>{item[0][0]}</option>
);
});
firstOptionIataCode = arrayOfOptions[0][1];
firstOptionName = arrayOfOptions[0][0];
console.log(firstOptionIataCode);
this.setState({
options: destinationOptions,
iataDestinationAirports: firstOptionIataCode,
value: firstOptionName
});
console.log(this.state.iataDestinationAirports);
console.log(this.state.options);
return destinationOptions;
}
render() {
const selectionOptions = this.state.options;
return (
<div className='filter__form__select'>
<select value={this.state.value} onChange={this.handleChange}>
{selectionOptions}
</select>
</div>
);
}
}
export default Destination;
As Tholle mentioned, you need to lift the state up. Here's an example:
import React from "react";
import ReactDOM from "react-dom";
const A = ({ users, selectUser }) => {
return (
<React.Fragment>
<h1>I am A.</h1>
{users.map((u, i) => {
return <button onClick={() => selectUser(i)}>{u}</button>;
})}
</React.Fragment>
);
};
const B = ({ user }) => {
return <h1>I am B. Current user: {user}</h1>;
};
const C = ({ user }) => {
return <h1>I am C. Current user: {user}</h1>;
};
class App extends React.Component {
state = {
users: ["bob", "anne", "mary"],
currentUserIndex: 0
};
selectUser = n => {
this.setState({
currentUserIndex: n
});
};
render() {
const { users, currentUserIndex } = this.state;
const currentUser = users[currentUserIndex];
return (
<React.Fragment>
<A selectUser={this.selectUser} users={users} />
<B user={currentUser} />
<C user={currentUser} />
</React.Fragment>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Working example here.