Language used: javascript with react
I have made a custom HOOK to keep the previous state
here my custom hooks
import { useEffect, useRef } from 'react';
export const usePrevious = (value) => {
const ref = useRef();
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
};
here where I am using it :
export const ArticleQuantity = () => {
const [quantity, setQuantity] = useState(1);
const prevQuantity = usePrevious(quantity);
useEffect(() => {
console.log(prevQuantity + quantity);
}, [quantity]);
<div>
<input
onChange={(e) => setQuantity(e.target.value)}
defaultValue={quantity}/>
<div>
}
Problem : if my user enter "3" in the input and then remove the "3" to enter "5", my previous state will be "null" because the last value is removed.
How can i keep "3" instead of null ?
Thank you.
Add a condition to check null
useEffect(() => {
if(value){
ref.current = value;
}
}, [value]);
Related
Whenever I place the cursor in the middle of numbers, let's say 3|3000 then press either backspace or delete, cursor jumps to the back. I believe this is happening because I have value set as displayValue and since displayValue is a state, whenever displayValue changes value, input re-renders and therefore cursor moves back. I know simple solution is to set value={value}, but for this particular input I need to use state displayValue instead so I need to use alternative solution. Is there a way to cursor to remain in its natural position even through re-rendering? https://codesandbox.io/s/number-field-experiments-iii-ts-nh95up?file=/src/App.tsx:0-1633
import React, { useState, useRef, useEffect } from "react";
import "./styles.css";
export interface NumberFieldInterface {
onChange?: (value: number) => void;
value: number;
}
const NumberField: React.FC<NumberFieldInterface> = ({ value, onChange }) => {
const inputRef = useRef(null);
const [displayValue, setDisplayValue] = useState("");
const onChangeValue = (value: any) => {
if (typeof onChange === "function") {
onChange(value);
}
};
useEffect(() => {
const newFormatValue = value.toString();
setDisplayValue(newFormatValue);
}, [value]);
return (
<div>
<input
type="text"
ref={inputRef}
value={displayValue}
onChange={(e: React.FormEvent<HTMLInputElement>) =>
onChangeValue(e.target.value)
}
/>
</div>
);
};
export default function App() {
const [inputValue, setInputValue] = useState<number>(0);
const handleChange = (val: number) => {
setInputValue(val);
};
return (
<div className="App">
<NumberField value={inputValue} onChange={handleChange} />
</div>
);
}
Instead of using useEffect to change your displayValue, how about doing it inside the onChange?
const NumberField: React.FC<NumberFieldInterface> = ({ value, onChange }) => {
const inputRef = useRef(null);
const [displayValue, setDisplayValue] = useState("");
const onChangeValue = (value: any) => {
if (typeof onChange === "function") {
onChange(value);
}
const newFormatValue = value.toString();
setDisplayValue(newFormatValue);
};
return (
<div>
<input
type="text"
ref={inputRef}
value={displayValue}
onChange={(e: React.FormEvent<HTMLInputElement>) =>
onChangeValue(e.target.value)
}
/>
</div>
);
};
It seems to fix the issue of jumping around but I’m not entirely sure it fixes everything. And I’m unsure why value is passed to the component but never used.
Container
import { InputField } from './InputField';
const sleep = (time: number) => new Promise((res) => setTimeout(res, time, ''));
export const Container = () => {
const [inputValue, setInputValue] = React.useState('');
React.useEffect(() => {
(async () => await sleep(1000))();
async function fetchMyAPI(time, value) {
await sleep(time);
setInputValue(value);
}
fetchMyAPI(1000, 'vbc1');
fetchMyAPI(2000, 'dgi1');
}, []);
const inputChange = (value) => {
setInputValue(value);
};
return <InputField inputValue={inputValue} inputChange={inputChange} />;
};
InputField
export const InputField = ({
inputValue,
inputChange,
}: {
inputValue: string;
inputChange: (value: string) => void;
}) => {
const [value, setValue] = React.useState('');
React.useEffect(() => {
setValue(inputValue.slice(0, -1));
}, [inputValue]);
const handleChange = (event) => {
setValue(event.target.value);
inputChange(event.target.value + '1');
};
return <input value={value} onChange={handleChange} />;
};
inputValue above can change multiple times.
also a local variable in input is used to display , and inputValue is slightly different from it. So when we keep track of InputValue , we pass the cleared data to the local variable. And vice versa, we modify the data to put in the inputValue.
React.useEffect(() => {
setValue(inputValue.slice(0, -1));
}, [inputValue]);
Every time we call handleChange : we do setValue and inputChange. Thus, we change the value variable and the inputValue variable. After the inputValue is changed, useEffect is called which observes the inputValue. And overwrites exactly the same value of the Value variable. This is problem!
What is the correct solution to this problem?
You can create a boolean state effectRan to track whether the effect already ran or not, and only invoke the effect's logic if effectRan == false, then set it to true.
When the effect runs again with it as true, have it set it back to false to prepare to run again in the next change.
I changed the code a bit to highlight the approach:
const {useState, useEffect } = React
const InputField = () => {
const [value, setValue] = React.useState('');
const [effectRan, setEffectRan] = useState(true);
React.useEffect(() => {
if (!effectRan) {
setValue(prev => prev + '-');
setEffectRan(true)
console.log('Effect just ran');
} else {
setEffectRan(false)
}
}, [value]);
const handleChange = (event) => {
setValue(event.target.value);
};
return <input onChange={handleChange} value={value} />;
};
ReactDOM.render(<InputField />, root)
<script crossorigin src="https://unpkg.com/react#18/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.production.min.js"></script>
<div id="root"></div>
onClick2 causes the state to receive the changed value.
However, onClick3 wants to deliver the changed state value only when onClick2 is pressed.
How can I control the changed value of the input?
When onClick3 is pressed when the input value is changed to 456
Is it possible to get the previous value of onChange ?
my code...
import React, { useRef, useState } from "react";
const App = () => {
const [value, setValue] = useState("123");
const onChangeInput = (e) => {
setValue(e.target.value);
};
const onClick2 = () => {
console.log(value);
};
const onClick3 = () => {
// how to onclick2 preve
console.log(value);
};
return (
<div>
<input type="text" onChange={onChangeInput} value={value} />
<button onClick={onClick2}>Button1</button>
<button onClick={onClick3}>Button2</button>
</div>
);
};
export default App;
You should use 2 useState one for prevValue and one for currentValue like this-
const [value, setValue] = useState("123")
const [prevValue, setPrevValue] = useState(value)
const onChangeInput = (e) => {
setValue(e.target.value);
}
const onClick2 = () => {
setPrevValue(value)
}
const onClick3 = () => {
console.log('Previous : ', prevValue)
console.log('Current : ', value)
}
When you click on onClick2 btn it will update previous Value and when you click on onClick3 btn it will give you output as previous and updated Values.
# If your current value is 456
output:
Previous : 123
Current : 456
Just add this code and you will get your answer.
I made this Component it sends props to checkbox and range components. When I was testing functionality of this 2 components I saw when I made a change in range component checkbox also rerender but it wasn't changed and the same when I change checkbox renage rerender.
Problem
When I change range comp the second one also rerender
const GeneratePassword = () => {
// Checkbox
const [state, setState] = useState({
Symbols: false,
capiatalLetters: false,
digits: false,
})
const handleChange = (e) => {
setState({ ...state, [e.target.name]: e.target.checked })
}
// Range
const [value, setValue] = useState(8)
const handleInputChange = (event) => {
setValue(event.target.value === '' ? '' : Number(event.target.value))
}
const handleBlur = () => {
if (value < 8) {
setValue(8)
} else if (value > 30) {
setValue(30)
}
}
return (
<Comp.StyledCheckboxContainer>
<Comp.CheckboxBorder>
<CheckboxContainer isCheck={handleChange} option={state} />
<Range
value={value}
handleInputChange={handleInputChange}
handleBlur={handleBlur}
setValue={setValue}
/>
</Comp.CheckboxBorder>
</Comp.StyledCheckboxContainer>
)
}
export default GeneratePassword
Components are updated on each state change because their props change. Functions that are used as callbacks should be memoized to prevent this. That some of them rely on current state is a problem, they need to use updater function to avoid referring to stale state:
const handleChange = useCallback((e) => {
setState((state) => ({ ...state, [e.target.name]: e.target.checked }))
}, [])
...
const handleInputChange = useCallback((event) => {
setValue(event.target.value === '' ? '' : Number(event.target.value))
}, [])
...
const handleBlur = useCallback(() => {
setValue(value => {
if (value < 8) {
return 8
} else if (value > 30) {
return 30
}
})
}, [])
At this point child components are able to prevent unnecessary rerenders. If they don’t do this, they need to be additionally wrapped with React.PureComponent or React.memo. For arbitrary component a wrapper can be:
OptimizedComp = React.memo(props => <Comp {...props} />);
It is because your callback for Checkbox component is defined inside parent which re-renders whenever range component changes causing a change is callback for Checkbox component since it gets new value on every render.
const handleInputChange = (event) => {
setValue(event.target.value === '' ? '' : Number(event.target.value))
}
On every render, handleInputChange gets a new value, a new reference. You need to use React.useCallbak() to keep the same value for all renders
const handleInputChange = React.useCallback((event) => {
setValue(event.target.value === '' ? '' : Number(event.target.value))
}, []);
I'm using React right now and I'm trying to get my localstorage to update a state once the event handles a return on search and then hold that state until the next search is completed. Right now I can't figure out where to put an event handler that triggers the correct state and holds the correct value.
const useStateWithLocalStorage = localStorageKey => {
const [value, setValue] = React.useState(
localStorage.getItem(localStorageKey) || ''
);
React.useEffect(() => {
localStorage.setItem(localStorageKey, value);
}, [value]);
return [value, setValue];
};
export default function App() {
const [value, setValue] = useStateWithLocalStorage(
'myValueInLocalStorage'
);
const onChange = event => setValue(event.target.value);
const [state, setState] = useState({
message: 'test deploy',
results: [],
value: '',
});
...
and where I'm trying to implement the event handler
export default function SearchAppBar(props) {
const classes = useStyles();
const [searchTerm, setSearchTerm] = useState('');
const { onClick } = props;
...
<InputBase
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="Search…"
classes={{
root: classes.inputRoot,
input: classes.inputInput,
}}
inputProps={{ 'aria-label': 'search' }}
/>
<Button onClick={() => onClick(searchTerm)}> Search </Button>```
Hereby my solution. I've created an useLocalStorage function that stores and gets or sets items in the local storage and holds them in its own state:
import React from "react";
export const useLocalStorage = (key, initialValue) => {
const [storedValue, setStoredValue] = React.useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.log(error);
return initialValue;
}
});
const setValue = value => {
try {
setStoredValue(value);
window.localStorage.setItem(key, JSON.stringify(value));
} catch (error) {
console.log(error);
}
};
return [storedValue, setValue];
};
export default useLocalStorage;
For the searchBar component I've used a forwardRef to access the value of the input inside our higher component App. The newSearch function and searchTerm variable are destructured off the props. The placeholder holds the stored value in localStorage, which is searchTerm:
export const SearchAppBar = React.forwardRef(
({ newSearch, searchTerm }, ref) => {
return (
<>
<input ref={ref} type="text" placeholder={searchTerm} />
<button onClick={newSearch}> Search </button>
</>
);
}
);
Inside the main App component I'm using our useLocalStorage function hook to get and set the search. Inside newSearch I'm updating the search term by calling our hook with the value of the forwarded input ref.
export default function App() {
const ref = React.createRef();
const [searchTerm, setSearchTerm] = useLocalStorage(
"search",
"Not searched yet"
);
const newSearch = () => {
setSearchTerm(ref.current.value);
};
return (
<>
<SearchAppBar ref={ref} newSearch={newSearch} searchTerm={searchTerm} />
<p>Last search: {searchTerm}</p>
</>
);
}
Hope this is a workable solution for you.
Please find a code snippet here:
https://codesandbox.io/s/cranky-sunset-8fqtm?file=/src/index.js:387-773
I like the approach used by redux to handling the states on react. I use redux with redux-persist library to save the state instead of localStorage. If your project grows and you need to work with more complex states, it could help you.