React Component for editting data - reactjs

after getting data from API I want to show them into inputs,edit and update it in DB. I thought that beside the redux state, I should use also local state , but some people here say that is not good practise .So how I can handle my onChange methods and how pass updated data into axios.put method???
class ArticleEdit extends Component {
articleID = this.props.match.params.articleID;
state={
title:'',
text:'',
imgs:[]
}
onChange =(e)=>{}
componentDidMount(){
this.props.getArticleDetails(this.articleID);//get data from API
}
render() {
return (
<Fragment>
{this.props.article===undefined?(<Spin/>):
(
<div >
<div >
<Form onSubmit={this.handleSubmit}>
<Input name='title'
value='this.props.article.title'
onChange={this.onChange}/>
<Textarea
name='text'
value={this.props.article.title}
onChange={this.onChange}/>
<Button htmlType='submit'>Update</Button>
</Form>
</div>
</div>
)}
</Fragment>
)
}
}
const mapStateToProps = state =>({
article: state.articleReducer.articles[0],
})
export default connect(mapStateToProps,{getArticleDetails})
(ArticleEdit);

So how I can handle my onChange methods and how pass updated data into
axios.put method???
Well if that's literally what you want to do, then you can do it like this:
onChange = e => {
try {
const results = await axios.put(someurl, e.target.value)
console.log('results', results)
} catch(e) {
console.log('err', e)
}
}
This will call axios.put after every keystroke - however, I doubt that is what you want.

I've found the solution. I used the static method getDerivedStateFromProps,which calls everytime when props of your component has been changed.
static getDerivedStateFromProps(nextProps, prevState){
if(prevState.title===null && nextProps.article!==undefined){
return{
title:nextProps.article.title,
text:nextProps.article.text
}
}
return null;
}
after that is easy to work with onChange method ,which just call this.setState().

Related

what is the best way to share the state outside same component in react

I have encountered a problem and I am new to react. I wanted to find what is the best way to share react state outside of the same component for updating input value
function async callAjax(makeAjaxRequest){
//some ajax call
updateState();
return false;
}
function updateState() {
//I want to update state here from component and from outside
// component as well i.e call from callAjax function
//I wanted to submit form after state update, Can I pass formRef to
//chaining functions to submit or is there any better way?
}
export class test extends React.Component<testProps> {
constructor(props) {
super(props);
this.state = {
action: ''
}
AjaxRequest = await callAjax(
this.props.makeAjaxRequest
);
updateState();
render() {
<form>
<input type="hidden" value={this.state.action} />
</form>
}
}
I have done research around this found some like react sharedContext(useContext) but useContext is mostly used between different components for sharing data but I wanted inside single component. Can anyone help find best way to solve this problem?
Thanks in advance.
I think you shouldn't update the state of a component outside of the component as this may lead to problems. If you must have updateState outside of the component I think you can add callback which will be run when needed.
function async callAjax(makeAjaxRequest, stateCallback ){
updateState( stateCallback );
return false;
}
function updateState( stateCallback ) {
const newValue = 123
stateCallback( newValue )
}
export class Test extends React.Component<TestProps> {
constructor(props) {
super(props);
this.state = {
action: ''
}
}
AjaxRequest = await callAjax(
this.props.makeAjaxRequest,
( newValue ) => this.setState( newValue )
);
render() {
<form>
<input type="hidden" value={this.state.action} />
</form>
}
}
You can also find concept of Redux interesting.

React: react-datepicker won't update state

I am trying to do on change on datepicker, but the state doesn't update. I am using a state from props. So the date already exists. It shows expired dates. When I am doing the on change, it get stuck at the current date.
Something I am missing in the onChange handler?
constructor(props) {
super(props);
const {export} = this.props;
this.state = {showCalender: false};
this.date = export.expires;
this.handleChange = this.handleChange.bind(this);
}
static getDerivedStateFromProps(props, state) {
if (props.export.expires !== state.expires) {
return {
expires: props.export.expires
};
}
return null;
}
handleChange(date) {
this.setState({
expires: date
}, console.log(this.state.expires));
this.handleClick();
}
handleClick() {
this.setState({showCalender: !this.state.showCalender});
}
handleClear() {
this.setState({expires: ''});
}
render() {
const {expires, showCalender} = this.state;
const expiresDate = format(expires, 'MM/dd/yyyy');
return (
<div>
<FormGroup>
<FormControl
id="date"
value={expiresDate}
onChange={this.handleChange}
onClick={() => this.handleClick()}
title="set date"
aria-label="set date"
/>
<Button className="close-btn" onClick={() => this.handleClear()}>Clear</Button>
</FormGroup>
{ showCalender && (
<FormGroup>
<DatePicker
selected={expires}
onChange={this.handleChange}
inline
/>
</FormGroup>
)}
</div>
);
}
When you update the state you are triggering a re-render, but just before the next render, react is calling the getDerivedStateFromProps which there you are checking to see if the values are different:
static getDerivedStateFromProps(props, state) {
if (props.export.expires !== state.expires) {
return {
expires: props.export.expires
};
}
return null;
}
Which they are, as you just updated the state but the props stayed the same.
And then you update the state again but now you set it back to whatever the value in props is.
From the DOCS:
getDerivedStateFromProps is invoked right before calling the render method, both on the initial mount and on subsequent updates.
I'm not sure why you are trying to sync props and state, but usually this indicates a bad design of your app.
As mentioned in the comments, do not use a variable named export as its a reserved word since ES2015.
You might get an error of:
Unexpected keyword 'export'

React: Get state of children component component in parent

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 to recursively pass up all data from form?

I've got a form, it looks like this:
export default class BookingForm extends React.Component {
constructor(props) {
super(props);
this.state = {data: props.data};
}
render() {
const {booking, vehicleSelect, vehicleData, customer, drivers, fees, occasions} = this.props;
return (
<form className="grid-form">
<div className="row">
<div className="col">
<label>Is this a new or returning customer?</label>
<RadioMenu name="repeat_customer">
<RadioButton value="NEW">New Customer</RadioButton>
<RadioButton value="EXIST">Returning Customer</RadioButton>
</RadioMenu>
</div>
</div>
<div className="row new-customer-row">
<div className="col-1-2">
<label htmlFor="customer.first_name">First Name</label>
<Input id="customer.first_name" name="customer.first_name" type="text"/>
</div>
<div className="col-1-2">
<label htmlFor="customer.last_name">Last Name</label>
<Input id="customer.last_name" name="customer.last_name" type="text"/>
</div>
</div>
// .. more inputs ..
Where <RadioMenu> renders a list of <RadioButton>s which in turn contain an <Input>.
<Input> just looks like this:
export default function Input(attrs) {
return <input {...attrs}/>;
}
I made it a React component hoping I can do something useful with it.
Basically, I want all the form data to be stuffed into this.data.INPUT_NAME as soon as the input is changed. If the input name contains a . then I want to put it into a sub-object. For example, customer.last_name will be stored in this.state.data.customer.last_name. I also want to to use this.state.data to set the initial value for all the Input elements without having to explicitly add a value attribute to each of them; it should just know what value to pull out of the data object by using the input's name.
I don't know how to approach this. My first thought is that instead of returning the <form> I should put it into a variable, and then pre-process it, adding onChange and value attributes to anything of type Input, but even I try that, I don't think it would work on my RadioMenu because RadioMenu is not of type Input and I don't think I could recurse down into its children.
I could try using this context feature but the warnings are scaring me away.
I haven't looked into Flux/Reflux/Redux/xyz yet, but I don't think I really want to incorporate another framework this early in the game; I want to understand how to approach this properly before tucking it away.
So, how can I get all my form data into this.state.data?
The radio widgets look like this. I'm open to changing them if necessary. This is my first custom input widget.
// RadioMenu.jsx
import React from 'react';
import {cloneWithProps} from '../helpers/react-helpers';
import Input from './Input';
export default class RadioMenu extends Input {
constructor(props) {
super(props);
this.state = {value: props.value};
}
onChange = ev => {
this.setState({value: ev.target.value});
if(this.props.onChange) {
this.props.onChange(ev);
}
};
render() {
let {children, name, onChange, ...attrs} = this.props;
return (
<div className="radio-horizontal radio-menu" {...attrs}>
{cloneWithProps(children, btn => ({
name,
checked: btn.props.value == this.state.value,
onChange: this.onChange
}))}
</div>
);
}
}
// RadioButton.jsx
export default function RadioButton({children, ...attrs}) {
return (
<label className="checkable">
<input type="radio" {...attrs}/>
<span>{children}</span>
</label>
);
}
I was trying to use inheritance has so I could pluck out all the Inputs, regardless if they're custom or not, but I can't seem to get this to work in React. mycomp.type instanceof Input doesn't return true for sub-classes. I know React suggests composition over inheritance, but I don't know how to make that work.
This kind of problem is the reason we have libraries/patterns like Redux/Flux, but that doesn't mean it's not possible to solve without React, just a little bit harder.
In this specific case, you have a few options.
Child-Parent Events
If you change your <RadioButton /> component to accept an onChange handler, then you can listen for changes to the button and put them straight into your state.
function RadioButton(props) {
return (
// pass the onChange prop down
<input type="radio" onChange={props.onChange} />
);
}
Then update your <BookingForm /> component to make use of this new handler prop.
const setRadioState = e => this.setState({ radio: e.target.value });
// ...
<RadioMenu name="repeat_customer">
<RadioButton value="NEW" onChange={setRadioState}>New Customer</RadioButton>
<RadioButton value="EXIST" onChange={setRadioState}>Returning Customer</RadioButton>
</RadioMenu>
Accessing the Form
You can listen to the form for the submit event then iterate through the form's elements to build up an object you can put in your state.
render() {
// ...
<form onSubmit={this.onSubmit}>
// ...
},
onSubmit(e) {
const form = e.target;
const elements = form.elements;
// remove numeric keys
const keys = Object.keys(elements).filter(k => /[^\d]/.test(k);
const data = {};
keys.forEach(k => data[k] = elements[k].value);
this.setState(data);
}
If you aren't listening to the submit event and want to submit with say, a button press, then you'll need to use refs to get a instance of the form.
I'm more or less just making this approach up off the top of my head, so be wary of edge cases.
ReactLink will do what you want, but it's on its way out the door. Fortunately, it's easy to recreate this functionality in just a few lines of code.
Instead of using <input>, you can use this component:
import React from 'react';
export default class LinkedStateInput extends React.Component {
render() {
const {value, ...attrs} = this.props;
return <input {...attrs} value={value.value} onChange={ev => value.requestChange(ev.target.value)} />;
}
}
Usage example:
<LinkedStateInput value={this.linkState('passenger_count')} type="text"/>
Now just add a method to your BookingForm to handle the state updates:
linkState(name) {
return {
value: _.get(this.state.data, name, ''),
requestChange: value => {
let data = _.clone(this.state.data);
_.set(data, name, value);
this.setState({data})
}
}
}
I've used lodash here to handle deep sets/gets.
RadioMenu becomes even simpler because now it doesn't even have to remember its own state:
export default function RadioMenu({children, name, valueLink}) {
return (
<div className="radio-horizontal radio-menu">
{
valueLink
? cloneWithProps(children, btn => ({
name,
checked: btn.props.value === valueLink.value,
onChange: ev => valueLink.requestChange(ev.target.value)
}))
: cloneWithProps(children, {name})
}
</div>
);
}

Comparing this.props.prop and nextProps.prop with mobx

Is there any way to do something like this with mobx?
componentWillUpdate(nextProps) {
if (this.props.prop !== nextProps.prop) {
/* side effect, ex. router transition */
}
}
From my experience, this.props.prop is always equal to nextProps.prop even in componentWill- hooks...
UPD Here is more specific use case — simple login scenario:
Store
class AppStore {
#observable viewer = new ViewerStore();
}
class ViewerStore {
#observable id;
#observable name;
#observable error;
fromJSON(json = {}) {
this.id = json.id;
this.name = json.name;
}
clearError() {
this.error = null;
}
login({ email, password }) {
fetch('/login', {
method: 'POST',
body: JSON.stringify({
email,
password
}),
credentials: 'same-origin'
})
.then(json => {
this.fromJSON(json);
})
.catch(({ error }) => {
this.error = error;
})
}
}
React part
class LogInPage extends Component {
componentWillUpdate(nextProps) {
if (nextProps.viewer.id && !this.props.viewer.id) {
this.props.viewer.clearError();
this.props.onSuccess();
}
}
login = e => {
e.preventDefault();
this.props.viewer.login({
email: e.target.elements['email'].value,
password: e.target.elements['password'].value,
});
}
render() {
return (
<div>
<h1>Log In</h1>
<form onSuccess={this.login}>
<input type="text" name="email" />
<input type="password" name="password" />
<input type="submit" />
</form>
{ this.props.viewer.error && (
<div>
<b>Error</b>: {this.props.viewer.error}
</div>
)}
</div>
);
}
}
const store = new AppStore();
ReactDOM.render(<LogInPage
viewer={store.viewer}
onSuccess={() => alert("Hello!")}
/>
, container);
So basically, I just want to be able to do something when viewer id switches from undefined to something
Thanks for the update, that clarifies the question a lot. It depends a bit on what you want to do, but if you want to change the rendering of your component, it is enough to decorate your component with #observer from the mobx-react package. It will then automatically re-render when the store changes.
If you want do any additional actions, you can setup an autorun in your componentWillMount. Like
componentWillMount() {
this.autorunDisposer = mobx.autorun(() => {
if (nextProps.viewer.id && !this.props.viewer.id) {
this.props.viewer.clearError();
this.props.onSuccess();
}
})
}
But I don't see this pattern very often, as often it is cleaner to have this kind of logic just in your store. Personally I would expect a login form component to rougly look like this:
#observer class RequiresLogin extends Component {
render() {
if (this.props.viewerStore.id !== undefined) {
return this.props.children
} else {
return <LoginPage viewer={this.props.viewerStore}/>
}
}
}
#observer class MyTestPage extends Component {
render() {
return <RequiresLogin viewer={this.props.viewerStore}>
Secret Area!
</RequiresLogin>
}
}
#observer class LogInPage extends Component {
login = e => {
e.preventDefault();
this.props.viewer.login({
// tip: use React refs or the onChange handler to update local state:
email: e.target.elements['email'].value,
password: e.target.elements['password'].value,
});
}
render() {
if (this.props.viewer.id)
return null; // no need to show login form
return (
<div>
<h1>Log In</h1>
<form onSuccess={this.login}>
<input type="text" name="email" />
<input type="password" name="password" />
<input type="submit" />
</form>
{ this.props.viewer.error && (
<div>
<b>Error</b>: {this.props.viewer.error}
</div>
)}
</div>
);
}
}
In 99,9% of cases, this happens because somewhere else in your code props are mutated directly. And this is not allowed in react.
Typical scenario's include:
var myObject = this.props.someObject; // myObject is a POINTER to props, not copy
myObject.attribute = newAttribute; // Now props are directly mutated
var myArray = this.props.someArray;
myArray.splice(a,b); // splice directly mutates array
To solve, make a proper copy of the props object before you update the prop.
Well, actually the problem happened because I passed a mobx store as the single prop (in order to call it's methods like this.props.store.update()), so even though when something changes in store, component is updated by mobx, nextProps still holds the same reference to that store.
I finally ended up with destructuring store into component's props, when I need this kind of check.

Resources