ReactJS Lifting State fully or Partally - reactjs

I have an application where in some cases I am lifting the state to the closest ancestor. however, I came across an instance where I am lifting some of the states and leaving others locally. Is this a good practice? Does it make confusing long term?
textField Component
export const FieldInput = props => {
// local
const {
inputId,
inputLabel,
inputValue,
} = props;
return(
<>
<TextField
id={inputId}
label={inputLabel}
value={inputValue}
className={props.classes.textField}
onChange={props.handleChange}
data=""
/>
</>
)
}
export default withStyles(styles)(FieldInput);
closest ancestor with lifted state
const [textState, setTextState] = useState({
textTitle: "Please Provide Title",
textRequiermentPoc: "Please Provide POC",
textDirectingRequierment: "",
textRequiermentDescription: "",
});
const handleTitleText = (event) => {
setTextState({
textTitle: event.target.value,
})
}
const handleRequiermentPocText = (event) => {
setTextState({
textRequiermentPoc: event.target.value,
})
}
INPUT FORM
<FieldInput
inputLabel={"Requirement point of contact"}
inputId={"requiermentPOC"}
inputValue={textState.textRequiermentPoc}
handleChange={handleRequiermentPocText}>
</FieldInput>

Related

Using a High Order Component does not send data to the form in React

I want to adjust a demo provided by some tutorial about React Design Patterns, subject: Higher Order Component, and want to use an external data source from the url:
https://jsonplaceholder.typicode.com/users/1
to display the data within my form.
I guess since it's an async call, my Form always displays the "Loading part". What's the best way to solve this issue to ultimately receive the data? I can clearly see response.data not being empty when I log it, but the State variables are when I log them inside of the useEffect Hook
This is what I got so far.
Any help, tips, additional sources to learn this would be highly appreciated.
This is my HOC which I just copied:
import React, { useState, useEffect } from "react";
import axios from "axios";
const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1);
export const withEditableResource = (Component, resourcePath, resourceName) => {
return (props) => {
const [originalData, setOriginalData] = useState(null);
const [editedData, setEditedData] = useState(null);
useEffect(() => {
(async () => {
const response = await axios.get(resourcePath);
setOriginalData(response.data);
setEditedData(response.data);
})();
}, []);
const onChange = (changes) => {
setEditedData({ ...editedData, ...changes });
};
const onSave = async () => {
const response = await axios.post(resourcePath, {
[resourceName]: editedData,
});
setOriginalData(response.data);
setEditedData(response.data);
};
const onReset = () => {
setEditedData(originalData);
};
const resourceProps = {
[resourceName]: editedData,
[`onChange${capitalize(resourceName)}`]: onChange,
[`onSave${capitalize(resourceName)}`]: onSave,
[`onReset${capitalize(resourceName)}`]: onReset,
};
return <Component {...props} {...resourceProps} />;
};
};
That's my form, I want to use - in the last lines you can find the hard-coded URL path, I want to swap for a parameter once this problem is done:
import { withEditableResource } from "./withEditableResource";
export const UserInfoFormImproved = withEditableResource(
({ user, onChangeUser, onSaveUser, onResetUser }) => {
const { name, email, username } = user || {};
return user ? (
<>
<label>
Name:
<input
value={name}
onChange={(e) => onChangeUser({ name: e.target.value })}
/>
</label>
<label>
Email:
<input
value={email}
onChange={(e) => onChangeUser({ email: e.target.value })}
/>
</label>
<label>
Username:
<input
value={username}
onChange={(e) => onChangeUser({ username: e.target.value })}
/>
</label>
<button onClick={onResetUser}>Reset</button>
<button onClick={onSaveUser}>Save Changes</button>
</>
) : (
<p>Loading...</p>
);
},
`https://jsonplaceholder.typicode.com/users/3`,
"User"
);
And that's the actual use of this two components within my App - I've added my idea on how to solve the parameter argument here:
import { UserInfoFormImproved } from "./HigherOrderComponents/UserInfoFormImproved";
function App() {
return (
<UserInfoFormImproved userId={1} />
);
}
export default App;

React Native open only a specific block [duplicate]

I have a question, if I can use useState generic in React Hooks, just like I can do this in React Components while managing multiple states?
state = {
input1: "",
input2: "",
input3: ""
// .. more states
};
handleChange = (event) => {
const { name, value } = event.target;
this.setState({
[name]: value,
});
};
Yes, with hooks you can manage complex state (without 3rd party library) in three ways, where the main reasoning is managing state ids and their corresponding elements.
Manage a single object with multiple states (notice that an array is an object).
Use useReducer if (1) is too complex.
Use multiple useState for every key-value pair (consider the readability and maintenance of it).
Check out this:
// Ids-values pairs.
const complexStateInitial = {
input1: "",
input2: "",
input3: ""
// .. more states
};
function reducer(state, action) {
return { ...state, [action.type]: action.value };
}
export default function App() {
const [fromUseState, setState] = useState(complexStateInitial);
// handle generic state from useState
const onChangeUseState = (e) => {
const { name, value } = e.target;
setState((prevState) => ({ ...prevState, [name]: value }));
};
const [fromReducer, dispatch] = useReducer(reducer, complexStateInitial);
// handle generic state from useReducer
const onChangeUseReducer = (e) => {
const { name, value } = e.target;
dispatch({ type: name, value });
};
return (
<>
<h3>useState</h3>
<div>
{Object.entries(fromUseState).map(([key, value]) => (
<input
key={key}
name={key}
value={value}
onChange={onChangeUseState}
/>
))}
<pre>{JSON.stringify(fromUseState, null, 2)}</pre>
</div>
<h3>useReducer</h3>
<div>
{Object.entries(fromReducer).map(([key, value]) => (
<input
name={key}
key={key}
value={value}
onChange={onChangeUseReducer}
/>
))}
<pre>{JSON.stringify(fromReducer, null, 2)}</pre>
</div>
</>
);
}
Notes
Unlike the setState method found in class components, useState does not automatically merge update objects. You can replicate this behavior by combining the function updater form with object spread syntax:
setState(prevState => {
// Object.assign would also work
return {...prevState, ...updatedValues};
});
Refer to React Docs.
The correct way to do what you're trying to do is to create your own hook that uses useState internally.
Here is an example:
// This is your generic reusable hook.
const useHandleChange = (initial) => {
const [value, setValue] = React.useState(initial);
const handleChange = React.useCallback(
(event) => setValue(event.target.value), // This is the meaty part.
[]
);
return [value, handleChange];
}
const App = () => {
// Here we use the hook 3 times to show it's reusable.
const [value1, handle1] = useHandleChange('one');
const [value2, handle2] = useHandleChange('two');
const [value3, handle3] = useHandleChange('three');
return <div>
<div>
<input onChange={handle1} value={value1} />
<input onChange={handle2} value={value2} />
<input onChange={handle3} value={value3} />
</div>
<h2>States:</h2>
<ul>
<li>{value1}</li>
<li>{value2}</li>
<li>{value3}</li>
</ul>
</div>
}
ReactDOM.render(<App />, document.querySelector("#app"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
<div id="app"></div>
Note the use of React.useCallback to stop your hook from returning a new handler function on every render. (We don't need to specify setValue as a dependency because React guarantees that it will never change)
I didn't actually test this, but it should work.
See https://reactjs.org/docs/hooks-reference.html#usestate for more info.
import React, {useState} from 'react';
const MyComponent = () => {
const [name, setName] = useState('Default value for name');
return (<div><button onClick={()=>setName('John Doe')}}>Set Name</button></div>);
};
export default MyComponent;

Unable to update parent prop in react

I can insert the input value say "1,2,3" and when backspace it removes all but in the console "1" is still shown i.e., House{props{house{property{rent:1}}}}
I am providing the code here which has 3 files.
(1) house.js
import ValInput from "main/components/val-input";
class House extends Component {
state = {
rent:"",
};
componentDidMount() {
if (this.props.house.rent) {
const { rent} = this.props.house;
this.setState({ rent });
}
}
onChange = (e) => {
const rent = parseInt(e.target.value.replace(string);
this.setState({
rent,
});
};
render(){
const {house} = this.props;
const {rent} = this.state;
...
<ValInput
type="text"
value={ rent }
onChange={e => {
this.onChange(e);
}}
/>
}
(2) val-input\index.js
import React from "react";
import Input from "main/components/input";
const ValInput = props => (
<Input
{...props}
type={props.type ? props.type : "text"}
/>
);
export default valInput;
(3) components/input/index.js
import React from "react";
const noOp = () => {};
const Input = ({
onBlur = xP,
...otherProps
}) => (
<input
onBlur={e => {
e.target.placeholder = placeholder;
onBlur(e);
}}
{...otherProps}
/>
);
export default Input;
The expected result should be, after emptying the value say with backspace, and visit the page next time, the input field should be empty and should not show old value.
Check this CodeSandbox out I replicated your code and if I understood the problem right then fixed it
https://reactjs.org/docs/cross-origin-errors.html
For updating #NaderZouaoui, has given me an example how to do Call back :
1. Child file :
onChange={e => {
this.onChange(e);
}}
onChange = e => {
this.setState({
rent
});
this.props.callback(rent);
};
2. Parent file :
state = {
rent: ""
};
handleChangeRent = newRent => {
this.setState({ rent: newRent });
};
render(){
return(
<House house={{ rent }} callback={this.handleChangeRent} />
);
}

Using functional setState in forms

I have been learning react and on setState i get about functional setState, i wanted to implement this on the forms to see how it works ( please don't give me counter example,i am seeing if it can be implemented in forms for usage only ).
I am trying to do this because i saw similar approach in a github but he had included all the things inside the single object, so he is doing it easily. But my question is can i do the same but without including my form properties like username and password inside object. I am talking about this repo => Form Container. What do you people recommend? How may i can do ? Thanks !
My form code is like this:
index.js
import React,{ Component } from 'react';
import ReactDOM from 'react-dom';
class Form extends Component{
constructor(props){
super(props);
this.state = {
name: "",
age: "",
};
}
handleInput = (e) => {
let name = e.target.name;
let value = e.target.value;
this.setState({ [name]: value });
}
//The block where i am trying to implement the function setState
handleInput = (e) => {
let name = e.target.name;
let value = e.target.value;
this.setState( state => ({
name:{state.name,[name]: value}
}));
}
render(){
return(
<div>
<label> Name:
<input type="text" name="name" value={this.state.name} onChange={this.handleInput} />
</label>
<br />
<label> Age:
<input type="text" name="age" value={this.state.age} onChange={this.handleInput} />
</label>
</div>
);
}
}
ReactDOM.render(<Form />, document.getElementById('root'))
handleInput = ({ target }) => {
const { name, value } = target;
this.setState({ [name]: [value] });
}
//Can even boil down to one line of code
handleInput = ({ target }) => this.setState({ [target.name]: [target.value] })
You may try the code above. It's pretty concise and not sure how to elaborate on it. We can discuss about my approach in comment
UPDATES:
let value = e.target.value;
this.setState( prevState => ({
newUser : { ...prevState.newUser, name: value }
}), () => console.log(this.state.newUser))
The above code was copied from the link you commented which in our case, should be implemented as below:
handleInput = ({ target }) => {
const { name, value } = target;
this.setState(prevState => ({ ...prevState, [name]: value }));
}

React controlled input cursor jumps

I am using React and have formatted a controlled input field, which works fine when I write some numbers and click outside the input field. However, when I want to edit the input, the cursor jumps to the front of the value in the input field. This only occur in IE, and not in e.g. Chrome. I've seen that for some programmers the cursor jumps to the back of the value. So I think the reason that my cursor is jumping to the front is because the value is aligned to the right instead of to the left in the input field. Here is a senario:
My first input is 1000
Then I want to edit it to 10003, but the result is
31000
Is there a way to controll that the cursor should not jump?
Here's a drop-in replacement for the <input/> tag. It's a simple functional component that uses hooks to preserve and restore the cursor position:
import React, { useEffect, useRef, useState } from 'react';
const ControlledInput = (props) => {
const { value, onChange, ...rest } = props;
const [cursor, setCursor] = useState(null);
const ref = useRef(null);
useEffect(() => {
const input = ref.current;
if (input) input.setSelectionRange(cursor, cursor);
}, [ref, cursor, value]);
const handleChange = (e) => {
setCursor(e.target.selectionStart);
onChange && onChange(e);
};
return <input ref={ref} value={value} onChange={handleChange} {...rest} />;
};
export default ControlledInput;
Taking a guess by your question, your code most likely looks similar to this:
<input
autoFocus="autofocus"
type="text"
value={this.state.value}
onChange={(e) => this.setState({value: e.target.value})}
/>
This may vary in behaviour if your event is handled with onBlur but essentially its the same issue. The behaviour here, which many have stated as a React "bug", is actually expected behaviour.
Your input control's value is not an initial value of the control when its loaded, but rather an underlying value bound to this.state. And when the state changes the control is re-rendered by React.
Essentially this means that the control is recreated by React and populated by the state's value. The problem is that it has no way of knowing what the cursor position was before it was recreated.
One way of solving this which I found to work is remembering the cursor position before it was re-rendered as follows:
<input
autoFocus="autofocus"
type="text"
value={ this.state.value }
onChange={(e) => {
this.cursor = e.target.selectionStart;
this.setState({value: e.target.value});
}
}
onFocus={(e) => {
e.target.selectionStart = this.cursor;
}
}
/>
This is my solution:
import React, { Component } from "react";
class App extends Component {
constructor(props) {
super(props);
this.state = {
name: ""
};
//get reference for input
this.nameRef = React.createRef();
//Setup cursor position for input
this.cursor;
}
componentDidUpdate() {
this._setCursorPositions();
}
_setCursorPositions = () => {
//reset the cursor position for input
this.nameRef.current.selectionStart = this.cursor;
this.nameRef.current.selectionEnd = this.cursor;
};
handleInputChange = (key, val) => {
this.setState({
[key]: val
});
};
render() {
return (
<div className="content">
<div className="form-group col-md-3">
<label htmlFor="name">Name</label>
<input
ref={this.nameRef}
type="text"
autoComplete="off"
className="form-control"
id="name"
value={this.state.name}
onChange={event => {
this.cursor = event.target.selectionStart;
this.handleInputChange("name", event.currentTarget.value);
}}
/>
</div>
</div>
);
}
}
export default App;
This is an easy solution. Worked for me.
<Input
ref={input=>input && (input.input.selectionStart=input.input.selectionEnd=this.cursor)}
value={this.state.inputtext}
onChange={(e)=>{
this.cursor = e.target.selectionStart;
this.setState({inputtext: e.target.value})
/>
Explanation:
What we are doing here is we save the cursor position in onChange(), now when the tag re-renders due to a change in the state value, the ref code is executed, and inside the ref code we restore out cursor position.
If you're using textarea, then here's the hook based on Daniel Loiterton's code using TypeScript:
interface IControlledTextArea {
value: string
onChange: ChangeEventHandler<HTMLTextAreaElement> | undefined
[x: string]: any
}
const ControlledTextArea = ({ value, onChange, ...rest }: IControlledTextArea) => {
const [cursor, setCursor] = useState(0)
const ref = useRef(null)
useEffect(() => {
const input: any = ref.current
if (input) {
input.setSelectionRange(cursor, cursor)
}
}, [ref, cursor, value])
const handleChange = (e: ChangeEvent<HTMLTextAreaElement>) => {
setCursor(e.target.selectionStart)
onChange && onChange(e)
}
return <textarea ref={ref} value={value} onChange={handleChange} {...rest} />
}
As (I think) others have mentioned, React will keep track of this if you make your changes synchronously. Unfortunately, that's not always feasible. The other solutions suggest tracking the cursor position independently, but this will not work for input types like 'email' which will not allow you to use cursor properties/methods like selectionStart, setSelectionRange or whatever.
Instead, I did something like this:
const Input = (props) => {
const { onChange: _onChange, value } = props;
const [localValue, setLocalValue] = useState(value);
const onChange = useCallback(
e => {
setLocalValue(e.target.value);
_onChange(e.target.value);
},
[_onChange]
);
useEffect(() => {
setLocalValue(value);
}, [value]);
// Use JSX here if you prefer
return react.createElement('input', {
...props,
value: localValue,
onChange
});
};
This allows you to delegate the cursor positioning back to React, but make your async changes.
My cursor jumped always to the end of the line. This solution seems to fix the problem (from github):
import * as React from "react";
import * as ReactDOM from "react-dom";
class App extends React.Component<{}, { text: string }> {
private textarea: React.RefObject<HTMLTextAreaElement>;
constructor(props) {
super(props);
this.state = { text: "" };
this.textarea = React.createRef();
}
handleChange(e: React.ChangeEvent<HTMLTextAreaElement>) {
const cursor = e.target.selectionStart;
this.setState({ text: e.target.value }, () => {
if (this.textarea.current != null)
this.textarea.current.selectionEnd = cursor;
});
}
render() {
return (
<textarea
ref={this.textarea}
value={this.state.text}
onChange={this.handleChange.bind(this)}
/>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
Here is my solution
const Input = () => {
const [val, setVal] = useState('');
const inputEl = useRef(null);
const handleInputChange = e => {
const { value, selectionEnd } = e.target;
const rightCharsCount = value.length - selectionEnd;
const formattedValue = parseInt(value.replace(/\D/g, ''), 10).toLocaleString();
const newPosition = formattedValue.length - rightCharsCount;
setVal(formattedValue);
setTimeout(() => {
inputEl.current.setSelectionRange(newPosition, newPosition);
}, 0);
};
return <input ref={inputEl} value={val} onChange={handleInputChange} />;
};
// Here is a custom hook to overcome this problem:
import { useRef, useCallback, useLayoutEffect } from 'react'
/**
* This hook overcomes this issue {#link https://github.com/reduxjs/react-redux/issues/525}
* This is not an ideal solution. We need to figure out why the places where this hook is being used
* the controlled InputText fields are losing their cursor position when being remounted to the DOM
* #param {Function} callback - the onChangeCallback for the inputRef
* #returns {Function} - the newCallback that fixes the cursor position from being reset
*/
const useControlledInputOnChangeCursorFix = callback => {
const inputCursor = useRef(0)
const inputRef = useRef(null)
const newCallback = useCallback(
e => {
inputCursor.current = e.target.selectionStart
if (e.target.type === 'text') {
inputRef.current = e.target
}
callback(e)
},
[callback],
)
useLayoutEffect(() => {
if (inputRef.current) {
inputRef.current.setSelectionRange(inputCursor.current, inputCursor.current)
}
})
return newCallback
}
export default useControlledInputOnChangeCursorFix
// Usage:
import React, { useReducer, useCallback } from 'react'
import useControlledInputOnChangeCursorFix from '../path/to/hookFolder/useControlledInputOnChangeCursorFix'
// Mimics this.setState for a class Component
const setStateReducer = (state, action) => ({ ...state, ...action })
const initialState = { street: '', address: '' }
const SomeComponent = props => {
const [state, setState] = useReducer(setStateReducer, initialState)
const handleOnChange = useControlledInputOnChangeCursorFix(
useCallback(({ target: { name, value } }) => {
setState({ [name]: value })
}, []),
)
const { street, address } = state
return (
<form>
<input name='street' value={street} onChange={handleOnChange} />
<input name='address' value={address} onChange={handleOnChange} />
</form>
)
}
For anybody having this issue in react-native-web here is solution written in TypeScript
const CursorFixTextInput = React.forwardRef((props: TextInputProps, refInput: ForwardedRef<TextInput>) => {
if(typeof refInput === "function") {
console.warn("CursorFixTextInput needs a MutableRefObject as reference to work!");
return <TextInput key={"invalid-ref"} {...props} />;
}
if(!("HTMLInputElement" in self)) {
return <TextInput key={"no-web"} {...props} />;
}
const { value, onChange, ...restProps } = props;
const defaultRefObject = useRef<TextInput>(null);
const refObject: RefObject<TextInput> = refInput || defaultRefObject;
const [ selection, setSelection ] = useState<SelectionState>(kInitialSelectionState);
useEffect(() => {
if(refObject.current instanceof HTMLInputElement) {
refObject.current.setSelectionRange(selection.start, selection.end);
}
}, [ refObject, selection, value ]);
return (
<TextInput
ref={refObject}
value={value}
onChange={event => {
const eventTarget = event.target as any;
if(eventTarget instanceof HTMLInputElement) {
setSelection({
start: eventTarget.selectionStart,
end: eventTarget.selectionEnd
});
}
if(onChange) {
onChange(event);
}
}}
{...restProps}
/>
)
});
The simplest and safest way of doing this is probably to save the cursor position before React renders the input and then setting it again after React finishes rendering.
import React, {ReactElement, useEffect, useRef} from "react";
/**
* Text input that preserves cursor position during rendering.
*
* This will not preserve a selection.
*/
function TextInputWithStableCursor(
props: React.InputHTMLAttributes<HTMLInputElement> & {type?: "text"}
): ReactElement {
const inputRef = useRef<HTMLInputElement>(null);
// Save the cursor position before rendering
const cursorPosition = inputRef.current?.selectionStart;
// Set it to the same value after rendering
useEffect(function () {
if (
typeof cursorPosition === "number" &&
document.activeElement === inputRef.current
) {
inputRef.current?.setSelectionRange(cursorPosition, cursorPosition);
}
});
return <input ref={inputRef} {...props} />;
}
If you faced an issue with the cursor jumping to the end after updating the input state and updating the cursor using refs -> I found a workaround for it by setting the cursor in Promise.resolve's microtask.
<input
value={value}
onChange={handleValueUpdate}
ref={inputRef}
/>
const handleValueUpdate = (e: React.ChangeEvent<HTMLInputElement>) => {
e.preventDefault();
// ...
// some value handling logic
setValue(newValue)
const cursorPosition = getCursorPositionLogic();
/**
* HACK: set the cursor on the next tick to make sure that the value is updated
* useTimeout with 0ms provides issues when two keys are pressed same time
*/
Promise.resolve().then(() => {
inputRef.current?.setSelectionRange(cursorPosition, cursorPosition);
});
}
I know the OP is 5 years old but some are still facing the same kind of issue and this page has an high visibility on Google search.
Try by replacing :
<input value={...}
with
<input defaultValue={...}
This will solve most of the cases i've seen around there.
I tried all of the above solutions and none of them worked for me. Instead I updated both the e.currentTarget.selectionStart & e.currentTarget.selectionEnd on the onKeyUp React synthetic event type. For example:
const [cursorState, updateCursorState] = useState({});
const [formState, updateFormState] = useState({ "email": "" });
const handleOnChange = (e) => {
// Update your state & cursor state in your onChange handler
updateCursorState(e.target.selectionStart);
updateFormState(e.target.value);
}
<input
name="email"
value={formState.email}
onChange={(e) => handleOnChange(e)}
onKeyUp={(e) => {
// You only need to update your select position in the onKeyUp handler:
e.currentTarget.selectionStart = cursorState.cursorPosition;
e.currentTarget.selectionEnd = cursorState.cursorPosition;
}}
/>
Also, be aware that selectionStart & selectionEnd getters are not available on input fields of type email.

Resources