I'm working on a form with Reactjs that gets some defaultValues from the parent component.
The problem is, the parent component set states of the values with a axios post and pass those values to the child as props. I can print those values on the child component with console.log but if I try to put those values on defaultValues on the TextFields I got a empty form, none of the values is rendered on the form.
Parent component:
export default class Parent extends Component {
constructor(props){
super(props);
this.state={
somevalue: '',
}
}
componentDidMount(){
this.getData();
}
getData = async () => {
await api.post('/getValue')
.then((res) => {
this.setState({
someValue: res.data;
})
}).catch((err) => {
console.log("Error: ", err);
})
}
render(){
return(
<Child value={this.state.someValue}/>
)}
}
Child component
export default function Child(props) {
console.log(props.value); // THIS LOG PRINT THE VALUE PROPERLY
return(
<TextField defaultValue={props.value}/>
)
}
This is basically my code structure, but it's not working. TextField still empty after this.
The property defaultValue is only used on the initial render. If you'll inspect your code you'll see that before the console.log outputs the value it will first output undefined. You can either change it to a controlled component by changing defaultValue to value. This way the value will display, but you'll need to add an onChange handler for the changes to the value.
function Child(props) {
// Using the value prop your value will display, but you will also have to pass an onChange handler to update the state in the parent
return <TextField value={props.value} />;
}
Or you can wait until the value is available before rendering your component
const { someValue } = this.state;
if (!someValue) {
return "loading the data";
}
return <Child value={someValue} />;
It depends on the exact situation what solution will be better. But I think it's likely you'll want to update the value in the input and do something with it, so I would go with the first situation.
Related
I have two components - a sign in form component that holds the form and handles login logic, and a progress bar similar to the one on top here in SO. I want to be able to show my progress bar fill up as the login logic executes if that makes sense, so as something is happening show the user an indication of loading. I've got the styling sorted I just need to understand how to correctly trigger the functions.
I'm new to React so my first thought was to define handleFillerStateMax() and handleFillerStateMin() within my ProgressBarComponent to perform the state changes. As the state changes it basically changes the width of the progress bar, it all works fine. But how do I call the functions from ProgressBarComponent as my Login component onSubmit logic executes? I've commented my ideas but they obviously don't work..
ProgressBarComponent:
class ProgressBarComponent extends React.Component {
constructor(props) {
super(props)
this.state = {
percentage: 0
}
}
// the functions to change state
handleFillerStateMax = () => {
this.setState ({percentage: 100})
}
handleFillerStateMin = () => {
this.setState ({percentage: 0})
}
render () {
return (
<div>
<ProgressBar percentage={this.state.percentage}/>
</div>
)
}
}
Login component:
class SignInFormBase extends Component {
constructor(props) {
super(props);
this.state = {...INITIAL_STATE};
}
onSubmit = event => {
const {email, password} = this.state;
// ProgressBarComponent.handleFillerMax()????
this.props.firebase
.doSignInWithEmailAndPass(email,password)
.then(()=> {
this.setState({...INITIAL_STATE});
this.props.history.push('/');
//ProgressBarComponent.handleFillerMin()????
})
.catch(error => {
this.setState({error});
})
event.preventDefault();
}
Rephrase what you're doing. Not "setting the progress bar's progress" but "modifying the applications state such that the progress bar will re-render with new data".
Keep the current progress in the state of the parent of SignInFormBase and ProgressBarComponent, and pass it to ProgressBarComponent as a prop so it just renders what it is told. Unless there is some internal logic omitted from ProgressBar that handles its own progress update; is there?
Pass in a callback to SignInFormBase that it can call when it has new information to report: that is, replace ProgressBarComponent.handleFillerMax() with this.props.reportProgress(100) or some such thing. The callback should setState({progress: value}).
Now, when the SignInFormBase calls the reportProgress callback, it sets the state in the parent components. This state is passed in to ProgressBarComponent as a prop, so the fact that it changed will cause he progress bar to re-render.
Requested example for #2, something like the following untested code:
class App extends Component {
handleProgressUpdate(progress) {
this.setState({progress: progress});
}
render() {
return (
<MyRootElement>
<ProgressBar progress={this.state.progress} />
<LoginForm onProgressUpudate={(progress) => this.handleProgressUpdate(progress)} />
</MyRootElemen>
)
}
}
The simply call this.props.onProgressUpdate(value) from LoginForm whenever it has new information that should change the value.
In basic terms, this is the sort of structure to go for (using useState for brevity but it could of course be a class-based stateful component if you prefer):
const App = ()=> {
const [isLoggingIn, setIsLoggingIn] = useState(false)
const handleOnLoginStart = () => {
setIsLoggingIn(true)
}
const handleOnLoginSuccess = () => {
setIsLoggingIn(false)
}
<div>
<ProgressBar percentage={isLoggingIn?0:100}/>
<LoginForm onLoginStart={handleOnLogin} onLoginSuccess={handleOnLoginSuccess}/>
</div>
}
In your LoginForm you would have:
onSubmit = event => {
const {email, password} = this.state;
this.props.onLoginStart() // <-- call the callback
this.props.firebase
.doSignInWithEmailAndPass(email,password)
.then(()=> {
this.setState({...INITIAL_STATE});
this.props.history.push('/');
this.props.onLoginSuccess() // <-- call the callback
})
.catch(error => {
this.setState({error});
})
event.preventDefault();
}
I have a page with a child component for a browse button, on the parent component, with a callback I set the state with the browsed file.
For some reason, because of a different callback passed to an higher parent component the state is not being set with the attached file.
If I remove the second callback this.props.handleChange('attachment', file); everything works fine. Any idea why? (Nothing is wrong with the second callback, no errors etc)
Attachment page:
export default class Attachment extends React.Component {
state = {
attachment: {},
};
handleAddAttachment = file => {
this.setState({ attachment: file });
this.props.handleChange('attachment', file); // this causes the previous line to not working.
};
render() {
const { attachment } = this.state;
return (
<Fragment>
<div>
<div>
Do you have
<br />
something to <LineBreak />
show me?
</div>
<div css={attach}>Upload attachments here</div>
<AttachmentButton handleAddAttachment={this.handleAddAttachment} />
<AttachedFile attachment={attachment} />
</div>
</Fragment>
);
}
}
makeHandleChange method on parent component:
makeHandleChange = (pageName, change) => {
this.setState({
ticket: { ...this.state.ticket, [pageName]: change },
});
};
I think because your parent component gets new state so it rerenders and also causes the children to rerender and cancel the state. To keep your children intact you can set a key props to your child component or you can use shouldComponentUpdate. like this in your parent
<Attachment key="attachment-key" />
I don't know why this doesn't work, but you may want to try a workaround: passing the function as the second argument to setState.
See an example here:
react set state callback correct way to pass an argument
Since we don't know the context of what this.props.handleChange() does, I'm giving a generic answer. Try changing the function this way:
handleAddAttachment = file => {
this.setState({
attachment: file
}, () => {
this.props.handleChange('attachment', file);
});
};
I have this container where and is not placed in the same level. How can I get the state of the Form when I click on the button (which is placed on the parent) ?
I've created a demo to address my issue.
https://codesandbox.io/s/kmqw47p8x7
class App extends React.Component {
constructor(props) {
super(props);
}
save = () => {
alert("how to get state of Form?");
//fire api call
};
render() {
return (
<div>
<Form />
<button onClick={this.save}>save</button>
</div>
);
}
}
One thing I don't want to do is sync the state for onChange event, because within Form there might be another Form.
To access a child instance from parent, your need to know about ref:
First, add formRef at top your App class:
formRef = React.createRef();
Then in App render, pass ref prop to your Form tag:
<Form ref={this.formRef} />
Finaly, get state from child form:
save = () => {
alert("how to get state of Form?");
const form = this.formRef.current;
console.log(form.state)
};
Checkout demo here
ideally, your form submit action belongs to the Form component
You can put button inside your From component and pass a submit callback to the form.
class App extends React.Component {
constructor(props) {
super(props);
}
save = (data) => {
// data is passed by Form component
alert("how to get state of Form?");
//fire api call
};
render() {
return (
<div>
<Form onFormSubmit={this.save} />
</div>
);
}
}
you can write the code like this
https://codesandbox.io/s/23o469kyx0
As it was mentioned, a ref can be used to get stateful component instance and access the state, but this breaks encapsulation:
<Form ref={this.formRef}/>
A more preferable way is to refactor Form to handle this case, i.e. accept onChange callback prop that would be triggered on form state changes:
<Form onChange={this.onFormChange}/>
One thing I don't want to do is sync the state for onChange event, because within Form there might be another Form.
Forms will need to handle this any way; it would be impossible to reach nested form with a ref from a grandparent. This could be the case for lifting the state up.
E.g. in parent component:
state = {
formState: {}
};
onFormChange = (formState) => {
this.setState(state => ({
formState: { ...state.formState, ...formState }
}));
}
render() {
return (
<Form state={this.state.formState} onChange={this.onFormChange} />
);
}
In form component:
handleChange = e =>
this.props.onChange({
[e.target.name]: e.target.value
});
render() {
return (
<input
onChange={this.handleChange}
name="firstName"
value={this.props.state.firstName}
/>
);
}
Here is a demo.
How can I get the value of datapicker in react toobox?
I am using custom components.
I am using 2 components first one is called InputDateCustom.js with the code below:
import DatePicker from 'react-toolbox/lib/date_picker/DatePicker';
import React, { Component } from 'react';
const datetime = new Date(2015, 10, 16);
datetime.setHours(17);
datetime.setMinutes(28);
export default class InputDateCustomizado extends Component{
state = {date2: datetime};
handleChange = (item, value) => {
console.log(item+" - "+value)
this.setState({...this.state, [item]: value});
};
render() {
return (
<div>
<DatePicker
label={this.props.label}
locale={localeExample}
name={this.props.name}
required={this.props.required}
onChange={this.handleChange.bind(this, 'date1')}
value={this.state.date1}
/>
</div>
);
}
}
Another component is called Cadastro.js that contains the following logic:
constructor(props) {
super(props);
this.state = {msg: '', fim_vigencia:'', nome:''}
this.setNome = this.setNome.bind(this)
this.setFimVigencia = this.setFimVigencia.bind(this)
}
setFimVigencia(evento){
console.log("date")
this.setState({fim_vigencia:evento.target.value});
}
InputDateCustomizado
id="fim_vigencia"
label="Fim"
name="fim_vigencia"
value = {this.state.fim_vigencia}
onSubmit = {this.setFimVigencia}
/>
Get the value in an onChange event or using the value prop. Doc examples: http://react-toolbox.com/#/components/date_picker
<DatePicker label='Birthdate' onChange={this.handleChange.bind(this, 'date1')} value={this.state.date1} />
You can get access to the value in the handleChange event allowing you to update your state with the currently selected date.
EDIT: Ah okay I think I understand what you are asking now. You have wrapped DatePicker with your own component and now you want to get the DatePicker value through the Cadastro.js component.
You need to create a method in the Cadastro.js that accepts state changes from the InputDateCustomizado component and then pass that function as a prop to the InputDateCustomizado component. In the InputDateCustomizado when the state changes, call the passed in function and it should update the state in the parent component. Then you will always have the datepicker value in the parent component.
It looks like you are almost there. You need to add an updateState function to the Cadastro.js component. In the InputDateCustomizado component handleChange event, you need to call this.props.updateState and pass in the new value.
In Cadastro.js
updateState = (data) => {
this.setState({
date: data.data //set your state here to the date
})
}
In InputDateCustomizado
handleChange = (item, value) => {
console.log(item+" - "+value)
this.setState({...this.state, [item]: value});
this.props.updateState(this.state);
};
I have container, binded with connect() to my store.
// GetActiveAccount.jsx
const VisibleActiveAccountsList = connect(
mapStateToProps,
mapDispatchToProps
)(ActiveAccountsSelector)
ActiveAccountsSelector is the presentational component. Inside this presentational component I load two child presentational components which are only
//activeAccountSelector render method
return(
<div>
<GatewaySelector gateways={gateways} onSelectGateway={ (e) => {
console.log(e.target.value);
}
}/>
<PairSelector gateway={gateways[0]} onSelectPair={ (e) => {
console.log(e.target.value);
}
}/>
</div>
)
What I want is that the selected value on gatewaySelector determine the value passed to PairSelector and update it when changed.
What is the right way to do that ? Going through actions seems way overkill for a simple select change update.
I guess I have to manager that in the parent (activeAccountSelector) but how ? It seems not advised to change state without going through the whole process (actions,reducers ...etc) shall I do that changing parents props ?
Yes. You have to manage that in state of the parent component. Simply, you can change set the value for gateway property of PairSelector from parent's state and change the state when gateway change in GatewaySelector.
Redux suggest to using react state for the state that doesn't matter to the app globally. Read this comment from the Redux author for more information.
class ActiveAccountSelector extends React.Component{
contructor(props){
super(props);
this.state = { selectedGateway: null};
this.onSelectGateway = this.onSelectGateway.bind(this);
}
onSelectGateway(e){
this.setState({ selectedGateway: e.target.value });
}
render(){}
....
return(
<div>
<form>
<GatewaySelector gateways={gateways} onSelectGateway={ this.onSelectGateway}
}/>
<PairSelector gateway={this.state.selectedGateway} onSelectPair={ (e) => {
console.log(e.target.value);
}}/>
</form>
</div>
);
}
}