I am unable to update my react hooks state.
So this is what I am doing (this is a minified relevant code).
export const Signup = (props) => {
const key= 'randomKey'
const onTextChangeHandler = (text) => {
console.log(key)
setPayloadData[key] = text
console.log(payload)
}
const [payload, setPayloadData] = useState({})
return (
<View>
<TextInput
placeholder={placeHolder}
number={number}
style={[{color: defaultColor, borderColor: defaultColor}, styles.defaultTextInputStyle, templateStyle]}
onChangeText={text => onTextChangeHandler(text)}
value={payload[key]}
/>
</View>
)
}
here, In the above code, notice
const onTextChangeHandler = (text) => {
console.log(key)
setPayloadData[key] = text
console.log(payload)
}
Here text is coming out to be whatever I typed. console.log of the key is returning the randomKey but
console.log(payload)
Is coming out to be undefined. Can anyone help me in figuring out what I am doing wrong?
setPayload is a function, not an object. What you are actually doing is assigning a new field to the function, the payload remains unchanged, since the function responsible for updating it is not being called.
setPayloadData[key] = text; // the function object mutation occures
Solution: simply invoke it as a function and pass the argument you want:
setPayloadData({ [key]: text });
Example: Update state using useState hook
Update props value and HOC components accordingly
import React, { useState } from 'react';
const Signup = (props) => {
const key= 'userKeyboardStrokes'
const onTextChangeHandler = (event) => {
setPayloadData({ [key]: event.target.value })
}
const [payload, setPayloadData] = useState({})
return (
<React.Fragment>
<input
type="text"
onChange={text => onTextChangeHandler(text)}
/>
</React.Fragment>
)
}
module.exports = Signup;
Output Result:
{
userKeyboardStrokes: "user input on object"
}
Playground Example:
https://stackblitz.com/edit/react-npytvn?file=testing.js
setPayloadData is a setter, it should be setPayloadData(newData) to update the state.
Related
I am transfer an props from father component to child component.
On the child component I want to check if the father component is deliver the props,
If he does, i"m putting it on the state, If not I ignore it.
if(Object.keys(instituteObject).length > 0)
{
setInnerInstitute(instituteObject)
}
For some reason the setInnerInstitute() take me to infinite loop.
I don't know why is that happening and how to fix it.
getInstitutesById() - Is the api call to fetch the objects.
Father component(EditInstitute):
const EditInstitute = props => {
const {id} = props.match.params;
const [institute, setInstitute] = useState({})
useEffect(() => { //act like componentDidMount
getInstitutesById({id}).then((response) => {
setInstitute(response)
})
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
return (
<React.Fragment>
<InstituteForm instituteObject={institute.object}/>
</React.Fragment>
)
}
Child component(InstituteForm):
const InstituteForm = (props) => {
const {instituteObject = {}} = props // if not exist default value = {}
const [innerInstitute, setInnerInstitute] = useState({})
if (Object.keys(instituteObject).length > 0) // if exists update the state.
{
setInnerInstitute(instituteObject)
}
return (
<React.Fragment>
not yet.
</React.Fragment>
)
}
Thanks
I think the way you are changing your InstituteForm's state causing this error. You can try using the useEffect hook to change your innerInstitute based on instituteObject. That's why you need to also add instituteObject in the dependency array of that useEffect hook.
import { useEffect, useState } from "react"
const InstituteForm = (props) => {
const {instituteObject = {}} = props // if not exist default value = {}
const [innerInstitute, setInnerInstitute] = useState({})
useEffect(() => {
// this is be evoked only when instituteObject changes
if (Object.keys(instituteObject).length > 0){
setInnerInstitute(instituteObject)
}
}, [instituteObject])
return (
<React.Fragment>
not yet.
</React.Fragment>
)
}
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;
I'm using useState hook but after changing the state, the component is not rending itself. I don't know what thing I'm missing.
import React, {useState} from 'react'
import List from '#material-ui/core/List'
import ListTile from "./components/ListTile/ListTile"
import style from './App.module.css'
import InputField from "./components/inputField/InputField";
const App = () => {
const [list, setList] = useState([])
const onFormSubmitHandler = (data) => {
list.push(data)
setList(list)
}
return (
<div className={style.outerDiv}>
<h1 className={style.center}>CLister</h1>
<InputField onSubmit={onFormSubmitHandler}/>
<List component="nav">
{list.map((data, index) =>
<ListTile index={index} body={data}/>
)}
</List>
</div>
);
}
export default App
As your list an array a reference type in js. If you modify the list using push
like list.push() it will also modify the original list in your state ,as a result there will be no change in your state.
Example
let list = [1, 2, 3, 4];
let list2 = list;
// if I modify list2 now
list2.push(5);
console.log(list); // list also gets modified as ,they are reference type
So what you can do
const onFormSubmitHandler = (data) => {
let list2=[...list]; // creating a new variable from existing one
list2.push(data)
setList(list2);
}
or
const onFormSubmitHandler = (data) => {
setList(prev=>([...prev,data]));
}
Remember that your state cant be modificate with push, because the way to modificate it is with the method set
Use this code in the method onFormSubmitHandler
const onFormSubmitHandler = (data) => {
setList(list => ([...list, data]))
}
Lastly remember if your form will be submit you need to break it with e.prevent.default()
const onFormSubmitHandler = (data) => {
list.push(data);
setList([...list]);
}
You should try something like this
import List from '#material-ui/core/List'
import ListTile from "./components/ListTile/ListTile"
import style from './App.module.css'
import InputField from "./components/inputField/InputField";
const App = () => {
const [list, setList] = useState([])
const onFormSubmitHandler = (data) => {
list.push(data)
setList(list)
}
return (
<div className={style.outerDiv}>
<h1 className={style.center}>CLister</h1>
<InputField onSubmit={(e) => onFormSubmitHandler(e.target.value)}/>
<List component="nav">
{list.map((data, index) =>
<ListTile index={index} body={data}/>
)}
</List>
</div>
);
}
export default App
You are editing it the wrong way, you should directly give the new values to the setList function and not try to update the list variable. Thats why you have the function, so that you do not update the original value. What you have to do here is use the previous state within the function and the spread operator since its an array and u just want to add an item:
const onFormSubmitHandler = (data) => {
setList(prevList => [...prevList, data])
}
You should look at the list variable as a read-only variable and not attempt to modify it, you modify it through the setList function.
If you want to do some other modifications instead of just adding item:
const onFormSubmitHandler = (data) => {
let listCopy = [...list];
// do something with listCopy
setList(listCopy);
}
In addition, it seems like you are not sending data at all to the function, the way to send data with your function call is to do it with anonymous function in the component:
<Component onSubmit={(e) => { onFormSubmitHandler(e.target.value) }} />
import React, { useState } from "react";
import Child from "./Child";
import "./styles.css";
export default function App() {
let [state, setState] = useState({
value: ""
});
let handleChange = input => {
setState(prevValue => {
return { value: input };
});
console.log(state.value);
};
return (
<div className="App">
<h1>{state.value}</h1>
<Child handleChange={handleChange} value={state.value} />
</div>
);
}
import React from "react";
function Child(props) {
return (
<input
type="text"
placeholder="type..."
onChange={e => {
let newValue = e.target.value;
props.handleChange(newValue);
}}
value={props.value}
/>
);
}
export default Child;
Here I am passing the data from the input field to the parent component. However, while displaying it on the page with the h1 tag, I am able to see the latest state. But while using console.log() the output is the previous state. How do I solve this in the functional React component?
React state updates are asynchronous, i.e. queued up for the next render, so the log is displaying the state value from the current render cycle. You can use an effect to log the value when it updates. This way you log the same state.value as is being rendered, in the same render cycle.
export default function App() {
const [state, setState] = useState({
value: ""
});
useEffect(() => {
console.log(state.value);
}, [state.value]);
let handleChange = input => {
setState(prevValue => {
return { value: input };
});
};
return (
<div className="App">
<h1>{state.value}</h1>
<Child handleChange={handleChange} value={state.value} />
</div>
);
}
Two solution for you:
- use input value in the handleChange function
let handleChange = input => {
setState(prevValue => {
return { value: input };
});
console.log(state.value);
};
use a useEffect on the state
useEffect(()=>{
console.log(state.value)
},[state])
Maybe it is helpful for others I found this way...
I want all updated projects in my state as soon as I added them
so that I use use effect hook like this.
useEffect(() => {
[temp_variable] = projects //projects get from useSelector
let newFormValues = {...data}; //data from useState
newFormValues.Projects = pro; //update my data object
setData(newFormValues); //set data using useState
},[projects])
I've recently started learning react and i'm using the context api to store my global state.
Here in MyProvider.js file i define my provider and its simply stores 2 arrays of json obj
import {MyContext} from "./MyContext";
import React, {useState} from "react";
export const MyProvider = (props) => {
const intialState = {
featuredListings: [],
setFeaturedListings: (val) => setState({...state, featuredListings: val}),
featuredVendors: [],
setFeaturedVendors: (val) => setState({...state, featuredVendors: val})
}
const [state, setState] = useState(intialState)
return (
<MyContext.Provider
value={state}
>
{props.children}
</MyContext.Provider>
);
}
I'm wrapping all my components in my App.js in the Provider by doing this , side not using ReachRouter to handle routing,
<MyProvider>
<div className="content">
<Header/>
<Router>
<Home path="/"/>
</Router>
</div>
<Footer />
</MyProvider>
In my Home.js file I make a network call in the useEffect hook which successfully returns the json which i expect and with that json response i update the state of the context so that it can be visible globally.
My code for that is
export const Home = () => {
let state = useContext(MyContext)
async function apiCalls() {
const featuredVendors = await getFeaturedVendors()
console.log("featuredVendors - ", featuredVendors) // the correct response is returned
state.setFeaturedVendors(featuredVendors)
const featuredListings = await getFeaturedListing()
console.log("featuredListings - ", featuredListings) // the correct response is returned
state.setFeaturedListings(featuredListings)
}
useEffect(() => {
apiCalls()
}, []);
return (
<div>
{console.log(state.featuredVendors)} // empty array
{console.log(state.featuredListings)} // contains correct values
</div>
)
}
]
To remove any ambiguity my Context is created in a separate file which is Called MyContext.js
and I create the Context like so
export const MyContext = React.createContext()
Why is the state.featuredVendors not updating when I set it?
Also another strange thing i noticed is if I rearrange the orders of the calls , i.e call the
getFeaturedListing first followed by the getFeaturedVendors then my state only updates for featuredVendors and featuredListings will be an empty array.
When you call useState the initialValue is only set once. When your MyProvider is first mounted, the state is initialised with your setFeaturedListings and setFeaturedVendors methods but these are not updated whenever the value of state changes. Therefore the value of state when you spread the values will always be its initial value.
setState can also be called with a function that always receives the current value as an argument, so you could rewrite these methods to spread that value like so:
const intialState = {
featuredListings: [],
setFeaturedListings: (val) => setState(state => ({...state, featuredListings: val})),
featuredVendors: [],
setFeaturedVendors: (val) => setState(state => ({...state, featuredVendors: val}))
}
Or, alternatively, you could move these functions outside of your state altogether.
export const MyProvider = (props) => {
const intialState = {
featuredListings: [],
featuredVendors: [],
}
const [state, setState] = useState(intialState)
return (
<MyContext.Provider
value={{
...state,
setFeaturedListings: (val) => setState({...state, featuredListings: val}),
setFeaturedVendors: (val) => setState({...state, featuredVendors: val})
}}
>
{props.children}
</MyContext.Provider>
);
}