Many UseState() in component - how should be done? [duplicate] - javascript

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;

Related

how to pass only those values which changed in POST request body

I have multiple state variables, that contains data entered in a form by the user. Since this form is only meant to update the existing values, I have to pass in only those values that have changed from its initial value (the one returned from the GET request).
State:
const [name, setName] = useState(props.user?.name ?? null);
const [lang, setLang] = useState(props.user?.lang ?? null);
const [enableChecks, setEnableChecks] = useState(props.user?.checkEnabled ?? false)
In the event that the user only changed the name, how can I pass in only name in the request body?
What I have tried: I have the user props, so I have multiple if statements that check if the props matches the state. If it doesn't, then I add it to the request payload. This works, but when there's a lot of state, there will be a lot of if statements, which isn't nice to look at.
Is there a better way to do this?
Instead of having multiple state variables, you can have a single state variable like
const [state, setState] = useState(props.user)
and then change handler should look like
const handleChange = (e) => {
setState({
...state,
[e.target.name]: e.target.value,
});
};
finally, when submitting the form you can make your body data for post request like
const handleSubmit = () => {
const requestData = {}
for (const [key, value] of Object.entries(state)){
if(props.user[key] !== value) {
requestData[key] = value
}
}
axios.post('some api url', responseData)
}
You can keep your state in an object, and then only update field state when the updatedUser and user state values are different.
//use `import` in your real component instead
//import { useState } from 'react';
const { useState } = React;
//fake your user prop
const userProp = {
name: "Name",
lang: "English",
}
function App(props) {
const [user, setUser] = useState(props.user);
const [updatedUser, setUpdatedUser] = useState({});
const handleChange = (e) => {
const newlyUpdatedUser = {
...updatedUser,
}
if(props.user[e.target.name] === e.target.value) {
delete newlyUpdatedUser[e.target.name]
} else {
newlyUpdatedUser[e.target.name] = e.target.value
}
setUpdatedUser(newlyUpdatedUser);
setUser({
...user,
[e.target.name]: e.target.value
})
};
console.log(updatedUser)
return (
<React.Fragment>
<label>
Name:
<input value={user.name} name="name" onChange={handleChange} />
</label>
<label>
Lang:
<input value={user.lang} name="lang" onChange={handleChange} />
</label>
</React.Fragment>
);
}
ReactDOM.render(<App user={userProp} />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>

How do I pass an image from array to react material ui modal with map funciton? [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;

Replace parent state with state of child. Button in parent

EDIT: See the comment of O.o for the explanation of the answer and the variant in case you are using classes.
I've come across to something and I can't find the solution.
I have 4 components in my web app:
Parent
child_1
child_2
child_3
I have a button on the Parent, and different forms (with inputs, checkboxes and radiobuttons) at the children.
Each child has his own button that executes several functions, some calculations, and updates the corresponding states. (No states are passed through parent and child).
I need to replace the three buttons of the children with the parent button.
Is there a way that I can execute the functions at the three children from the parent button and retrieve the results? (the results are one state:value per child.)
function Child1(props) {
const [value, setValue] = useState("");
useEffect(() => {
calculate();
}, [props.flag]);
calculate() {
//blah blah
}
onChange(e) {
setValue(e.target.value);
props.onChange(e.target.value); // update the state in the parent component
}
return (
<input value={value} onChange={(e) => onChange(e)} />
);
}
function Parent(props) {
const [flag, setFlag] = useState(false);
const [child1Value, setChild1Value] = useState("");
return (
<div>
<Child1 flag={flag} onChange={(value) => setChild1Value(value)}/>
<button onClick={() => setFlag(!flag)} />
</div>
);
}
I didn't test this but hope this helps you. And lemme know if there is an issue.
Try the following:
create refs using useRef for child form components.
for functional components, in order for the parent to access the child's methods, you need to use forwardRef
using the ref, call child component functions on click of parent submit button (using ref.current.methodName)
See the example code. I have tested it on my local, it is working ok.
Parent
import React, { Fragment, useState, useRef } from "react";
import ChildForm1 from "./ChildForm1";
const Parent = props => {
const [form1Data, setFormData] = useState({});//use your own data structure..
const child1Ref = useRef();
// const child2Ref = useRef(); // for 2nd Child Form...
const submitHandler = e => {
e.preventDefault();
// execute childForm1's function
child1Ref.current.someCalculations();
// execute childForm2's function
// finally do whatever you want with formData
console.log("form submitted");
};
const notifyCalcResult = (calcResult) => {
// update state based on calcResult
console.log('calcResult', calcResult);
};
const handleChildFormChange = data => {
setFormData(prev => ({ ...prev, ...data }));
};
return (
<Fragment>
<h1 className="large text-primary">Parent Child demo</h1>
<div>
<ChildForm1
notifyCalcResult={notifyCalcResult}
ref={child1Ref}
handleChange={handleChildFormChange} />
{/*{do the same for ChildForm2 and so on...}*/}
<button onClick={submitHandler}>Final Submit</button>
</div>
</Fragment>
);
};
export default Parent;
ChildFormComponent
import React, { useState, useEffect, forwardRef, useImperativeHandle } from "react";
const ChildForm1 = ({ handleChange, notifyCalcResult }, ref) => {
const [name, setName] = useState("");
const [calcResult, setCalcResult] = useState([]);
const someCalculations = () => {
let result = ["lot_of_data"];
// major calculations goes here..
// result = doMajorCalc();
setCalcResult(result);
};
useImperativeHandle(ref, () => ({ someCalculations }));
useEffect(() => {
// notifiy parent
notifyCalcResult(calcResult);
}, [calcResult]);
return (
<form className="form">
<div className="form-group">
<input
value={name}// //TODO: handle this...
onChange={() => handleChange(name)}//TODO: notify the value back to parent
type="text"
placeholder="Enter Name"
/>
</div>
</form>
);
};
export default forwardRef(ChildForm1);
Also as a best practice, consider to maintain state and functions in the parent component as much as possible and pass the required values/methods to the child as props.

Using localStorage to return the last search

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.

React Hooks - How to use a make a state dependent on another state?

I recently started using hooks in react and I am often having this problem: I create a first big state which is used by all my component, but some of the smaller parts of my component divide this state and create their own state for simplicity.
For example
import React, { useState } from "react";
const initialFilters = {
name: "",
code: ""
};
function Filter({ value, setFilters }) {
const [tempValue, setTempValue] = useState(value);
return (
<input
value={tempValue}
onChange={e => setTempValue(e.target.value)}
onBlur={() => setFilters(tempValue)}
/>
);
}
function App() {
const [filters, setFilters] = useState(initialFilters);
const agents = [
{ name: "bob", code: "123" },
{ name: "burger", code: "3123" },
{ name: "sponge", code: "34" }
];
return (
<div>
<label>Name filter</label>
<Filter
value={filters.name}
setFilters={value =>
setFilters(filters => ({ ...filters, name: value }))
}
/>
<label>Code filter</label>
<Filter
value={filters.code}
setFilters={value =>
setFilters(filters => ({ ...filters, code: value }))
}
/>
<button onClick={() => setFilters(initialFilters)}>Reset filters</button>
<ul>
{agents
.filter(
agent =>
agent.name.includes(filters.name) &&
agent.code.includes(filters.code)
)
.map((agent, i) => (
<li key={i}>
name: {agent.name} - code: {agent.code}
</li>
))}
</ul>
</div>
);
}
export default App;
CodeSandox available here
In this example the filters work fine, but their value are not emptied when we use the button Reset.
The filters create their own states to dispatch new state only on blur, and still be controlled. I guess I could use ref here, but I use this example to showcase a simple case of state dependent on another state (and therefore on props).
How should I go about implementing this in an idiomatic React way?
You can use a useEffect hook. The 1st argument is a function and the 2nd argument is an array of dependencies. When a dependency changes value, the function is executed again.
import { useEffect } from 'react';
// ....code removed....
useEffect(() => {
setTempValue(value);
}, [value]);
// ....code removed....
Sandbox with changes: https://codesandbox.io/s/kind-bogdan-ljugv
As you can read in the documentation (https://reactjs.org/docs/hooks-state.html#declaring-a-state-variable), your state is created on the first render only and only then it is equal to your initial value.
You could write a custom hook useFilter and expose your filter-reseter:
const useFilter = (value, setFilters) => {
const [tempValue, setTempValue] = useState(value);
const resetFilter = () => setTempValue(value)
return {
resetFilter,
getInputProps: () => ({
onChange: e => setTempValue(e.target.value),
onBlur: () => setFilters(tempValue),
value: tempValue,
})
}
and instead of doing:
<Filter
value={filters.name}
setFilters={value =>
setFilters(filters => ({ ...filters, name: value }))
}
/>
do this:
const setFilters = value => setFilters(filters => ({ ...filters, name: value }))
const { resetTempFilter, getInputProps } = useFilter(value, setFilters)
...
<input {...getInputProps()} />
In this case you much easier to re-instantiate children with changing key prop(so named "reset state" technique based on how reconciliation works in React):
const [resetKey, setResetKey] = useState(0);
const doReset = setResetKey(key => key + 1);
<Filter
key={`name-filter-${resetKey}`}
...
/>
<Filter
key={`code-filter-${resetKey}`}
...
/>
<button onClick={doReset}>Reset!</button>
It's not only easier to achieve. It also will work the same for any stateful components you cannot modify(for any reason).

Categories