Update functionality in react - javascript

I am newbie to React and I am trying to do update on react. I don't get the exact logic to make it and hence I need your help.
On click of update, I managed to get the values of selected contact but later on, i don't get how to populate those value onto input text boxes and again on submit after change of values, update the selected contact. I came across onChange but I don't understand.
Clues i knew:
this.refs.name.value and this.refs.number.value are values which are in input textbox . And on update, we need to set these value into the state on that corresponding index.
My code and screenshot is below:
Person.js - number is taken as the key , considering individual number is unique
editcontact(id){
this.props.onChange(id);
}
render() {
return(
<div className="panel panel-default">
<div className="panel-heading">
<h4>{this.props.detail.name} </h4>
<a className="b" href="#" onClick={this.deletecontact.bind(this,this.props.detail.number)}> Delete </a>
<a className="b" href="#" onClick={this.editcontact.bind(this,this.props.detail.number)}> Update </a>
</div>
<h6 className="panel-body">{this.props.detail.number}</h6>
</div>
</div>
)
}
It is passed to Contact.js
editcontact(id)
{
this.props.onChange(id);
}
render() {
var details;
if(this.props.data){
details=this.props.data.map(dts=>{
return(
<Person key={dts.number} detail={dts} onChange={this.editcontact.bind(this)} onDelete={this.deletecontact.bind(this)}></Person>
)
})
}
Then comes App.js
handleEdit(id){
console.log(id);
let cts=this.state.contacts;
let index=cts.findIndex( x => x.number === id);
console.log(cts[index]);
this.setState({ selectedContact: cts[index]; });
}
render() {
return (
<div className="App">
<div className="page-header">
<h2>Contact list</h2>
</div>
<AddContact newOne={this.state.selectedContact} addcontact={this.handleAddition.bind(this)}></AddContact>
<Contact onChange={this.handleEdit.bind(this)} onDelete={this.handleDelete.bind(this)} data={this.state.contacts}> </Contact>
</div>
);
}
AddContact.js
constructor(){
super();
this.state={
newContact:{
name:'',
number:''
}
}
}
addcontact(e){
// console.log(this.refs.name.value);\
e.preventDefault();
this.setState({
newContact:{
name: this.refs.name.value,
number:this.refs.number.value
}
},function(){
console.log(this.state.newContact);
this.props.addcontact(this.state.newContact);
})
this.refs.name.value="";
this.refs.number.value="";
}
render() {
console.log(this.props.newOne);
return (
<div className="col-md-6">
<form onSubmit={this.addcontact.bind(this)}>
<div className="form-group">
<label>Name </label>
<input className="form-control" type="text" ref="name" />
</div>
<div className="form-group">
<label>Number</label>
<input className="form-control" type="number" ref="number" />
</div>
<input type="submit" value="Submit"/>
</form>
</div>
);
}
}

what you need is to tell your component that you have a new state and you want it to re-render.
handleEdit(id){
console.log(id);
let cts=this.state.contacts;
let index=cts.findIndex( x => x.number === id);
this.setState({ selectedContact: cts[index]; });
}
render() {
return (
<div className="App">
<div className="page-header">
<h2>Contact list</h2>
</div>
<AddContact addcontact={this.handleAddition.bind(this)}></AddContact>
<Contact onChange={this.handleEdit.bind(this)} onDelete={this.handleDelete.bind(this)} data={this.state.contacts}> </Contact>
</div>
);
with the setState function you updating the state of this compoent and also make it to re-render. now you can decide what you want to do with this data: this.state.selectedContact like passing it to AddContact

Don't use .bind(this... there is no reason to do it. Use attribute={() => this.functionName()}
Don't use different naming, use some pattern for attributes names. e.g. addcontact should be addContact
Don't use so long lines. Use Eslint to show you all of such tips.
It's really hard to read your code now, make it more readable and you will have better time editing it yourself.
And now to have update, i would suggest using pure functional component to display things and higher order component to manage state of data.
(props) => <form> .... <input value={props.email} /> ... </form;
and in parent component, which is responsible for all data management add state. In state you can save values and pass it into child components using props.
When you will advance in React, you will start using extra libraries to manage state of the app, e.g. Redux. It makes similar thing, but all app's state is in one place and then you can access it from any part of the app. E.g. you show these inputs, then jump to another state of app to add some other thing and then you can easily jump back to this state and still have input's values that are partly entered.
Just save values in state. No matter how you manage your app's state and push values to display components using props. Google, read, check some videos on Youtube and you will get it.
https://facebook.github.io/react/docs/thinking-in-react.html

Related

Manage many inputs between sibling components in React - best approach?

I am new to this topic.
In the parent component App I have two siblings : SideMenu and Document
The idea is that the user inputs values (SideMenu) which will be renedered on the Document. There will be more than 20 inputs. Since this is the first time I do this sort of state management, what are the best or maybe easiest approaches for this attempt of project.
function App() {
const [fullName, setFullName] = useState("")
const [address, setAddress] = useState("")
return (
<div className='app'>
<SideMenu />
<Document />
</div>
)
}
export default App
const SideBar = () => {
return (
<div>
<div className='input-group'>
<label>Full Name:</label>
<input type='text' />
</div>
<div className='input-group'>
<label>Address:</label>
<input type='text' />
</div>
</div>
)
}
const Document = () => {
return (
<div>
<h1>{fullName}</h1>
<p>{address}</p>
</div>
)
}
You can create an object for your form and store the form inputs in this object. Shared state can be stored in the most closest and common component (in your situation this is your parent component) according to your child components. [1]
When you make an update from a child component other child component that is sharing state will be syncronized and your state will be updated. You shouldn't use redux like state management tools unless you are need to set a global state.
I have made a revision for your example, this scenario allows you to pass the state in the parent component to the child components and update the state in the parent component from the child components.
I used a common event handler in the parent component, this functions captures the html event and we parse this event and update the state via this function. [2][3]
import "./styles.css";
import { useState } from "react";
import SideBar from "./SideBar";
import Document from "./Document";
export default function App() {
const [values, setValues] = useState({
fullName: "",
address: "",
postalCode: ""
});
function handleChange(event) {
setValues({ ...values, [event.target.name]: event.target.value });
}
return (
<div className="app">
<SideBar values={values} setValues={handleChange} />
<Document values={values} setValues={handleChange} />
</div>
);
}
export default function Document({ values }) {
return (
<div>
<h1>Document</h1>
<p>Full Name: {values.fullName}</p>
<p>Address: {values.address}</p>
<p>Postal Code: {values.postalCode}</p>
</div>
);
}
export default function Sidebar({ setValues }) {
return (
<div>
<div className="input-group">
<label>Full Name:</label>
<input type="text" name="fullName" onChange={setValues} />
</div>
<div className="input-group">
<label>Address:</label>
<input type="text" name="address" onChange={setValues} />
</div>
<div className="input-group">
<label>Address:</label>
<input type="text" name="postalCode" onChange={setValues} />
</div>
</div>
);
}
Code Sandbox Link: https://codesandbox.io/s/stackoverflow-74961591-wpmcnd
[1]: Passing Props to a component: https://beta.reactjs.org/learn/passing-props-to-a-component
[2]: Updating Objects in State: https://beta.reactjs.org/learn/updating-objects-in-state
[3]: HTML Change Event: https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/change_event
The default, go-to solution would be to use a container component that controls your form inputs state (this would be App in your case). Just pass the values and setters down one level by props and everything should be ok, simple and predictable.
If things start to get complicated then libraries such as Formik or react-hook-form help a lot. When it comes to managing multiple or complex forms that may also need validation they are your best bet. I suggest you take this approach.
Using Redux for this kind of situation is a huge anti-pattern. Redux global store should be only used for global state, not local form state.
Context API is well suited when you need to pass data to multiple deeply nested children. This way you do not need to pass props dozens of levels down the tree. However, it is usually used by 3rd party libraries such as the ones mentioned above (all of them).
You can use Formik library for handling many inputs. Wrap both components inside Formik and use formik's methods.
import { Formik } from 'formik';
<Formik
initialValues={{ fullName: '', address: '' }}
onSubmit={(values) => {
alert(JSON.stringify(values, null, 2));
}}
>
{({handleChange, values, handleSubmit}) => (
<form onSubmit={handleSubmit}>
<div className='app'>
<SideMenu
handleChange={handleChange}
/>
<Document values={values} />
<button type="submit">Submit</button>
</form>
)}
</Formik>
You dont need to create multiple states for each input. handlChange will handle itself. You just need add name or id attribute to input. Also you can access values of each input using the values parameter like values.fullName.
const SideBar = ({handleChange}) => {
return (
<div>
<div className='input-group'>
<label>Full Name:</label>
<input
type='text'
onChange={handleChange}
name="fullName"
/>
</div>
<div className='input-group'>
<label>Address:</label>
<input
type='text'
onChange={handleChange}
name="address"
/>
</div>
</div>
)
}
const Document = ({values}) => {
return (
<div>
<h1>{values.fullName}</h1>
<p>{values.address}</p>
</div>
)
}

React - selected value is not displayed after refreshing page

So, I'm using react-select to let the user pick a choice from a list of options. It's supposed to update on-change. I've verified that the selected option is indeed being updated into the database, and the input is being recognized by React upon checking it in the React chrome tools. What's puzzling is how it doesn't get displayed after refreshing the page.
class ContractBasicForm extends React.Component {
constructor (props) {
super(props)
this.state = {
contractingEntity: props.contracting_entity
}
componentWillReceiveProps(nextProps) {
this.setState({
contractingEntity: nextProps.contracting_entity
})
}
autoSetState = (newState) => {
this.setState(newState)
this.props.formSubmit()
}
render () {
return(
<div className="container-fluid">
<section className="row ml-2 mr-2 mt-2">
<article className="col-12 side-modal-form">
<SelectInput
header="Contracting Entity"
name="contract[contracting_entity]"
options={this.props.contracting_entity_opts}
value={this.state.contracting_entity}
userCanEdit={this.props.user_can_edit}
multi={false}
onChange={(e) => {
this.autoSetState({contracting_entity: e.value})
}}
/>
</article>
</section>
</div>
)
}
}
I have another input called Stage which is very similar to ContractingEntity, but its value is displayed after refreshing the page:
<SelectInput
header="Stage"
name="contract[stage]"
options={this.props.stage_opts}
value={this.state.stage}
userCanEdit={this.props.user_can_edit}
multi={false}
onChange={(e) => {
this.autoSetState({stage: e.value})
}}
/>
React app state will be initialised on page refresh. You need to persist such data in localStorage if you want to keep it after page refresh. This is considered as anti-pattern in react and it is recommended not to use this unless it becomes necessity.
I hope this made things clear for you.

React doesn't re-render on props change

I am kinda new to react and to the webitself.
this is my render function
render() {
const {repositories} = this.props
return (
<div className='mt4 bt b--black-20 boardingbox scrollarea-content' style={{overflow: 'scroll', height: '100vh'}}>
{
repositories.map((repo, index) => {
console.log(repo.name)
return <Note name={repo.name} desc={repo.name} key={index} onClick={ this.handleClick.bind(this) }/>
})
}
</div>
)
}
The repositories is changing the way I want, but for some reason the its not get re-rendered. I passing the repositiores property from the parent.
The first time I render it (click to the search button, get a response from the server, and set the repo array), its working fine. But at the 2nd search, when there is something in the array, its not working properly, and not re-render.
UPDATE:
The parent's render / onClick
render() {
const {repositories} = this.state
return (
<div className='w-third navpanel br b--black-20'>
<SearchBar onClick={this.onClick} onChange={this.onChange}/>
<RepoList repositories={repositories}/>
</div>
//<NewNote />
//<Tags />
//<NoteList />
);
}
onClick = (event) => {
const {searchTerm} = this.state
let endpoint = 'https://api.github.com/search/repositories?sort=stars&order=desc&q=' + searchTerm;
fetch(endpoint)
.then(blob => blob.json())
.then(response => {
if(response.items)
this.setState({ repositories: response.items });
})
}
UP-UPDATE:
Search Comp:
constructor({onClick, onChange}) {
super()
this.onClick = onClick
this.onChange = onChange
this.state = {
imageHover: false
}}
render() {
return (
<div className='flex items-center justify-between bb b--black-20'>
<div className='ma2 inputContainer w-100'>
<input className='pa1 pl4 boardingbox w-100 input-reset ba b--black-20 br4 black-50 f6' placeholder='repos' type="text" onChange={this.onChange}/>
</div>
<div className='mr2'>
<div className='boardingbox pointer contain grow'>
<img src={(this.state.imageHover) ? NoteImageOnHover : NoteImage} alt=''
onMouseOver={()=>this.setState({imageHover: true})}
onMouseOut={()=>this.setState({imageHover: false})}
onClick={this.onClick}/>
</div>
</div>
</div>
)}
first responde
second responde
and I am really ashamed that I could screw up like this.
So basicly the problem was:
return <Note name={repo.name} desc={repo.name} key={index} onClick={ this.handleClick.bind(this) }/>
So I was as stupid to use INDEX as a KEY so I could not add again the same key to the array.
Thanks anyway guys! :)
The root cause most probably is due to error in function binding.
In your SearchComponent you are using the "props" to create function bindings in the contructor. This can cause your SearchComponent to refer to wrong instance of the functions for onClick and onChange. Would suggest referring to the official documentation for more details.
you do not need to rebind the functions in your SearchComponent, you can just use the functions received in props.
<input className='pa1 pl4 boardingbox w-100 input-reset ba b--black-20 br4 black-50 f6' placeholder='repos' type="text" onChange={this.props.onChange}/>
<!-- snipped other irrelevant code -->
<img src={(this.state.imageHover) ? NoteImageOnHover : NoteImage} alt=''
onMouseOver={()=>this.setState({imageHover: true})}
onMouseOut={()=>this.setState({imageHover: false})}
onClick={this.props.onClick}/>
Why could be happening to cause this behavior
Remember, constructor is only called once the component instance is being constructed, once it has been created and remains alive, React lifecycles take over.
So, when you first render your screen, the component is created and since there is only 1 of everything, it kind of works.
When you run your first search: onChange/onClick callbacks modify the state of the parent component. Which then calls render on the parent component.
At this point, it is possible that your SearchComponent maybe holding on to the wrong instance of the call back methods, which would thus not set state on the parent and thus not force re-render.
Additional Notes on your constructor
Normally you shouldn't refer to props in your constructor, but if you need to, then you need to have it in the format below. Here are the relevant docs:
constructor(props) {
super(props);
// other logic
}

Passing Props from State to Child

I am getting data from a WebAPI call in my componentDidMount on my parent react component. I put the values into state.
When I render my form, I am just making custom labels with data (A component), and passing the label text, and the data to this component. (One for each field I am displaying). I pass the values from state, via props, to the child components.
But what I am finding is that my child components are rendering without data being populated... as this seems to happen before the api call happens. The api happens, the state gets set, but data never gets to the child components. I thought that the props would pass the updated state data to the components. I'm wrong. How should I achieve this? I want to load the data in my parent, and then render the child components, passing in the data.
componentDidMount() {
this.loadData();
}
loadData() {
var request = {
method: 'GET',
URL: "http://example.com/api/user/profile",
}
fetchData(request).then(response => {
if(response.errorcode != "OK")
{
console.log("Bad response from API. Need to redirect!")
}
else
{
this.setState(
{
firstname: response.payload.firstname,
surname: response.payload.surname,
email: response.payload.email,
countryId: response.payload.countryId,
countries: response.payload.countries
}
);
}
});
}
render() {
return (
<div>
<h2>Your Profile</h2>
<div className="row">
<div className="col-xs-6">
<DisplayLabel labelText={"Firstname"} data={this.state.firstname} />
<DisplayLabel labelText={"Surname"} data={this.state.surname} />
<DisplayLabel labelText={"Email Address"} data={this.state.email} />
<DisplayLabel labelText={"Country"} data={this.state.countryId} />
<div className="form-group row right">
<button className="btn btn-primary" onClick={this.openModal}>Edit</button>
</div>
</div>
</div>
<Modal isOpen={this.state.modalIsOpen} onAfterOpen={this.afterOpenModal} style={modalStyle}>
<ProfileEditBox closeMeCallback = {this.closeModal} />
</Modal>
</div>
)
}
My display label component is simply this. I'm new to this, and just trying to make reusable components:
import React, {Component} from 'react';
export default class DisplayLabel extends Component {
constructor(props)
{
super(props);
this.state = {
labelText: this.props.labelText,
data: this.props.data
}
console.log(this.state);
}
componentDidMount() {
this.setState({
labelText: this.props.labelText,
data: this.props.data
});
console.log("ComponentDidMount", this.state);
}
render() {
return (
<div>
<div className="row">
<div className="col-xs-12">
<label className="control-label">{this.state.labelText}</label>
</div>
</div>
<div className="row">
<div className="col-xs-12">
<strong><span>{this.state.data}</span></strong>
</div>
</div>
</div>
)
}
}
I need to wait for the API call to complete before I render the form?
This is a common problem in React. I usually try to resolve it with a pattern that shows some sort of loading indicator. So I would initialize your state like this in the constructor:
this.state = {
loading: true
}
And I would change your render to have a check for that loading bool:
render() {
if (this.state.loading) {
return (<h1>Loading, please wait...</h1>);
}
return (
<div>
<h2>Your Profile</h2>
<div className="row">
<div className="col-xs-6">
<DisplayLabel labelText={"Firstname"} data={this.state.firstname} />
<DisplayLabel labelText={"Surname"} data={this.state.surname} />
<DisplayLabel labelText={"Email Address"} data={this.state.email} />
<DisplayLabel labelText={"Country"} data={this.state.countryId} />
<div className="form-group row right">
<button className="btn btn-primary" onClick={this.openModal}>Edit</button>
</div>
</div>
</div>
<Modal isOpen={this.state.modalIsOpen} onAfterOpen={this.afterOpenModal} style={modalStyle}>
<ProfileEditBox closeMeCallback = {this.closeModal} />
</Modal>
</div>
)
}
Then you can set loading to false following a successful data pull, and your form will display correctly without any errors.
(I edited this to use your parent component rather than DisplayLabel, as it makes more sense there. However, the component name is omitted from your question).
I'm not sure you have to wait until the request complete (but you most likely do have to for UI issues), but i guess your problem is something else.
you took the props and included them inside your state (that's unneccery), and then when the component complete it's loading you update the state again, but the request probbaly compleate after the componnent complete it's loading.
if you must to put the props inside your state, you should add componentDidUpdate or componentWillReceiveNewProps lifecycle functions, not the componentDidMount.
but, if you don't have to (most likley), you might change your render funcion to:
render() {
return (
<div>
<div className="row">
<div className="col-xs-12">
<label className="control-label">{this.props.labelText}</label>
</div>
</div>
<div className="row">
<div className="col-xs-12">
<strong><span>{this.props.data}</span></strong>
</div>
</div>
</div>
)
}
(just changed the state to props)
Try that, Good Luck!
Just because constructor called once when component mount so you can do one thing, in your render take two var and assign data into that. And then use those variable to show data. Because child component will render Everytime state of parent will change but constructor will not.

React - Access or assign data object directly to Field component for FieldArray

I am having some concept problem in using FieldArray, I have multiple Select components wired to FieldArray, each select component should update value onChange and should delete it through api. To be able to perform api request I need to access other details for the field like id. I have setup my component something like shown below, it works fine without api call..
renderAdditionalSpeakers({fields}){
return (<div>
{
fields.map((speaker,index)=>
<Field
name={`${speaker}.speakerid`}
label="Choose Speaker"
type="select"
component={this.renderSelectWithDelete}
mandatory='false'
placeholder='Select Speaker'
opts={this.props.speakers}
key={`KEY-${index}`}
deleteaction={() => fields.remove(index)}
onChange={this.onSpeakerChange.bind(this)}
/>
)
}
<div className="form-group row">
<div className="col-sm-3"> </div>
<div className="col-sm-9 col-sm-offset-0">
<Button className="button-theme button-theme-blue" type="button" onClick={() => fields.push({})}>Link More Speaker</Button>
</div>
</div>
</div>);
}
and render method look like this
render() {
var {handleSubmit, invalid, pristine, submitting,speakers,tracks} = this.props;
return(
<div>
<form onSubmit={handleSubmit(this.onFormSubmit.bind(this))}>
<FieldArray name="additionalspeakers" component={this.renderAdditionalSpeakers.bind(this)}/>
</form>
</div>
);
}
From the above code, I need to call api with in ondeleteaction callback and onChange action. This is only possible when I can access the json object which consist of following values
{
"id":"1",
"speakerid":"23",
"sessionid":"102",
"eventid":"200"
}
How to achieve?
Thanks for help.
You can pass further arguments to your field array and use them within your render method, like
<FieldArray name="additionalspeakers" component={this.renderAdditionalSpeakers.bind(this)} props={{ onDeleteAction: this.ondeleteaction, onChange: this.onChange }} />
(where this.ondeleteaction and this.onChange are the callback function defined in your component).
Then you can declare the FieldArray as:
renderAdditionalSpeakers({fields, onDeleteAction, onChange}) {
and use the function callbacks within the component.

Categories

Resources