React class component inside react hook - DOM not updating - reactjs

I have below composition:
const HookComponent = (props)=> {
let [month, changeMonth] = useState(moment());
return (
<ChildComponent
month={month}
onChange={m => changeMonth(m)}
minStep={5}
prevMonthIcon="ion-ios-arrow-left"
nextMonthIcon="ion-ios-arrow-right"
/>
)
}
The ChildComponent is a class component which updates the month using setState. Problem with above is that the change is not reflecting on DOM but the state in parent component is changing (via user input - button which changes the state in ChildComponent). I logged it and confirm the month in parent is changing. Is this some limitation of react when using class components within hooks?
When I convert HookComponent to class component and change month using setState, it works as expected and DOM changes on input change.

It seems that the InputMoment component does not use a month prop, but a moment one.
Also, it seems that InputMoment is returning the same moment instance that is passed as moment prop. This causes that when you execute the changeMonth statement, as the reference does not change, the element is not re-rendered.
You can solve this by storing an object in the state. When you call changeMonth you pass a new object, and the InputMoment is then re-rendered correctly:
const HookComponent = (props)=> {
let [month, changeMonth] = useState({ m: moment() });
return (
<ChildComponent
moment={month.m}
onChange={m => changeMonth({ m })}
minStep={5}
prevMonthIcon="ion-ios-arrow-left"
nextMonthIcon="ion-ios-arrow-right"
/>
)
}

Related

How to access data of Child Component from parent component in react js

I am doing a project where i have a toast function which implements toast there i call the function of fetching data from api and updating my state so that whenever i click the update feed button fetching data from api function called, updation of state and toast of success appears. Now the question is i have a component of categories post displays seperate category post inside of all post component which has the function to display toast, how could i pass the updated state,fetching data from api function from child component that is category post component to parent component that is all post component to implement toast for category component.
If I understand your question correctly -- at a high level, you're trying to figure out how to update a state variable of a parent component from within a child component. Easiest way would be with the useState hook, and then by passing the setState function to the child component.
const ParentComponent = () => {
const [state, setState] = useState([])
useEffect(() => {
// logic that will be executed every time the state variable changes
}, [state])
return <ChildComponent setState={setState} />
}
const ChildComponent = ({setState}) => {
const handleClick = () => {
setState((currentState) => currentState.concat(1))
}
return <Button onClick={handleClick} />
}
Edit: To answer your question from the comment -- a few things to point out here:
You can pass a value to useState which will be the starting value of the variable. In our example, it's an empty array
setState has access to the current state, so you can push a value to an array with this syntax: setState((previousState) => previousState.concat(val))
useEffect is a hook which is invoked whenever there's a change in the value of the dependency (or dependencies) passed in the second argument. So by including state in its dependency array, we can execute whatever logic we want every time the value of the state variable changes
I would also recommend looking into useMemo. It similarly allows you to have aspects of your component logic that are re-executed only when values of certain variables change. For example:
const ParentComponent = () => {
const [state, setState] = useState([])
useEffect(() => {
// logic that will be executed every time the state variable changes
}, [state])
const renderCards = useMemo(() => {
return state.map(val => <SomeOtherComponent val={val}/>)
}, [state])
return (
<div>
{renderCards}
<ChildComponent setState={setState} />
</div>
)
}
By wrapping the function inside renderCards in the useMemo hook, the evaluated result is "memoized". And so it won't be executed on every render, unless the variable in the dependency array changes.
Passing down setState to a child component in order to trigger a re-render in the parent component is straightforward when it's an immediate child. If the child component is nested deeper, or there are multiple components that need to react to a change in a variable (e.g. light/dark mode) -- that's when you want to look into a state management tool like Redux or Context.
There are two ways I can think of to achieve what you are trying to do here, i.e. get the child component's state in a parent component.
You can make use of refs. You should be familiar with React hooks to use this approach. The documentation is really good.
You can also make use of a global state using either Redux or Context.

React: useState value doesn't update on parameter value change

In the example below, the value of the display state on Child component never updates, even if the show parameter toggles between true and false.
I expect it to receive the value and to update accordingly. Can someone please elaborate on why this is not working?
(I know I can use a useEffect callback and setDisplay(show) from inside it, but I'd like to know why a simpler approach like this doesn't work)
function Child({ show }) {
const [display] = React.useState(show);
console.log({ show, display });
return display ? "Message!" : null;
}
function Parent() {
const [show, setShow] = React.useState(false);
const handleClick = () => {
setShow(!show);
};
return (
<div>
<div>
<button onClick={handleClick}>Toggle</button>
</div>
<Child show={show} />
</div>
);
}
Working example: https://codesandbox.io/s/react-boilerplate-4hexp?file=/src/index.js
Well the value of display is set only on the first render of the component (because it is state and state doesnt change with renders, but only when you tell it to change). If you want it to be changing with changing props just use a normal constant instead.
I believe it's because the useState in the Child component is reading show when it first loads but then never updates because it's just set, it doesn't automatically update.
You could either just use show directly which should be used for return show ? 'message' : <></>
Or you could still use the local state with useState, but you would need to add a useEffect to listen to the props change then change the local state of that child.
Update:
Third option for your current code to work would also be to do:
{show && <Child show={show} />}
That way at the time when it's true, the component will read the latest data.
display is local component state of Child, given an initial value from props.show when Child mounted. There is never a state update within Child to render any other value of display. This is actually an anti-pattern to store passed props in local component state, but there are two alternatives/solutions to getting display to update.
Use an effect to update state when the props update
function Child({ show }) {
const [display, setDisplay] = React.useState(show);
useEffect(() => setDisplay(show), [show]);
console.log(show, display);
return display ? "Message!" : null;
}
Or better, just consume the prop show directly
function Child({ show }) {
console.log(show);
return show ? "Message!" : null;
}
The benefit of the latter is that the new value of show and the updated/rerendered UI occur in the same render cycle. With the former (the anti-pattern) the state needs to update then the component rerenders, so the updated UI is a render cycle delayed.

Does React renders Component due to props?

According to that link: http://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/
render() may be triggered with new props. Could someone give me a code example for that? I cannot see how props change invoke rendering! Please not by changing the props via the state; then it is setState() that invokes render()...
Look at shouldComponentUpdate() - this is it's signature - it returns a boolean. Props is there so you can compare and manually say whether the component should update.
shouldComponentUpdate(nextProps, nextState)
For function components React.memo is the replacement for shouldComponentUpdate which is used with class components.
const myComponent = React.memo(props => {
...
code of React.FunctionComponent
...
},
(prevProps, nextProps) => prevProps.propA === nextProps.propA
);
React.memo gets two arguments shown above: a React.FunctionComponent which will be wrapped around by memo and an optional function that returns a boolean.
When the function returns true, the component will not be re-rendered. If the function is omitted then its default implementation in React.memo works like the implementation of shouldComponentUpdate in React.PureComponent. E.g. it does shallow comparison of props, the difference is that only props are taken into account because state doesn’t exist for functional components.
Using hooks is neater to show. The new props data passed to ComponentB causes a re-rendering of ComponentB:
import React, { useState } from 'react'
import ComponentB from '...'
const ComponentA = props => {
const [data, setData] = useState(0) // data = 0
handleChangeProp = item => setData(item) // data = 1
return(
<div>
<button onClick{() => handleChangeProp(1)}
<ComponentB props={data} />
</div>
)
}
Yes, when you do a setState(newState) or when you pass in changed props the component will re render, this is why you can't mutate. The following will not work because you set state with mutated state.
export default function Parent() {
const [c, setC] = useState({ c: 0 });
console.log('in render:', c);
return (
<div>
<button
onClick={() =>
setC(state => {
state.c++;
console.log('state is:', state);
return state;
})
}
>
+
</button>
<Child c={c.c} />
</div>
);
}
That code "won't work" because pressing + will not cause a re render, you mutated state and then set state with the same object reference so React doesn't know you changed anything.
This is how React detects changes, you may think that comparing {c:0} to {c:1} is a change but because you mutated there actually is no change:
const a = {c:1};
a.c++;//you "changed" a but a still is a
To indicate a change in React you have to create a new reference:
const a = {c:1};
const b = {...a};//b is shallow copy of a
a===b;//this is false, even though both a and b have same internal values
This means you can also have unintended renders because you create an object prop that may have the same value but still is a different reference than the last time you created it.
Note that even <Child prop={1} will cause Child to render if Child is not a pure component (see links at the end).
What you want to avoid is doing <Child prop={{c:value}} because every time you pass prop it'll force Child to render and React to do a virtual DOM compare even if value didn't change. The virtual DOM compare will probably still detect that Child virtual DOM is the same as last time and won't do an actual DOM update.
The most expensive thing you can do is <Child onEvent={()=>someAction(value)}. This is because now the virtual DOM compare will fail even if value and someAction did't change. That's because you create a new function every time.
Usually you want to memoize creating props in a container, here is an example of doing this with react-redux hooks. Here is an example with stateful components passing handlers.

Strange problem using conditional rendering of component

I have a simple React component that takes in a single prop, if this prop is 0 show one component otherwise show another.
On page load it equals 0 so the first component loads, once my Redux store is updated from localstorage this value in the parent component updates its local state passing down the new prop to my component.
What happens is rather than switching to render my other component it puts the second component inside of the first... I have never seen this behaviour before and cannot for the life of me figure out why.
I have tried all sorts, switching between stateless and functional components, I have tried using componentWillReceiveProps to receive the updated prop and setState based on this and have my conditional logic based on state. All with the same results.
const ComponentThatGetsUpdated = (props) => (
props.PropThatGetsUpdated === 0 ? <ComponentOne /> : <ComponentTwo />
);
I expect on page load the output to be the contents of
<div>contents of component one</div>
then once my redux store has updated the new props are passed down the output should now be the contents of component two
<div>contents of component two</div>
The actual output once props have been updated is
<ComponentOne />
<ComponentTwo></ComponentTwo>
<ComponentOne />
FYI the parent component of ComponentThatGetsUpdated looks like the below:
class ParentComponent extends Component {
constructor(props) {
super(props);
this.state = {PropToPassDown: 0}
this.sub = this.sub.bind(this);
const sub = store.subscribe(this.sub);
}
sub() {
//when my redux store is updated this function runs and sets PropToPassDown to the new value, for this example it sets PropToPassDown in local state to 1
}
render() {
return (
<ComponentThatGetsUpdated PropToPassDown={this.state.PropToPassDown} />
)
}
}

How do I force a child component to rerender when given new props values?

I have tried this pattern.
ParentComponent
...
render(
return <ChildComponent newProps="newPropsValue />)
ChildComponent
...
ComponentWillReceiveProps{
this.setState({"propsKey": "newPropsValue"})
}
As far as I understand the initial component rendering is triggered by the props change, and as setState is asynchronous (for some reason), the rendering with the new state update is not done on the first pass.
However what I don't understand is why when it finally decides to update the state, it doesn't rerender the component. I thought state changes that are caused by setState always trigger a rerender.
So in the end I have a component that uselessly rerenders before the state is actually changed, and then does nothing when/if(?) the state is updated. I don't understand this behaviour at all.
setState will trigger componentUdpate -> componentWillUpdate -> render. props change will trigger componentWillReceiveProps before this chain. You can have a look here at this image about React lifecycle. You can see the different how React behave on props and state.
So:
However what I don't understand is why when it finally decides to update the state, it doesn't re-render the component.
Updating state by setState will trigger the render function (re-render). And props also trigger render as well.
Following your code:
componentWillReceiveProps:
this.props.newProps="newPropsValue"
this.state.propsKey="newPropsValue"
render: as above, nothing change.
If any event of childComponent setting propsKey by setState (onClick, onChange ...). Assuming setState({propsKey: "anotherValue"}). Then render will be triggered again with this.state.propsKey="anotherValue and this.props.newProps="newPropsValue"
Now let's update your childComponent's props within parentComponent, assuming newProps="latestPropsValue":
Before componentWillReceiveProps:
this.props.newProps="latestPropsValue"
this.state.propsKey="anotherValue"
After componentWillReceiveProps:
this.props.newProps="latestPropsValue"
this.state.propsKey="latestPropsValue"
How do I force a child component to rerender when given new props values?
If your render is using state then setState inside render. And if you are using props inside render, it also being updated accordingly
I have found a nice solution using key attribute. If we changed key property of a child component or some portion of React Component, it will re-render entirely. It will use when you need to re-render some portion of React Component or re-render a child component depending on props or state. Here is a example. I will re-render the full component.
import React, { useState, useEffect } from "react";
import { PrEditInput } from "./shared";
const BucketInput = ({ bucketPrice = [], handleBucketsUpdate, mood }) => {
const data = Array.isArray(bucketPrice) ? bucketPrice : [];
const [state, setState] = useState(Date.now());
useEffect(() => {
setState(Date.now());
}, [mood, bucketPrice]);
return (
<span key={state}>
{data.map((item) => (
<PrEditInput
key={item.id}
label={item?.bucket?.name}
name={item.bucketId}
defaultValue={item.price}
onChange={handleBucketsUpdate}
mood={mood}
/>
))}
</span>
);
};
export default BucketInput;

Resources