I am retrieving DOM nodes of my child components by passing a callback into its ref prop as shown.
Parent component:
setElementRef = (name, element) => {
if (element) {
this.setState({
...this.state,
menuItems: {
...this.state.menuItems,
[name]: prop,
},
});
}
};
render() {
return <Child refCallback={(node) => this.setElementRef("child", node)} />
}
Child component:
render() {
return (
<div ref={this.props.refCallback}/>
}
The information in the nodes such as getBoundingClientRect() are needed. However, I am unable to setState as it exceeds the maximum update depth when multiple child components trigger the callback. Is there a way of storing multiple DOM nodes in the state, or should I avoid setting the state completely and use a class variable instead?
Theoertically said, reference is not state. Therefore you should not use state to store component reference.
In your case, you just need to create object on class to keep your references (Unline setState it won't trigger re-render, and it will be accessible from within your component, or you can pass it as prop)
childRefs = {}
setElementRef = (name, element) => {
this.childRefs.current[name] = element;
}
// ... and use it like this
someOperation = () => {
const { child } = this.childRefs;
if (child) {
const rect = child.getBoundingClientRect();
}
}
Original answer - to be used with functional components and hooks
If you need to work with references, it is recommended to use useRef (It allows you to update it's value without rerendering component) to keep actual reference, or to keep one single object, and just set properties with your reference. Then you can work with those refernces in callbacks or useEffect.
Related
Take this example of state:
const [state, setState] = useState({ x: 1, nested: { y: 2, z: 3 } });
The immutable version of a handler to increment the value of y would be as follows:
function incrementY() {
setState((state) => {
return { ...state, nested: { ...state.nested, y: state.nested.y + 1 } };
});
}
The next version creates a new state object, but mutates the nested object:
function incrementY() {
setState((state) => {
state.nested.y += 1;
return { ...state };
});
}
Both versions will lead to a re-render of the component. Now, let's assume this component renders a child:
return (
<div onClick={() => incrementY()}>
<Child nested={state.nested} />
</div>
);
If I understand correctly, a re-render of the parent component will always lead to a re-render of the child component (even when the reference to the nested object stays the same).
I'm aware that immutable state can be useful when there is a need to keep the history of states (e.g. for CTRL + Z functionality). But are there any reasons specific to React that I'm missing?
If I understand correctly, a re-render of the parent component will always lead to a re-render of the child component
By default yes, but the child component can use React.memo (or other techniques for class components) to skip rendering if its props have not changed. If you mutate the nested object, then pass that object to the child, it will look to the child like nothing has changed.
So if you want to be able to reliably use react's tools for skipping rendering, then you must make your state immutable at all levels.
How can I convert this hook-based code to class-based code? Does the code still work?
I'm using a react/ASP.net Core template. My project consists of several components that are siblings.
I'm trying to send a state from a component to another one.
import { useState } from "react";
//the change is reflected in the ImageEditor component
const ImageEditor = ({ yourState }) => (
<p>State in the ImageEditor = {yourState}</p>
);
//ImageTile changes the state through the setYourState method
const ImageTile = ({ yourState, setYourState }) => (
<button onClick={() => setYourState("World!")}>
Change State from the ImageTile
</button>
);
//App is the parent component and contains both image editor and tile
const App = () => {
//the state which holds the image ID is declared in the parent
const [imageId, setImageId] = useState("Hello");
return (
<div>
<ImageTile yourState={imageId} setYourState={setImageId} />
<ImageEditor yourState={imageId} />
</div>
);
};
export default App;
You can see the complete code on:
https://codesandbox.io/s/billowing-brook-9y9y5?file=/src/App.js:0-775
A parent passes it’s state to a child via props. The child is not allowed to change its parents state, if a child wants to change a parents state then the parent passes a callback to the child that the child can call to change the state. This is fundamental to reacts state management. A child does not need to know how a parent stores it’s state (class instance, hook instance or state library).
if your application uses a global state manager like redux, then global state is mapped to props and a store action can be called to update global state. In this case the child does not need to know who else is using the state because it’s global.
class Foo extends Component {
constructor (props) {
super(props);
this.state = { myState: 0 };
this.setMyState = this.setMyState.bind(this);
}
setMyState (value) {
this.setState({
myState: value
});
}
render () {
return (
<MyChildCompoent myStat={this.state.myState} setMyState={this.setMyState} />
);
}
}
you'll need to declare the state in the parent:
state = {
someKey: '',
};
And then in the parent, define some function to update it:
updateSomeKey = (newValue) => {
this.setState({ someKey: newValue });
}
And then pass both of these values as props to your sibling components:
render() {
return (
<div>
<Sib1 someKey={this.state.someKey} updateSomeKey={this.updateSomeKey} />
<Sib2 someKey={this.state.someKey} updateSomeKey={this.updateSomeKey} />
</div>
)
}
You shouldn't need to in order to update the 'shared' state. In the code above, the someKey state can be updated in either component by calling the updateSomeKey function that is also available as a prop.
If either component calls that function (this.props.updateSomeKey('hello!')) the updated state will propagate to both components.
const Parent = () => {
const [thing, setThing] = useState('a string');
// code to update thing
return <Child thing={thing} />
}
const Child = props => {
return <div>I want {props.thing} to be initial value without updating</div>
}
If I want 'thing' to be passed from parent to child but not update when parent changes it, how do I accomplish this?
I've tried useEffect, tried cloning 'thing' to a constant within Child...
I would use useEffect with the empty [] for dependencies, so that it only runs once.
From Reactjs.org:
If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as a second argument. This tells React that your effect doesn’t depend on any values from props or state, so it never needs to re-run.
const Child = props => {
let thing = props.thing;
let [initialValue, setInitialValue] = useState("");
useEffect(() => {
setInitialValue(thing);
}, []);
return (
<div>
I want <strong>{initialValue}</strong> to be initial value without
updating
</div>
);
};
CodeSandbox
Maybe you can try setting the thing to a variable in the child component when it's null. So when the parent update, you don't have to update the child.
let childThing = null;
const Child = props => {
if(childThing === null)
childThing = props.thing
return <div>I want {childThing} to be initial value without updating</div>
}
I have a problem with getting data from child to parent that is fetched from API
Child:
class Carton extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: false,
matchData: []
};
}
Here I'm fetching data from API and updating the state
async componentDidMount() {
setTimeout(() => {
this.loading = true;
fetch(url)
.then(res => res.json())
.then(data => {
this.setState({
matchData: data.doc[0].data[this.props.sportId].realcategories
});
});
}, 500);
I have looped through nested objects from API and displayed it in the child but I'm stuck how to get data of nested objects to parent. Here is an example how deep I needed to go to get values that I needed
getData() {
this.state.matchData.map((data, i) =>
data.tournaments.map((tour, j) =>
tour.matches.map((matc, k) =>
//values of objects that i need to render a card in Child component
)//...
My first question is if it's better to practice to fetch data from API in parent and then pass objects to the child?
How to pass those objects to parent so I can iterate over and select the first 5 of them for
example
If anybody has any idea or suggestions, please let me know
Br
Stevo
My first question is if it's better to practice to fetch data from API in parent and then pass objects to the child?
It depends. If your parent object needs to be aware of the children without any event occurring inside the child (such as a click, focus, blur, etc.), then yes, the API call should occur inside the parent. If that is not the case, and the parent only needs to be aware of what child elements were acted up on the children, then you can use a function passed into the child from the parent.
For instance:
class Parent {
const handleSelection = childData => {
doSomething(childData)
}
render() {
return(
<Child handleSelection={handleSelection} />
)
}
}
class Child {
render() {
const { myData } = this.state;
return(
<div onClick={() => this.props.handleSelection(myData)} />
)
}
}
This will pass the data from the child component back out to the parent component when the user clicks on the div in child. Hope this helps.
You can use a callback for the purpose, and create a state variable for matchData in the parent component instead. Then pass the matchData state to the child. When you make the API call in componentDidMount, make a call to the callback function (defined in the parent), sending the data you fetched from the API as args. And further in the callback function, set the matchData state variable, which will then automatically re-render both the parent & the child.
I'm having a heck of time with this. I do NOT want my child to re-render when the parents state changes. I've tried to use shouldComponentUpdate in the child component, but for some reason that's not even being invoked.
My issue is this. I have several charts on a grid, I want to update one of the charts configuration settings, which I pass as a prop to the component. The parent, which they all share, updates that childs config, but in the process, the config changes and thus they all re-render.
Why isn't shouldComponentUpdate being invoked? It gets invoked on the parent, so I'm assuming it is invoked where the state changes???
My code looks something like:
Parent - has selectDataType with setState
Child1 - calls selectDataType which was passed down as a prop, which re-renders
Child2 - no changes to it's props, but re-renders, which I need to stop
Parent:
selectDataType(e) {
e.stopPropagation();
var cellId = e.currentTarget.dataset.id;
var cellValue = e.currentTarget.childNodes[0].value;
var newCells = [];
newCells = this.state.cells.map(function (cell, i) {
var newObj = Object.assign({}, cell);
if (cellId == cell.id) {
newObj['dataType'] = cellValue;
}
return newObj;
});
this.setState({
cells: newCells
});
return;
}
Child1:
export default class Pie extends React.Component {
constructor(props) {
super(props);
this.create = this.create.bind(this);
}
shouldComponentUpdate() {
return false;
}
create() {
return {
//some data
}
}
render() {
return (
<div>
<ReactECharts
option={ this.create() }
style={{ position: "absolute", top: 0, bottom: 0, left: 0, right: 0, height: "100%" }}
theme="chalk"
notMerge={ true }
/>
</div>
)
}
}
Child2: exactly like Child1
Make sure your children are given a static key prop (as for all arrays of components). If they're given a random key, then when the parent re-renders it'll give all the children a new random key (different from their old one). This will make React think it's a completely different component, so the existing one will completely unmount and be remounted (with the new properties). So the child component isn't updating, it's remounting. Hence shouldComponentUpdate won't be called.
You might find answers here, for both classes and hooks:
ReactJS: setState on parent inside child component
Short answer for hooks would be:
Send parent state and setState in child prop. Lets call those parentState and setParentState
In child component:
have a useEffect which only does something when parentState is updated:
useEffect(() => {
props.parentStateSetter(childStateValueYouWantToTrack);
}, [props.parentState, childStateValueYouWantToTrack]);
You should check out the link for more information