ReactJS: Passing state to child returns undefined - javascript

I am setting the state in a parent component. Then when that state is set (with componentDidMount) I need to pass that to a child.
Currently, since it is returning undefined in the child component. I presume this is because state isn't set at the moment that the non-existent state is being passed. Making it undefined.
Tried putting boolean conditions in for state so it needs to be true before passing. Booleans in the child, in the parent, anywhere i thought it might make a difference and would prevent the component for looking for the prop I'm passing before it exists.
yearArr is being set here with the result of a function being executed as the component is mounted
componentDidMount() {
const yearArrSorted = dataSet && this.playFunction();
this.setState({
locationData: dataSet,
yearArr: yearArrSorted
});
this._layerRendering();
this._animate();
}
here is the func:
playFunction = () => {
//i know this is unreadable, but i wanted to see how simple i could go.
return dataSet.map(i => {
return Object.keys(Object.values(i)[2])
.map(i => (i.startsWith(9) ? 19 + i : 20 + i))
.sort();
})[0];
};
Here's state:
this.state = {
locationData: "",
year: 95,
elevationScale: elevationScale.min,
yearArr: []
};
Hjere is where state is being passed, with my terribly hacky approach to try to get the state only being passed once its set:
<YearSelector
yearOnChange={this.yearOnChange}
year={this.state.year}
dataStateChange={this.dataStateChange}
dataSet={dataSet}
years={this.state.yeaArr.length > 0 && this.state.yeaArr}
/>
I expect to send state to the child component as a prop when it is set so that prop can be used to .map through the prop array and create a <select> DOM element with <option>
Can edit question as needed and code can be supplied.

Sorted the issues.
Tried this initially but it didn't work for whatever reason.
years={this.state.yearArr && this.state.yearArr}
Now it works, the method i tried at first, which is a status quo for react.

Related

How to update or add an array in react.js?

I'm using react native and my component is based on class base function. I'm facing difficulty in updating or adding object in an array..
My case :
I have an array:
this.state = {
dayDeatil:[]
}
now i want to add an obj in it but before that i want check if that object exist or not.
obj = { partition :1, day:"sunday","start_time","close_time",full_day:false}
in condition i will check partition and day if they both not match. then add an object if exist then update.
here is function in which i'm trying to do that thing.
setTimeFunc =(time)=>{
try{
console.log("time.stringify() ")
let obj = {
partition:this.state.activePartition,
day:this.state.selectedDay.name,
full_day:false,
start_time:this.state.key==="start_time"?time.toString():null
close_time:this.state.key==="close_time"?time.toString():null
}
let day = this.state.dayDetails.filter((item)=>item.day===obj.day&&item.partition===obj.partition)
if (day.length!==0) {
day[this.state.key]=time.toString()
this.setState({...this.state.dayDetail,day})
} else {
console.log("2")
this.setState({
dayDetails: [...this.state.dayDetails, obj]
})
}
this.setState({ ...this.state, clockVisiblity: false });
}
catch(e){
console.log("error -> ",e)
}
}
To check if the object exists or not, you can use Array.find() method, if it doesn't exists, the method will return undefined.
Now to update the state, the easier way would be to create a new array and set it as the new dayDetails state like this:
const { dayDetails } = this.state;
dayDetails.push(newData);
this.setState({ dayDetails: [...dayDetails] })
You should use the spread operator when setting the state because React uses shallow comparision when comparing states for a component update, so when you do [...dayDetails] you're creating a new reference for the array that will be on the state, and when React compares the oldArray === newArray, it will change the UI.
Also, after your else statement, you're updating the state with the state itself, it's good to remember that React state updates are asynchronous, so they won't be availabe right after the setState function call, this may be causing you bugs too.

How to make props equal to state that does not exist yet?

Solved Thank you for your help
I am setting props of component
<Component myprops={state_variable}/>
The problem is that when I am creating the component and setting the props the state variable does not exist yet and my code breaks. What can I do to solve this problem? In addition when I change the state the prop is not updated.
<ServiceTicket
showOverlay={Tickets_disabled_withError[ticket_num]?.isDisabled}
showSelectedError={Tickets_disabled_withError[ticket_num]?.showError}
/>
My intial state initial variable:
const [Tickets_disabled_withError,setTickets_disabled_withError] = useState({})
I am trying to call function that will update state and change value that props is equal to.
const OverLayOnAll = (enable) =>
{
let tempobject = Tickets_disabled_withError
for (let key in tempobject)
{
if (enable == "true")
{
tempobject[key].isDisabled = true
}
else if (enable == "false")
{
tempobject[key].isDisabled = false
}
}
setTickets_disabled_withError(tempobject)
}
I fixed the issue. Thank you so much for your help. I had to set use optional chaining ?. and also re render the component.
The value exists. It's just that the value itself is undefined. You need to set an initial value when defining your state
const [statevariable, setstatevariable] = useState({
somekey: {
isDisabled: false // or whatever the initial value should be
}
}) // or whatever else you need it to be
For your second problem, you are using the same pointer. JavaScript does equality by reference. You've transformed the existing value, so React doesn't detect a change. The easiest way to fix this is to create a shallow copy before you start transforming
let tempobject = {...Tickets_disabled_withError}
Your question isn't very clear to me, but there's a problem in your setTickets_disabled_withError call.
When you update a state property (ticketsDisabledWithError) using its previous value, you need to use the callback argument.
(See https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous)
overlayAll = (enable)=> {
setTicketsDisabledWithError((ticketsDisabledWithError)=> {
return Object.keys(ticketsDisabledWithError).reduce((acc,key)=> {
acc[key].isDisabled = (enabled=="true");
return acc;
}, {}); // initial value of reduce acc is empty object
})
}
Also, please learn JS variable naming conventions. It'll help both you, and those who try to help you.

Getting previous State of useState([{}]) (array of objects)

I am struggling to get the real previous state of my inputs.
I think the real issue Which I have figured out while writing this is my use of const inputsCopy = [...inputs] always thinking that this creates a deep copy and i won't mutate the original array.
const [inputs, setInputs] = useState(store.devices)
store.devices looks like this
devices = [{
name: string,
network: string,
checked: boolean,
...etc
}]
I was trying to use a custom hook for getting the previous value after the inputs change.
I am trying to check if the checked value has switched from true/false so i can not run my autosave feature in a useEffect hook.
function usePrevious<T>(value: T): T | undefined {
// The ref object is a generic container whose current property is mutable ...
// ... and can hold any value, similar to an instance property on a class
const ref = useRef<T>();
// Store current value in ref
useEffect(() => {
ref.current = value;
}); // Only re-run if value changes
// Return previous value (happens before update in useEffect above)
return ref.current;
}
I have also tried another custom hook that works like useState but has a third return value for prev state. looked something like this.
const usePrevStateHook = (initial) => {
const [target, setTarget] = useState(initial)
const [prev, setPrev] = useState(initial)
const setPrevValue = (value) => {
if (target !== value){ // I converted them to JSON.stringify() for comparison
setPrev(target)
setTarget(value)
}
}
return [prev, target, setPrevValue]
}
These hooks show the correct prevState after I grab data from the api but any input changes set prev state to the same prop values.
I think my issue lies somewhere with mobx store.devices which i am setting the initial state to or I am having problems not copying/mutating the state somehow.
I have also tried checking what the prevState is in the setState
setInputs(prev => {
console.log(prev)
return inputsCopy
})
After Writing this out I think my issue could be when a value changes on an input and onChange goes to my handleInputChange function I create a copy of the state inputs like
const inputsCopy = [...inputs]
inputsCopy[i][prop] = value
setInputs(inputsCopy)
For some reason I think this creates a deep copy all the time.
I have had hella issues in the past doing this with redux and some other things thinking I am not mutating the original variable.
Cheers to all that reply!
EDIT: Clarification on why I am mutating (not what I intended)
I have a lot of inputs in multiple components for configuring a device settings. The problem is how I setup my onChange functions
<input type="text" value={input.propName} name="propName" onChange={(e) => onInputChange(e, index)} />
const onInputChange = (e, index) => {
const value = e.target.value;
const name = e.target.name;
const inputsCopy = [...inputs]; // problem starts here
inputsCopy[index][name] = value; // Mutated obj!?
setInputs(inputsCopy);
}
that is What I think the source of why my custom prevState hooks are not working. Because I am mutating it.
my AUTOSAVE feature that I want to have the DIFF for to compare prevState with current
const renderCount = useRef(0)
useEffect(() => {
renderCount.current += 1
if (renderCount.current > 1) {
let checked = false
// loop through prevState and currentState for checked value
// if prevState[i].checked !== currentState[i].checked checked = true
if (!checked) {
const autoSave = setTimeout(() => {
// SAVE INPUT DATA TO API
}, 3000)
return () => {
clearTimeout(autoSave)
}
}
}
}, [inputs])
Sorry I had to type this all out from memory. Not at the office.
If I understand your question, you are trying to update state from the previous state value and avoid mutations. const inputsCopy = [...inputs] is only a shallow copy of the array, so the elements still refer back to the previous array.
const inputsCopy = [...inputs] // <-- shallow copy
inputsCopy[i][prop] = value // <-- this is a mutation of the current state!!
setInputs(inputsCopy)
Use a functional state update to access the previous state, and ensure all state, and nested state, is shallow copied in order to avoid the mutations. Use Array.prototype.map to make a shallow copy of the inputs array, using the iterated index to match the specific element you want to update, and then also use the Spread Syntax to make a shallow copy of that element object, then overwrite the [prop] property value.
setInputs(inputs => inputs.map(
(el, index) => index === i
? {
...el,
[prop] = value,
}
: el
);
Though this is a Redux doc, the Immutable Update Patterns documentation is a fantastic explanation and example.
Excerpt:
Updating Nested Objects
The key to updating nested data is that every level of nesting must be
copied and updated appropriately. This is often a difficult concept
for those learning Redux, and there are some specific problems that
frequently occur when trying to update nested objects. These lead to
accidental direct mutation, and should be avoided.

React setstate not merging the old state into the new state

according to many examples, this should work:
const [_timeseries, $timeseries] = useState({hi:'lol'})
useEffect(() => {
socket.on('plot', e => {
let keyname = Object.keys(e)[0]
$timeseries({..._timeseries, [keyname] : value)})
}
}, [])
console.log(_timeseries) // here results in the initial state, not the set state
The first time it merges, it works.
But once a new event with another keyname enters, it replaces the whole thing again.
Instead of adding a new key with [keyname], the old [keyname] is being replaced.
The problem here is closures.
The callback assigned to the useEffect closes the initial value of _timeseries in it's the lexical scope and it never updated.
To fix it, you need to use the functional useState which uses the most updated state within its callback:
const [_timeseries, $timeseries] = useState({hi:'lol'})
useEffect(() => {
socket.on('plot', e => {
let keyname = Object.keys(e)[0]
$timeseries(timeSeries => {...timeseries, [keyname] : value)})
}
}, [])
The useState hook gives you a function which replaces the state entirely with a new value (doesn't merge it): https://reactjs.org/docs/hooks-state.html
However, unlike this.setState in a class, updating a state variable always replaces it instead of merging it.
You can use setState with a function and merge it yourself:
$timeseries((old) => ({...old, [keyname] : value)}))
If you use it without a function it might have the old values (because you don't specify it as a dependency of useEffect)

Change a prop value inside functional component can cause problems in react component?

If I receive a prop and change it in my functional component, can it create a problem? Or it's ok to change it inside the component?
e.g
const MyComponent = ({ foo }) => {
// is this bad?
foo = someCondition ? bar : foo
return (...)
}
I know that I could create another variable and store the values, but I would like to know if changing the prop it self won't cause any problem because it's a functional component.
No, it shouldn't create any problems. As with regular functions the arguments passed are their own variables in the function scope and don't mutate the original value passed to the function.
function something(value) {
value = 'nothing';
}
var anything = 0;
something(anything);
// Anything should still be 0;
console.log(anything);
But I would suggest to not mutate your variables.
If foo in your example is passed from the parrent, and the parrent keeps it in its state, then you would also need to pass setFoo as a paramater to your component and use that to update it properly.
function Parrent(){
let [foo, setFoo] = useState('foo');
return <Child foo={foo} setFoo={setFoo}/>
}
As for changing the props directly, you can if they are arrays or objects.
Props in the React are just read-only variables. You should change the props values by the parent component
I avoid changing the prop.
But I created a simple example and changing the prop in the children do not affected the value in the parent component.
https://codesandbox.io/s/objective-cdn-cq55d
I tested it with several render. Thats why I added an input. Typing in it makes the component rerender.
const MyComponent = ({ foo }) => {
// Not valid
foo = someCondition ? bar : foo
return (...)
}
There are two kinds of data in React,
a)Props(immutable data)
b)State(mutable data)
you are not supposed to change the immutable data(there are some ways to do it, but not recommended). what you should do is, (you can't assign a callback and change from here, i'll explain later why)
if you want to just use just the value inside this component
const baz = foo === condition ? bar : foo
or render something based on foo meets some condition
return (
<div>
{foo === somecondition ? <A /> : <B />}
</div>
)
Or you want to actually change it,
coming from a global state like redux or mobx,
u should change it from the reducers in case of redux or
#action decorated functions in mobx.
if it's a local state which passed down to the child component,
u can set a call back and assign to a click handler in which the case it is feasible
handleClick = () => {
this.setState(prevState => ({
...prevState,
foo: someCondition ? bar : foo,
}))
}
render () {
const { handleClick } = this
return <ChildComponent {...{ handleClick }} />
}
Like said before u can't change the passed down local state from render of the child(or render of any component[actually u can, but may end up in infinite loops: each time a state change happens, the component will re render, so the loop(pure components excluded eg: shouldComponentUpdate() hook which validates an unrelated condition)])
in such cases what u should do is to make the child component also a stateful component and change the parent props with a callback
class Child extends Component {
//set once
componentWillMount() {
if (condition) callback
}
//whenever there is change
componentWillReceiveProps(nextProps) {
if (condition) callback
}
//use correct lifecycle method which meets your requirement..
}

Categories

Resources