Refs in dynamic components with ReactJS - reactjs

I'm facing a issue and I haven't found any documentantion related.
This is my component's render method:
render()
{
return (
<div refs="myContainer">
</div>
);
}
Additionally I have a method to get data from my Java server:
getAsyncData()
{
$.get('...URL...')
.done(function(response) {
var list = JSON.parse(response); // This is an objects array
var elementsList = [];
list.forEach(function(item) {
elementsList.push(<div ref={item.id}>{item.name}</div>);
});
React.render(elementsList, React.findDOMNode(this.refs.myContainer));
});
}
In my componentDidMount method I just start the async method:
componentDidMount()
{
this.getAsyncData();
}
So I'm getting this error from ReactJS:
Only a ReactOwner can have refs. This usually means that you're trying
to add a ref to a component that doesn't have an owner (that is, was
not created inside of another component's render method). Try
rendering this component inside of a new top-level component which
will hold the ref.
So this means that I'm not able to use my dynamic elements, additionally think that instead of a simple DIV I would have a complex component with methods within.
How can I deal this?
Thank you!

How can I deal this?
By writing the component how it is supposed to. Keep the data in the state. .render() is called when the state changes, updating the output.
Example:
var Component = React.createClass({
getInitialeState() {
return {items: []};
},
getAsyncData() {
$.get(...).done(
response => this.setState({items: JSON.parse(response)})
);
},
render() {
var list = this.state.items.map(
item => <div key={item.id} ref={item.id}>{item.name}</div>
);
return <div>{list}</div>;
}
});
That's what React is all about: Describing the output based on the data. You are not adding or removing elements dynamically, you update the state, rerender and let React figure out how reconcile the DOM.
However, it's unclear to me why you need a ref in this situation.
I recommend to read https://facebook.github.io/react/docs/thinking-in-react.html

Related

How to structure a component using React Hooks with an object that uses the DOM directly? (such as OpenLayers)?)

I'm trying out using React Hooks where in a component that I previously have as class based. The component looks like this:
class A extends Component {
constructor(props) {
super(props)
this.mapRef = createRef()
this.map = new Map({ ... })
}
componentDidMount() {
this.map.setTarget(this.mapRef.current)
}
// Also shouldComponentUpdate & componentWillUnmount
render() {
return (
<div>
<div ref={this.mapRef}></div>
{this.props.children({map: this.map})}
</div>
)
}
}
My understanding of the open-layers library is that when I create an instance of the Map object, I need to pass it a reference to a DOM element since the library requires direct control over a DOM element. I'm doing this in the componentDidMount function via a ref.
Trying to change this code to React Hooks (out of interest), I have tried this:
function A (props) {
var map
const mapRef = useRef(null)
useEffect(() => {
map = new Map()
map.setTarget(mapRef.current)
})
return (
<div>
<div ref={mapRef}></div>
{props.children({map})}
</div>
)
}
Which just errors (because the props.children function gets null for the map object). I tried moving the map object initialization out of the function, which seems to work:
const map = new Map({ ... })
function A (props) {
const mapRef = useRef(null)
useEffect(() => {
map.setTarget(mapRef.current)
// Then adjust the map however necessary depending on props
})
return (
<div>
<div ref={mapRef}></div>
{props.children({map})}
</div>
)
}
This somewhat works... although it seems that the useEffect callback fires far more often then necessary. And I have to figure out how to implement shouldComponentUpdate.
Is this the 'correct' approach to using React Hooks? I can't help feeling that in this case either a class component makes a lot more sense, or I'm not using Hooks correctly (probably the latter).
In this case I'm not actually using a class component for state at all, but rather for the ability to use lifecycle methods to update the map instance due to DOM changes.
This was a useful question for me when I was trying to create my own Map component with OpenLayers. I used a slightly different approach:
The olMap.setTarget method accepts either an HTML element or the ID of one. So I construct the initial OlMap object and give the target key a value of undefined. The target is then set in a useEffect to the div's id.
To make sure this effect only runs when the component mounts and not on each render, an empty array is passed as the second parameter to useEffect. The effect returns a function that sets the target back to undefined when the component unmounts. This part is necessary if the map is in a component that is only rendered on certain routes. The map will not re-render if you navigate away and then back if you don't set the target again.
import React, { useEffect } from 'react';
import { Map, View } from 'ol';
const olMap = new Map({
target: undefined,
layers: [
new TileLayer({
source: new OSM()
})
],
view: new View({
center: [-6005420.749222653, 6000508.181331601],
zoom: 9
})
});
export default function OlMap() {
useEffect(() => {
olMap.setTarget('map')
return () => olMap.setTarget(undefined);
}, []);
return (
<div id='map'>
</div>
)
}
If you are using a library that needs a HTMLElement you can use useRef and ref instead:
export default function OlMap() {
let mapDiv = useRef(null);
useEffect(() => {
olMap.setTarget(mapDiv.current)
return () => olMap.setTarget(undefined);
}, []);
return (
<div id='map' ref={mapDiv}>
</div>
)
}
I've left the second parameter as an empty array again, but you could pass mapDiv in that array if you wanted to call the effect again should the ref ever change. I don't think this is needed for OpenLayers, but another library might make changes other than just appending to the target HTMLElement.
If you want the useEffect hook to fire only when needed you can put an array with the properties that would trigger the hook as a second argument.
useEffect(() => {map.setTarget(...)}, [mapRef.current])

How to update react state without re-rendering component?

I am building a gallery app where I need to create multiple HTTP requests to pull gallery entries(images & videos).
As gallery will be auto scrolling entries, I am trying to prevent re-rendering component when I make subsequent HTTP requests and update the state.
Thanks
Here's an example of only re-rendering when a particular condition is fulfilled (e.g. finished fetching).
For example, here we only re-render if the value reaches 3.
import React, { Component } from 'react';
import { render } from 'react-dom';
class App extends React.Component {
state = {
value: 0,
}
add = () => {
this.setState({ value: this.state.value + 1});
}
shouldComponentUpdate(nextProps, nextState) {
if (nextState.value !== 3) {
return false;
}
return true;
}
render() {
return (
<React.Fragment>
<p>Value is: {this.state.value}</p>
<button onClick={this.add}>add</button>
</React.Fragment>
)
}
}
render(<App />, document.getElementById('root'));
Live example here.
All data types
useState returns a pair - an array with two elements. The first element is the current value and the second is a function that allows us to update it. If we update the current value, then no rendering is called. If we use a function, then the rendering is called.
const stateVariable = React.useState("value");
stateVariable[0]="newValue"; //update without rendering
stateVariable[1]("newValue");//update with rendering
Object
If a state variable is declared as an object, then we can change its first element. In this case, rendering is not called.
const [myVariable, setMyVariable] = React.useState({ key1: "value" });
myVariable.key1 = "newValue"; //update without rendering
setMyVariable({ key1:"newValue"}); //update with rendering
Array
If a state variable is declared as an array, then we can change its first element. In this case, rendering is not called.
const [myVariable, setMyVariable] = React.useState(["value"]);
myVariable[0] = "newValue"; //update without rendering
setMyVariable(["newValue"]); //update with rendering
None of the answers work for TypeScript, so I'll add this. One method is to instead use the useRef hook and edit the value directly by accessing the 'current' property. See here:
const [myState, setMyState] = useState<string>("");
becomes
let myState = useRef<string>("");
and you can access it via:
myState.current = "foobar";
So far I'm not seeing any drawbacks. However, if this is to prevent a child component from updating, you should consider using the useMemo hook instead for readability. The useMemo hook is essentially a component that's given an explicit dependency array.
It's as easy as using this.state.stateName = value. This will change the state without re-rendering, unlike using this.setState({stateName:value}), which will re-render. For example;
class Button extends React.Component {
constructor( props ){
super(props);
this.state = {
message:"Hello World!"
};
this.method = this.method.bind(this);
}
method(e){
e.preventDefault();
this.state.message = "This message would be stored but not rendered";
}
render() {
return (
<div >
{this.state.message}
<form onSubmit={this.method}>
<button type="submit">change state</button>
</form>
</div>
)
}
}
ReactDOM.render(<Button />, document.getElementById('myDiv'));
If you just need a container to store the values, try useRef. Changing the value of ref.current doesn't lead to re-rendering.
const [ loading,setLoading] = useState(false)
loading=true //does not rerender
setLoading(true) //will rerender
In functional component refer above code, for class use componentShouldUpdate lifecycle

How to force dom re render in react

I am trying to force a child component to re-render. I have tried this.forceUpdate();, but it does not work. I put console.log statements in my <PostList /> component, and none of them are ever called--not componentDidMount, nor componentWillMount, componentWillReceiveProps, none of them. It's as if the <PostList /> component is never initialized. I am sure it is though, because I know for a fact items.count retrieves my items. Here is my render method:
render() {
const items = this.state.posts;
const postList = items.count > 0 ? (<PostList comingFromSearch={true} xyz={items} />) : (<div></div>)
const navBar = <NavigationBar />
return (
<div><br/>{navBar}
<div className="container">
<h3>Search Results for {this.state.searchTerm}</h3>
<div className="row">
<div className="col-x-12">{postList}</div>
</div>
</div>
</div>
)
}
And here is my api call:
retrieveSearch(term) {
Helpers.searchWithTerm(term).then((terms) => {
const postsWithTermsInTitle = terms.titleResults
this.setState({posts: postsWithTermsInTitle})
this.forceUpdate();
}).catch((error) => {
console.log("error searching: " + error);
})
}
I should note, on my previous page, i had another ` component, and maybe react is using that one instead of this one? I want to force it to use this instance.
If this.forceUpdate(); does not make the whole DOM re-render, how can I do that?
thanks
your PostList and NavigationBar Components might not update because they only update when their props are changed (shallow compare).
PostList might not update when changing the inner content of the array, because the component will shallow compare the new state with the previous one. Shallow comparing an array will basically checked against its length property. which does not change in this case.
Quick Solution
Sometimes you need to update a List, without changing any of its props or the length of the list. To achieve this, just pass a prop to the component and keep incrementing it instead of calling force update.
retrieveSearch(term) {
Helpers.searchWithTerm(term).then((terms) => {
const postsWithTermsInTitle = terms.titleResults
this.setState((curState) => ({posts: postsWithTermsInTitle, refreshCycle: curState.refreshCycle+1}))
this.forceUpdate();
}).catch((error) => {
console.log("error searching: " + error);
})
}
render() {
...
<PostList
...
refreshCycle={this.state.refreshCycle}
/>
...
}
Right solution
The right solution is to provide an itemRenderer which you is a function that returns the an individual item from the list. This function is passed as a prop to the component.
This way you have control over how the items inside the list will appear, also changes inside the itemRenderer function will cause a component update.
itemRenderer(itemIndex) {
return <div>{this.props.item[itemIndex]}</div>;
}
render() {
...
<PostList
itemRenderer={this.itemRenderer.bind(this)}
itemsLength={items.length}
/>
...
}
The itemRenderer will be called inside the PostList in a loop (of length itemsLength). each loop will be passed the index of the current iteration, so you can know which item of the list to return from the function.
This way you can also make your list more scalable and more accommodating.
You can check an implementation of such solution on a list package like this one: https://www.npmjs.com/package/react-list
You can force a re-render of a component and all its children by changing the state of the component. In the constructor add a state object:
constructor(props) {
super(props)
this.state = {
someComponentState: 'someValue'
}
}
Now whenever you do:
this.setState(someComponentState, 'newValue')
It will re-render the component and all its children.
This of course assumes your component is a class based component, not a functional component. However, if your component is a functional component you can easily transform it to a class based component as follows:
class ComponentName {
constructor() {
// constructor code
}
render() {
// render code
}
}
export default ComponentName
Understand that componenet level state is not the same as redux state but is exposed only inside the component itself.

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.

Reflux/React - Managing state with asynchronous data

I think this is a trivial use case but I am still getting to grips with aspects of Flux and React. In my example I am using Reflux and React Router.
I have a basic component that displays the details of a sales lead. Using the lead id from the URL parameter, it makes a call to my service to get the lead:
var React = require('react'),
Reflux = require('reflux');
var leadStore = require('../stores/lead'),
actions = require('../actions');
var Lead = React.createClass({
render : function () {
var p = this.props.lead;
return (
<div>
<h2>{p.details}</h2>
<p>{p.description}</p>
</div>
);
}
});
module.exports = React.createClass({
mixins: [Reflux.connect(leadStore)],
componentDidMount : function () {
actions.loadLead(this.props.routeParams.id);
},
render : function () {
if (!this.state.lead) {
return (
<Lead lead={this.state.lead} />
);
}
return (
<div>Loading</div>
);
}
});
If I don't include the conditional check then on the first pass the render method will fire and kick off an error because there's nothing in state. I could set some default state parameters but what I would ultimately like to do is show a pre-loader.
The approach I've taken feels wrong and I was wondering what the common practice was for this situation? All the examples I've seen use default states or pre-load mixins. I'm interested in knowing if there's a better way to make use of the component life-cycle?
Aside from if (!this.state.lead) should be if (this.state.lead) it looks ok. Another way would be.
module.exports = React.createClass({
mixins: [Reflux.connect(leadStore)],
componentDidMount : function () {
actions.loadLead(this.props.routeParams.id);
},
render : function () {
var returnIt;
if (this.state.lead) {
returnIt = (<Lead lead={this.state.lead} />);
} else {
returnIt = (<div>Loading</div>);
}
return (returnIt);
}
});
React Wait to Render is worth checking out. It's a tiny component that will display a loader until all of the component's props are defined, e.g. while we wait for lead data. Good for keeping the render clean, as easy as:
<WaitToRender
wrappedComponent={Lead}
loadingIndicator={LoadingComponent}
lead={this.state.lead} />
You can also use it to decorate your Lead component.

Resources