I'm setting the value of an input to a property on an object (from the state). I have an onChange event to update the state. However, when I try to type, it doesn't actually update in the input. If I don't use an object and just have a string, it works fine. How can I fix this?
Demo of the issue
My code:
import React from "react";
export function App() {
const [data, setData] = React.useState({
test: "hello"
});
const change = (e) => {
data.test = e.target.value;
setData(data);
};
return <input value={data.test} onChange={change}></input>;
}
Per the comment by Aman Sadhwani, I used spread syntax to update the state without modifying the original object.
const change = (e) => {
setData({
...data,
test: e.target.value
});
};
Thanks, #Aman Sadhwani. Use Spread operator to update the state.
import React from "react";
export function App() {
const [data, setData] = React.useState({
test: "hello"
});
const change = (e) => {
setData({
...data
test:e.target.value
});
};
return <input value={data.test} onChange={change}></input>;
}
Related
I want to obtain a value from a variable hosted in redux and memorize it, to later compare it with itself and verify if it is different.
I need, for example, to do the following : const value = useSelector(state => state.data.value); (suppose that here the value is 0) now, when value changes, i need to compare it with the value it had previously
If you want to check what the value was on the previous render, you can save it in a ref:
const SomeComponent = () => {
const value = useSelector(state => state.data.value)
const prevRef = useRef();
useEffect(() => {
// Once the render is complete, update the ref's value
prevRef.current = value;
});
// Do something comparing prevRef.current and value
}
If you're doing this a lot you might find it useful to make a custom hook:
const usePrevious = (value) => {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.curren;t
}
// used like:
const SomeComponent = () => {
const value = useSelector(state => state.data.value)
const prev = usePrevious(value);
// Do something comparing prev and value.
}
You have to use selectors(i use 'reselect' library for that), such as:
file: selectors.js
import { createSelector } from 'reselect';
const stateEnvironments = state => state.environments;
export const selectEnvironments = createSelector([stateEnvironments],
environments => environments.data);
so then in your component you can use mapStateToProps with reselect and connect
import { createStructuredSelector } from 'reselect';
import { connect } from 'react-redux';
// your component here
const EnvironmentCurrencies = props => {
const {
data,
} = props;
return (
<div>
...
</div.
);
};
const mapStateToProps = createStructuredSelector({
data: selectEnvironments,
});
// here you can update your values with actions
// mapDispatchToProps is especially useful for constraining our actions to the connected component.
// You can access these via `this.props`.
const mapDispatchToProps = dispatch => ({
setEnvironment: environment => dispatch(actions.setEnvironment(environment)),
});
export default connect(mapStateToProps, mapDispatchToProps)(Environments);
this is not a full working example and the version that you might use, can have a bit different synaxis, but I hope this gives you a kick start. If not, let me know, I'll add more extensive example
So, in my code looks like setData works fine and gives the fetched data to my state. However it doesn't work at fist render. But if I cahange something in the code website will be rendered again and country name is gonna show up on the screen just how I wanted.
But this error keeps popping up:
Uncaught TypeError: Cannot read properties of undefined (reading 'common')
import React, { useState, useEffect } from 'react';
import { useParams } from 'react-router-dom';
function Country() {
const { countryName } = useParams();
const [data, setData] = useState([]);
const [country, setCountry] = useState({});
useEffect(() => {
fetch('https://restcountries.com/v3.1/all')
.then(resp => resp.json())
.then(datas => setData(datas));
}, [])
useEffect(() => {
setCountry(data.find(a => a.name.common.toLowerCase() == countryName.replaceAll("-", " ")));
}, [data])
return (
<>
{
<h1>{country.name.common}</h1>
}
</>
)
}
export default Country
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
It means that country.name is undefined (and not an object with common property).
On initial render country is {} (an empty object).
So, country.name returns undefined.
And undefined does not have common property. Hence, the error.
So, a good rule of thumb before accessing a property would be to check if that property even exists.
<h1>{country?.name?.common}</h1>
Most of the time, you don't want to display tags if there is no data.
So, you would like to do something like this.
Markup after && will be rendered only if expression before it, is truthy value.
{country?.name?.common && (
<h1>{country.name.common}</h1>
)}
As country initial value is an empty object, the name property will be undefined, which causes this error, You just need to set an initial value for name too, as an empty object
const [country, setCountry] = useState({ name: {} });
Full example of code.
import React, { useState, useEffect } from 'react';
import { useParams } from 'react-router-dom';
function Country() {
const { countryName } = useParams();
const [data, setData] = useState([]);
const [country, setCountry] = useState({ name: {} });
useEffect(() => {
fetch('https://restcountries.com/v3.1/all')
.then(resp => resp.json())
.then(datas => setData(datas));
}, [])
useEffect(() => {
setCountry(data.find(a => a.name.common.toLowerCase() == countryName.replaceAll("-", " ")));
}, [data])
return (
<>
{
<h1>{country.name.common}</h1>
}
</>
)
}
export default Country
Or you can optionally chain the name property.
<h1>{country.name?.common}</h1>
I opened a new tab in Chrome and paste your api https://restcountries.com/v3.1/all and from the network tab, the results have some strange objects which do not have "name": {"common"....}
You have to filter these objects out
I need to change the useState without rendering the page.
First is it possible?
const UsersComponent = ({valueProp}) => {
const [users, setUsers] = useState(valueProp);
const [oldUsers, setoldUsers] = useState(value);
const allUsers = useSelector((state) =>
state.users
);
useEffect(() => {
dispatch(getUsersData());
}, [dispatch]);
useEffect(() => {
// assign users to state oldUsers
}, [dispatch]);
const onClickMergeTwoArrayOfUsers = () => {
let oldUsers = collectData(oldUsers);
const filteredUsers = intersectionBy(oldUsers, valueProp, "id");
setUsers(filteredUsers); // most important
console.log("filteredUsers", filteredUsers); // not changed
};
I tried everything nothing helps me.
useEffect(() => {
let oldUsers = collectData(oldUsers);
const filteredUsers = intersectionBy(oldUsers, valueProp, "id");
setUsers(filteredUsers); // most important
}, [users]); // RETURN INFINITIVE LOOP
I am also try ->
useEffect(() => {
let oldUsers = collectData(oldUsers);
const filteredUsers = intersectionBy(oldUsers, valueProp, "id");
setUsers(filteredUsers); // most important
}, []);
Load only one and that doesn't mean anything to me..
I am try with useRef ,but that doesn't help me in this case.
I will try to explain the basis of the problem.
I need to get one get data. After that get on the click of a button, I need to merge oldUsers and users without rendering, change the state. That is problem.
If there is no solution to this problem, tell me what I could do to solve the problem?
I am googling but without succes ... I am also try this solution from interent ->
const [state, setState] = useState({});
setState(prevState => {
// Object.assign would also work
return {...prevState, ...updatedValues};
});
no work.
I am also try with ->
const [state, setState] = useState(() => {
const initialState = someExpensiveComputation(props);
return initialState;
});
Here is problem because I need to asynchronous get only after that can I looping.
Using a ref is probably a better option for whatever it is you're ultimately trying to do.
Yes, it is possible, but it violates one of the core rules of React state: Do Not Modify State Directly.
React compares state values using Object.is equality, so if you simply mutate an object in state instead of replacing it with a new value that is not object-equal, then the state "update" will not cause a re-render (but this is considered a bug in your program!). Anyway, this is how you'd do it:
<div id="root"></div><script src="https://unpkg.com/react#17.0.2/umd/react.development.js"></script><script src="https://unpkg.com/react-dom#17.0.2/umd/react-dom.development.js"></script><script src="https://unpkg.com/#babel/standalone#7.17.1/babel.min.js"></script>
<script type="text/babel" data-type="module" data-presets="env,react">
const {useCallback, useState} = React;
function Example () {
const [state, setState] = useState([1]);
const logState = useCallback(() => console.log(state.join(', ')), [state]);
// Don't actually do this!!!
const mutateState = () => {
setState(arr => {
arr.push(arr.at(-1) + 1);
return arr;
});
};
return (
<>
<div>{state.join(', ')}</div>
<button onClick={mutateState}>Mutate state</button>
<button onClick={logState}>Log state</button>
</>
);
}
ReactDOM.render(<Example />, document.getElementById('root'));
</script>
I have a component that uses a prop that recieves data from its parent. I need to edit the value of the prop Im not sure if its possible. I tried using state to copy
const Form = ({ details, form }) => {
useEffect(() => {
details.name = 'greg'
}, [])
}
Is it possible to do something like this? I get an error something like object is not extensible.
Assuming, details is a state variable, you can't update state that way. State is supposed to be immutable. To update state, you can do something like:
import React, {useState, useEffect} from "react";
const Parent = () => {
const [form, setForm] = useState({});
const [details, setDetails] = useState({ name: "bill" });
return (
<Form details={details} form={form} setDetails={setDetails} />
);
};
const Form = ({ details, form, setDetails }) => {
useEffect(() => {
setDetails({
...details,
name: "greg",
});
}, []);
return (
<div>Hello</div>
);
}
Hello so am trying to make a custom hook where i get my user object from database and using it however my console log shows the the object is undefined shortly after object appears and this effect other function relying on it they only capture the first state and gives an error how can i fix that here is my code for the hook:-
import { useState, useEffect } from 'react'
import { storage, auth, database } from '../api/auth';
export default function useUser() {
const [user, setUser] = useState()
const userId = auth.currentUser.uid;
useEffect(() => {
getCurrentUser();
}, [])
const getCurrentUser = () => {
database.ref("users/" + userId).orderByChild('name').once("value").then(snapshot => {
setUser(snapshot.val())
})
}
return user
}
First of all you can assign default value to userObject.
const [user, setUser] = useState(null);
After place where you use useUser hook you can make a check is it null or not.
const user = useUser();
if (!user) {
return <Text>Loading...</Text>;
}
return(
<Text>{user.name}</Text>
);