How to write and read component state in React? - reactjs

I have MyComponent using setData() to write data in state and getData() to read the state. Is it best practice in React ? It works fine for me but not sure if what I am doing is the simplest possible way, please advice
class MyComponent extends Component {
constructor(props) {
super(props);
this.state = {
data: []
};
}
setData(){
this.setState({data:"123"});
}
getData() {
console.log(this.state.data); // 123 OK !!!
}
componentDidMount() {
this.setData();
}
componentDidUpdate() {
this.getData();
}
render() {
return (
<div>
{this.state.data} // 123 OK !!!
</div>
);
}
}

There is absolutely no reason to do this.
Just use this.state.data wherever you need to use it.
If you want to use the state data in a child component, then pass it as a prop and you may also pass a function to the child component that changes the state of the parent component.
(source)

If you want to change the data as some component then you must use setState .
If you want to get the data then you must use this.state.data where data is state variable .
if you want to pass data from parent to child component by using pros.

Related

useState with arrays not rerendering

I am facing issue while using useState hook with array. I checked various resources on stackoverflow, but could not fix it.
my basic code snippet looks like :
const [users, setUsers] = useState([]);
function addNewContact(user) {
const newUsers = [...users,user];
console.log(newUsers);
setUsers(newUsers);
}
<CardContainer users={users}></CardContainer>
class CardContainer extends Component {
constructor(props) {
super(props);
console.log("this -> ");
console.log(this.props.users);
this.state = {
users: this.props.users
}
}
render() {
//console.log(this.state.users)
return (
<div class="row row-cols-1 row-cols-md-2 g-4">
{
this.state.users.map(user => {
return <Card id={user.phone} title={user.name} email={user.email} phone={user.phone}></Card>
})
}
</div>
)
}
}
export default CardContainer;
I am able to see updated array in the console, but the component using it is not rendering again. Can anyone please help me on this.
The issue is due to you're storing the prop in the state of the child component, which is assigned on component initialization and component initialization/constructor only run one, until its remounted. After that, whenever, the state changes in the parent component, the child component is not re-rendering, because it uses its own state for map.
This below code only runs once on the component initialization.
this.state = {
users: this.props.users
}
In the child component, you can directly use the props and the child component will always re-render on change in the parent component. Instead of this.state.users.map you can directly map the array from props like this this.props.users.map. This way,the component will re-render on state change in the parent compoenent.
As #Junaid said, constructor is only called once before component mounting. If you really need to set a separate state inside the child component, then you can use componentDidUpdate(prevProps) react life cycle method. Make sure to compare previous and current props in order to avoid infinite loop of re-rendering.
componentDidUpdate(prevProps) {
if (this.props.users !== prevProps.users) {
this.setState({ users: this.props.users });
}
};

How to update the value of a prop upon a state change in React

I have a parent React component (MainComponent) that renders a child component (theKeyComponent) and passes a constant as a prop (myID). The parent component also tracks the state 'activeLink'.
import theKeyComponent from "../components/theKeyComponent.jsx";
export default class MainComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
activeLink: '#section1'
}
}
render(){
const myId = this.state.activeLink === '#section1' ? '0001' : '0002';
return(){
<theKeyComponent myID={myID} />
<otherComponent otherPropsHere={otherProps} />
}
}
}
Here's a bit from inside the KeyComponent
export default class HelpScoutBeacon extends React.Component {
static propTypes = {
myID: PropTypes.string.isRequired
}
componentDidMount() {
myAPImethod('init', this.props.myID);
}
render(){return(){}}
}
I want to change the value of the constant myID depending on the value of 'activeLink'. This is not a problem when both components are mounted for the first time. However, when the value of 'activeLink' changes 'myID' doesn't change since the child component is already mounted.
I'm struggling to see what would be the 'React way' of doing this. Should 'myID' be set as another state and the function that sets the state for activeLink should include another one to set the state of myID? Or is this overcomplicating things and there's an easier way to re-render only that particular child component so that it considers the new value of myID.
I'm new to React so I was hoping I could get some clarification form SO community.
This is not a problem when both components are mounted for the first
time. However, when the value of 'activeLink' changes 'myID' doesn't
change since the child component is already mounted.
The issue is with regards to how you handle the trigger of the API call.
componentDidMount will only trigger when the component was initially mounted. This will not be triggered if a state is updated. You are going to want to use componentDidUpdate React Lifecycle as well in augmentation to componentDidMount. componentDidUpdate will trigger when your activeLink state changes because you pass it as props to theKeyComponent
componentDidUpdate() {
myAPImethod('init', this.props.myID);
}
Reference: https://reactjs.org/docs/react-component.html#componentdidupdate
import theKeyComponent from "../components/theKeyComponent.jsx";
export default class MainComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
activeLink: '#section1'
}
}
static getDerivedStateFromProps(props, state) {
if (props.activeLink !== state.activeLink ) {
// updating the state here if there are any changes in props.
return {
activeLink: props.activeLink,
};
}
// Return null if the props hasn't changed
return null;
}
use getDerivedStateFromProps hook to find out changes of props. you need to update the changes of props in state.
render(){
// now, here your state is updated.
const myId = this.state.activeLink === '#section1' ? '0001' : '0002';
return (
<theKeyComponent myID={myID} />
<otherComponent otherPropsHere={otherProps} />
)
}
Any suggestions are welcome.

Access updated state values of child from parent in react

I have one component 'Checkin', which further includes a child component 'CheckinGraph'. Checkin graph component has three states values which initialize to 0 but, on the ComponentDidMount life cycle, their values change from 0 to specific values. I want to access these state variables from the parent component.
Here is my code by which I access these state variables.
Child Component
class CheckinGraph extends React.PureComponent{
constructor(props)
{
super(props)
this.state={
totalMaleCount:0,
totalFemaleCount:0,
totalUnspecifiedCount:0
}
}
//Callback function for parent
getCount=()=>
{
let { totalMaleCount, totalFemaleCount, totalUnspecifiedCount}=this.state;
let arr=[totalCount,totalMaleCount,totalFemaleCount, totalUnspecifiedCount];
return arr;
}
componentDidMount()
{
this.setState({totalMaleCount:res.data.maleListCount}); //data is coming from API
this.setState({totalFemaleCount:res.data.femaleListCount});
this.setState({totalUnspecifiedCount:res.data.notSpecified});
}
}
Parent component
class CheckIn extends React.Component{
constructor(props)
{
this.state={}
this.graph=React.createRef();
}
componentDidMount()
{
let total=this.graph.current.getCount();
console.log(total);
}
render()
{
return(
<div>
<CheckinGraph ref={this.graph} />
</div>
)
}
}
Now the problem is, I able to access values from child to parent, but it is the initial values, not the updated values. Any idea what I am doing wrong?
Two approaches:
Use Redux to manage state and then dispatch an action from CHild Component
Pass onChange Function to Child Component and whenever value updated to call the function from Child Component.
The onChange Function is where you will have access to updated value and then you can do whatever you want to do in Parent.

Async update parent props to child component

How to send updated props of parent component to child component after async operation. Is there any lifecycle method available
class SignUp extends Component {
constructor(props){
super(props);
this.state = {...props};
}
componentDidMount(){
this.props.UserSignupType();
}
render(){
<Inputs {...this.state} >
}
}
const stateToProps = state => ({
signupInputs: state.signupInputs
});
connect(stateToProps, null)(SignUp);
class Inputs extends Component {
constructor(props){
super(props);
this.state = {...props};
}
render(){
if(!this.state.isLoaded) {
return <Loading />
} else {
let inputs = [];
this.state.inputs.forEach(function(input){
inputs.push(<TextField value={input.text}>)
});
return <View>{inputs}</View>
}
}
}
Note: Possible duplicate of Pass props to child components after Async update
First correction: You do not need to add data in state from props if you are not updating it. Also if you are updating it, it should be in state of the child component. So parent component can directly pass props to child component like this:
<Inputs {...this.props} >
Now in Inputs component you can directly use props if it's not going to change or you can add it to state using componentWillReceiveProps function as shown below:
componentWillReceiveProps(nextProps){
this.setState({...nextProps});
}
The state can be updated via getDerivedStateFromProps.
Its a static method, where you can compare the state and the new props and update the state.
static getDerivedStateFromProps(nextProps, prevState) {
// Store myId in state.
if (nextProps.myId !== prevState.myId) {
// this updates the state
return {
myId: nextProps.myId
};
}
// This implies it won't update the state
return null;
}
Why do you need to set the state of the child component to the props received? I would just use the props directly.
In your case I am not sure that the constructor gets called on refresh, thus the state of the child component does not get updated. Try using the props directly.

React Child Component Not Updating After Parent State Change

I'm attempting to make a nice ApiWrapper component to populate data in various child components. From everything I've read, this should work: https://jsfiddle.net/vinniejames/m1mesp6z/1/
class ApiWrapper extends React.Component {
constructor(props) {
super(props);
this.state = {
response: {
"title": 'nothing fetched yet'
}
};
}
componentDidMount() {
this._makeApiCall(this.props.endpoint);
}
_makeApiCall(endpoint) {
fetch(endpoint).then(function(response) {
this.setState({
response: response
});
}.bind(this))
}
render() {
return <Child data = {
this.state.response
}
/>;
}
}
class Child extends React.Component {
constructor(props) {
super(props);
this.state = {
data: props.data
};
}
render() {
console.log(this.state.data, 'new data');
return ( < span > {
this.state.data.title
} < /span>);
};
}
var element = < ApiWrapper endpoint = "https://jsonplaceholder.typicode.com/posts/1" / > ;
ReactDOM.render(
element,
document.getElementById('container')
);
But for some reason, it seems the child component is not updating when the parent state changes.
Am I missing something here?
There are two issues with your code.
Your child component's initial state is set from props.
this.state = {
data: props.data
};
Quoting from this SO Answer:
Passing the intial state to a component as a prop is an anti-pattern
because the getInitialState (in our case the constuctor) method is only called the first time the
component renders. Never more. Meaning that, if you re-render that
component passing a different value as a prop, the component
will not react accordingly, because the component will keep the state
from the first time it was rendered. It's very error prone.
So if you can't avoid such a situation the ideal solution is to use the method componentWillReceiveProps to listen for new props.
Adding the below code to your child component will solve your problem with Child component re-rendering.
componentWillReceiveProps(nextProps) {
this.setState({ data: nextProps.data });
}
The second issue is with the fetch.
_makeApiCall(endpoint) {
fetch(endpoint)
.then((response) => response.json()) // ----> you missed this part
.then((response) => this.setState({ response }));
}
And here is a working fiddle: https://jsfiddle.net/o8b04mLy/
If the above solution has still not solved your problem I'll suggest you see once how you're changing the state, if you're not returning a new object then sometimes react sees no difference in the new previous and the changed state, it's a good practice to always pass a new object when changing the state, seeing the new object react will definitely re-render all the components needing that have access to that changed state.
For example: -
Here I'll change one property of an array of objects in my state, look at how I spread all the data in a new object. Also, the code below might look a bit alien to you, it's a redux reducer function BUT don't worry it's just a method to change the state.
export const addItemToCart = (cartItems,cartItemToBeAdded) => {
return cartItems.map(item => {
if(item.id===existingItem.id){
++item.quantity;
}
// I can simply return item but instead I spread the item and return a new object
return {...item}
})
}
Just make sure you're changing the state with a new object, even if you make a minor change in the state just spread it in a new object and then return, this will trigger rendering in all the appropriate places.
Hope this helped. Let me know if I'm wrong somewhere :)
There are some things you need to change.
When fetch get the response, it is not a json.
I was looking for how can I get this json and I discovered this link.
By the other side, you need to think that constructor function is called only once.
So, you need to change the way that you retrieve the data in <Child> component.
Here, I left an example code: https://jsfiddle.net/emq1ztqj/
I hope that helps.
Accepted answer and componentWillReceiveProps
The componentWillReceiveProps call in accepted answer is deprecated and will be removed from React with version 17 React Docs: UNSAFE_componentWillReceiveProps()
Using derived state logic in React
As the React docs is pointing, using derived state (meaning: a component reflecting a change that is happened in its props) can make your components harder to think, and could be an anti-pattern. React Docs: You Probably Don't Need Derived State
Current solution: getDerivedStateFromProps
If you choose to use derived state, current solution is using getDerivedStateFromProps call as #DiogoSanto said.
getDerivedStateFromProps is invoked right before calling the render method, both on the initial mount and on subsequent updates. It should return an object to update the state, or null to update nothing. React Docs: static getDerivedStateFromProps()
How to use componentWillReceiveProps
This method can not access instance properties. All it does describing React how to compute new state from a given props. Whenever props are changed, React will call this method and will use the object returned by this method as the new state.
class Child extends React.Component {
constructor() {
super(props);
// nothing changed, assign the state for the
// first time to teach its initial shape.
// (it will work without this, but will throw
// a warning)
this.state = {
data: props.data
};
}
componentWillReceiveProps(props) {
// return the new state as object, do not call .setState()
return {
data: props.data
};
}
render() {
// nothing changed, will be called after
// componentWillReceiveProps returned the new state,
// each time props are updated.
return (
<span>{this.state.data.title}</span>
);
}
}
Caution
Re-rendering a component according to a change happened in parent component can be annoying for user because of losing the user input on that component.
Derived state logic can make components harder to understand, think on. Use wisely.

Resources