When I enter a character in the input box
The state updates with the new character
Then I loose focus on the input box
so I can only modify the box 1 keypress at at time
The input box is nested in 4 other components which includes 1 higher Order component (see below)
Page component
header(Modify)
InputForm
When I move the form code to the Page component it works.
How I keep the components separate (and reusable) and have the functionlity I need?
The form code is below
<input
key={props.id}
id={props.id}
type='text'
value={props.currentObject.name}
onChange={(event) => {
userFunctions.modifyItem(
editorState,
props.currentObject,
stateModifier,
event,
'name'
);
}}
/>
The full code for the entiure component is here
mport React, { Fragment } from 'react';
const InputForm = (props) => {
//prepare props
console.log('currentObject', props.currentObject);
const { editorState, stateModifier, userFunctions } = props.editorEssentials;
// const urlFormVisible = props.urlFormVisible;
//Styles
const componentStyle = 'container-flex-column';
// console.log('MOdify: currentState', editorState);
// console.log('MOdify: targetObject', currentObject);
// console.log('MOdify: stateModifier', stateModifier);
console.log('currentObject.name', props.currentObject.name);
return (
<Fragment>
<form
className={componentStyle}
// onSubmit={(event) =>
// userFunctions.submitItem(editorState, currentObject, stateModifier, event)
// }
>
<input
key={props.id}
id={props.id}
type='text'
value={props.currentObject.name}
onChange={(event) => {
userFunctions.modifyItem(
editorState,
props.currentObject,
stateModifier,
event,
'name'
);
}}
/>
{props.urlFormVisible && (
<input
type='url'
value={props.currentObject.url}
onChange={(event) =>
userFunctions.modifyItem(
editorState,
props.currentObject,
stateModifier,
event,
'url'
)
}
/>
)}
</form>
</Fragment>
);
};
export default InputForm;
The function operates on the state and is bound in the master component
WHAT I HAVE TRIED
There are some similar posts on stack overflow but they do not seem to answer my problem.
Changed the Key value in the input (although my original version of this had a no ket defined)
Double checked that the modifyItem function is bound correctly - it looks like it is and I guess if it wasn't the state would not update at all
Tried simplifing code to reduce the number of functions needed to run
MOVING THE COMPONENT THE CODE HAS MADE
I am not sure why but the Higher order component was the problem
when I changed the config from
Page component
header(Modify)
InputForm
to
Page component
header
Modify
InputForm
/header
It worked
Any ideas why I had this problem?
Related
I have an array with several "question" elements, each of them with a structure similar to this:
<><div className="row"><div className="col-12 col-md-9 col-lg-10 col-xl-10 col-xxl-10"><span>Question 1</span></div><div className="col-3 col-md-1 col-lg-1 col-xl-1"><div className="form-check"><input id="formCheck-1" className="form-check-input" type="checkbox" /><label className="form-check-label" for="formCheck-1">Yes</label></div></div><div className="col-3 col-md-1 col-lg-1 col-xl-1"><div className="form-check"><input id="formCheck-2" className="form-check-input" type="checkbox" /><label className="form-check-label" for="formCheck-2">No</label></div></div></div></>,
In order to give each element a bit of keyed structure, I store each array element in a helper component. The HTML of the questions is simply stored in "element". Simple:
const ElementoPaginacion = ({element}) =>{
return(
element
)
}
Since there can be many of these elements in this array, they are displayed with pagination. The displayed page is calculated (apparently correctly, using a simple calculation). The code snippet that calculates and displays it is as follows:
<>
{
//Calculate init index (it depends ont the current page) to show the questions, and the number of elements to show (its rangePages)
fullList.slice(currentPage * rangePages, (currentPage * rangePages) + rangePages).map((current) => (
<React.Fragment key={current.key}>
{current}
</React.Fragment>
))
}
What happens is that, when a change is made to that HTML by the user (for example, checking a checkbox), when changing the page, that change is NOT saved, if it is redrawed (for example, changing the page and returning to the same page afterwards). I am attaching images to see how it works:
We can see how we make changes to the questions on page 0, we change to page 1, and when we return to page 0 again, the changes (check ckeckboxes) have not been saved.
That could be happening?
------------- EDIT -------------
Okay. Right now, the idea is to save changes to any portion of HTML that is passed to us, be it a question with checkbox responses, or a radiobutton.
The problem: if we don't know what content is going to pass, what we have to save is all the content in html. Now, how can we save any HTML content that has been modified? I've tried creating this helper component, which wraps the HTML content passed to it inside a "div", but when clicked, how can I retrieve the new HTML content to reassign (ie the "newData" parameter)?
const ElementoPaginacion = ({element}) =>{
const [content, saveElement] = useState(element);
const saveData = (newData) =>{
saveElement(newData);
}
return(
<div onChange={saveData}>
{element}
</div>
)
}
Are you using a backend database? If not, the changes are stored in memory and will be lost each time you change the page.
You can use the useState hook to prevent the state of the forms from being lost each time the element is dismounted. Then the user can submit the state in its entirety after answering the questions.
Example:
import { useState } from 'react';
import { Button } from '#mui/material';
import { Checkbox } from '#mui/material';
function App() {
const [checkBoxIsMounted, setCheckBoxIsMounted] = useState(false);
const handleClick = () => {
setCheckBoxIsMounted(!checkBoxIsMounted)
}
return (
<>
<Button onClick={handleClick}>Mount Checkbox</Button>
{checkBoxIsMounted && <Checkbox />}
</>
);
}
export default App;
Note, I'm using MUI instead of Bootstrap, but the underlying principle is the same.
The above code snippet produces the following behavior:
If we add state to the checkbox, React will maintain the state in memory even after the component is dismounted:
import { useState } from 'react';
import { Button } from '#mui/material';
import { Checkbox } from '#mui/material';
function App() {
const [checkBoxIsMounted, setCheckBoxIsMounted] = useState(false);
const [checked, setChecked] = useState(false);
const handleClick = () => {
setCheckBoxIsMounted(!checkBoxIsMounted)
}
const handleChange = () => {
setChecked(!checked)
}
return (
<>
<Button onClick={handleClick}>Mount Checkbox</Button>
{checkBoxIsMounted && <Checkbox onChange={handleChange} checked={checked} />}
</>
);
}
export default App;
This modified snippet produces this behavior:
I have a component that implements a simle Formik form. The component also has listeners for an event generated by clicking on a LeafletJS map. I would like to be able to update the lat and lng fields whenever the event handler is called so that they can optionally be filled by clicking on the map instead of entering values directly.
import React, { useEffect } from 'react';
import { Formik, Form, Field } from 'formik';
const FormComponent = () => {
const onMapClick = e => {
console.log(e.detail.lat, e.detail.lng);
};
useEffect(() => {
document.addEventListener('map:click', onMapClick);
return () => {
document.removeEventListener('map:click', onMapClick);
};
}, []);
return (
<Formik
initialValues={{ username: 'guest', lat: 50.447243, lng: 30.524933 }}
onSubmit={values => { console.log(values); }}
>
{() => (
<Form id="test">
<Field type="text" name="username" />
<Field type="text" name="lat" />
<Field type="text" name="lng" />
<button type="submit">
Submit
</button>
</Form>
)}
</Formik>
);
};
export default FormComponent;
I have come across the Formik setFieldValue function but this only seems to be accessible from within the form. Likewise I cannot use UseEffect within the form function.
I have had some success by changing the intial values and using enableReinitialize but this resets other fields unless I keep track of all fields and update the initial values on each change.
What would be the recommended way of acheiving this? I should add that the code is a trimmed down example. The real form has way more fields.
This is how I would make this.
Imagine that you have component with form, component with map and some parent component that contain both of them.
I would use some useState in parent component that will handle change of selected lat and lng and provide this data to my form.
Then in form I'll use useEffect, so whenever those data is changed it will setFieldValue in form.
All this stuff will look like that (abstract example):
Parent component
const [coords, setCoords] = useState(null)
return (
<>
<Form coords={coords} />
<Map onChange={setCoords} />
</>
)
Form
useEffect(() => {
if(coords !== null) {
setFieldValue("lat", coords.lat)
setFieldValue("lng", coords.lng)
}
}, [coords])
This should work because setFieldValue can set values for fields in every moment of time, even after initialization. I'm not sure what problems have you had with useEffect and setFieldValue, so if my answer didn't helped you, provide some more code examples
I tried my best to search for a similar question before posting. I've got a Summary component in my project that accepts three user selected props (part #, start date, and end date), calls an API, then displays the fetched data. My problem is that the component re-renders every time the user changes one of the parameters (e.g. picks a new start date).
Ideally, the user would instead click an "Apply" button that would re-render the component using the set of three props. I tried using React.useRef() to create a reference to the component that I would use to update the Summary's state in a button's onClick event but no luck. I would greatly appreciate any advice on how to structure this situation. I'm editing the question to provide an extremely simple example below.
This is a sample application with an App.js and a Summary.jsx component. The code for App.js is as follows:
import React from "react";
import Summary from "./Components/Summary";
function App() {
const [input1, setInput1] = React.useState("");
const [input2, setInput2] = React.useState("");
const [input3, setInput3] = React.useState("");
return (
<>
<input
type="text"
id="input1"
onChange={(e) => setInput1(e.target.value)}
/>
<input
type="text"
id="input2"
onChange={(e) => setInput2(e.target.value)}
/>
<input
type="text"
id="input3"
onChange={(e) => setInput3(e.target.value)}
/>
<button
type="button"
onClick={() => {
alert("button has been clicked.");
}}
>
Apply
</button>
<Summary i1={input1} i2={input2} i3={input3} />
</>
);
}
export default App;
The code for Summary.jsx (contained in a Components folder) is as follows:
import React from "react";
const Summary = (props) => {
return (
<h1>{`Input 1: ${props.i1} Input 2: ${props.i2} Input 3:
${props.i3}`}</h1>
);
};
export default Summary;
You can see that as the user types into any of the inputs, it automatically re-renders the components as the state changes and thus the props that are supplied to the Summary component. Ideally, I would like no change to occur until the user hits the Apply button (I just supplied a bogus alert message as the onClick functionality for now).
If you don't want the Summary component to be re-rendered every time the parent component changes, I suggest using conditional rendering
Have a state isSubmited that defaults False, and set to True when user clicks Apply
Only render Summary when isSubmmited is True. If false, renders nothing (null)
If you want to switch isSummited back, pass a handler function setBack = () => setSubmited(false) as a props to the appropriate component
Something like this:
// App.js
function App() {
const [input1, setInput1] = React.useState("");
const [input2, setInput2] = React.useState("");
const [isSubmitted, setSubmitted] = React.useState(false);
return (
<>
<input
type="text"
id="input1"
value={input1}
onChange={(e) => setInput1(e.target.value)}
/>
<input
type="text"
id="input2"
value={input2}
onChange={(e) => setInput2(e.target.value)}
/>
<button
type="button"
onClick={() => {
alert("button has been clicked.");
setSubmitted(true);
}}
>
Apply
</button>
{/* Ternary operator */}
{isSumitted ? (
<Summary
i1={input1}
i2={input2}
afterDataFetch={() => setSubmitted(false)}
/>
) : null}
</>
);
}
// Summary.js
function Summary(props) {
const { i1, i2, i3, afterDataFetch } = props;
useEffect(() => {
fetchData();
// This will trigger 'setSubmitted(false)'
afterDataFetch();
});
}
Edit: As per request, to implement "keeping the old state and only send new state to Summary when click Submit", I have come up with a solution:
Besides the 3 input states, I also have a data state that is responsible for keeping the old states of the individual input fields (states from the previous Submit)
Therefore, the data state will only get updated when user clicks Submit
// App.js
function App() {
const initial = {
input1: "",
input2: "",
};
const [input1, setInput1] = useState("");
const [input2, setInput2] = useState("");
const [data, setData] = useState(initial);
return (
<>
<input
type="text"
id="input1"
value={input1}
onChange={(e) => setInput1(e.target.value)}
/>
<input
type="text"
id="input2"
value={input2}
onChange={(e) => setInput2(e.target.value)}
/>
<button
type="button"
onClick={() => {
setData({
input1: input1,
input2: input2,
});
}}
>
Apply
</button>
<Summary i1={data.input1} i2={data.input2} />
</>
);
}
// Summary.js
function Summary(props) {
const { i1, i2 } = props;
return <pre>{JSON.stringify({ i1, i2 })}</pre>;
}
export default React.memo(Summary);
Note the use of React.memo in the Summary.js file. This is for some rendering optimization. As you can imagine, the Summary component may get re-rendered (through setInput1 or setInput2), even though the data state has not changed. Therefore, using React.memo, per the docs:
If your component renders the same result given the same props, you can wrap it in a call to React.memo for a performance boost in some cases by memoizing the result. This means that React will skip rendering the component, and reuse the last rendered result.
If your Summary component fetches API every time it re-renders, that could be a pretty good optimization (prevent refetching when data has not changed - although you could use solution like "useSWR" to cache response anyway)
I developed a React App using Material-UI then I tried to create independent Components,
check the below independent components(<PanelDiv/>),
render() {
return (
<div className="panelDiv-component" style={{display:this.props.display}}>
<div className="panel-field-content">
<TextField
floatingLabelText={this.props.heading}
type={this.props.inputType}
value={this.props.value}
/>
{/* <input defaultValue className="form-control"></input>*/}
</div>
</div>
);
}
I tried to use the component like this,
<PanelDiv
heading='name'
inputType="text"
value={this.state.userData.name}
display={this.state.display[0]}
/>
But I can't update input field in this way.Also there is no error. How can i solve this? I want to update my input field.
Please check my input filed in the below image :
Because you are controlling the value of TextField by using value attribute but you are not updating the value by using onChange function, Since value of TextField is not changing so it becomes read only.
Solution:
Specify the onChange function with TextField and update the value inside that, Like this:
<TextField
floatingLabelText={this.props.heading}
type={this.props.inputType}
value={this.props.value}
onChange={this.props._change}
/>
Inside parent component:
_Change(event, value){
//update the value here
}
<PanelDiv
heading='name'
inputType="text"
value={this.state.userData.name}
_change={this._change}
display={this.state.display[0]}
/>
If you pass value as a prop to TextField you can't change that text!
On Material-UI official documentation they have used defaultValue="default val" as a prop.
So I used defaultValue as a prop! It worked fine for me!
<TextField
type="text"
defaultValue={this.props.val}
onChange={handleChange}
/>
Had the same error. I was not able to key in anything and it was because there was no name props included. example:
<TextField
type="email"
name='email'/>
Look at this example - https://jsfiddle.net/y857yeLq/
You should define a function, which is handles of text change and pass it to onChange property. In this example I used state for storing current text field value. I see, that you use props for that, but the principle is the same - function should update props in your case.
const { TextField, MuiThemeProvider, getMuiTheme } = MaterialUI;
class SliderExampleControlled extends React.Component {
state = {
value: '',
}
render() {
console.log(this.state);
return (
<div style={{width: '50%', margin: '0 auto'}}>
<TextField
hintText="Type here"
value={this.state.value}
onChange={(e) => this.setState(e.target.value)}
/>
</div>
);
}
}
const App = () => (
<MuiThemeProvider muiTheme={getMuiTheme()}>
<SliderExampleControlled />
</MuiThemeProvider>
);
ReactDOM.render(
<App />,
document.getElementById('container')
);
I was running into this problem as well and I needed the onClick function to take more than just the event I also needed it to take the row number because my state had an array representing the rows in a table. I was able to do that using this chunk of code
onChange={(e) => this.nameChange(e.target.value, row.row_num)}
then in my function called nameChange I was able to update the value
I am building a huge form which is made of atleast 50 inputs.
I have wrote a function in the form component that will save the value of the input to the form state:
PARENT FUNCTION
saveToState(details) {
const { company } = this.state;
company[details.part][details.element] = details.value;
this.setState({ company });
}
PASSING TO CHILD COMPONENT (INPUT)
<FieldInput
label="Name (as shown) *"
part="information"
element="displayName"
saveToState={this.saveToState}
/>
Here is the Input component:
import React, { Component } from 'react';
export default class FieldInput extends Component {
render() {
const { label, part, element, saveToState } = this.props;
return (
<div className="field">
<label>{label}</label>
<div className="ui input">
<input
type="text"
name={`${part}[${element}]`}
onChange={(e) => saveToState({
part,
element,
value: e.target.value
})}
/>
</div>
</div>
);
}
}
In result whenever I type something in the input It's taking it 200-300ms to really display what I wrote in the input, the state is getting updates instantly but whenever I type a character I set the new state of the parent form and update it which updates the whole component. The only way i found around it is to use saveToState within the parent component without passing it down. but that would require 1000's of line of code, Is there any way around this? Thanks!
There are alot of ways you can solve this problem. The easiest one and the fastest one is to use onBlur instead of onChange that way setState will happen not when you key pressing in the input but when the input loses focus.
import React, { Component } from 'react';
export default class FieldInput extends Component {
render() {
const { label, part, element, saveToState } = this.props;
return (
<div className="field">
<label>{label}</label>
<div className="ui input">
<input
type="text"
name={`${part}[${element}]`}
onBlur={(e) => saveToState({
part,
element,
value: e.target.value
})}
/>
</div>
</div>
);
}
}