why child component does not get rendered in react - reactjs

my doubt is why child component LibrarySubChild is not called, Instead it goes to infinite rendering
my library component is fetching an JSON from local url which in then set the result
import React, { Component } from 'react';
import LibrarySubChild from './Library';
class Library extends Component {
state = {
libraries: []
};
componentDidMount() {
console.log("library componentDidMount method called");
this.fetchData();
}
fetchData = () => {
fetch('/library/get-all-library',{ mode: 'no-cors'})
.then(res => res.json())
.then((data) => {
this.setState({ libraries: data })
console.log(this.state.libraries)
})
.catch(console.log);
};
render() {
console.log("library render method called");
const arrLength=this.state.libraries.length;
return (
this.state.libraries.length>0 ?
<LibrarySubChild libraries={this.state.libraries} /> : arrLength
);
}
}
export default Library;
below component is not rendered
import React, { Component } from 'react';
const LibrarySubChild = ({ libraries }) => {
console.log("library sub child render method called");
return (
<div>
<h1>Contact List</h1>
{libraries.map((library) => (
<div key={library.libid}>
<h5>{library.libName}</h5>
</div>
))}
</div>
)
};
export default LibrarySubChild;
what is the best way to solve this issue
o/p from the console
library componentDidMount method called
library render method called
library componentDidMount method called
(2) [{…}, {…}] this is console output and it goes on for an infinite time

return/render executes before componentDidMount.
the state libraries value (the length of which determines whether LibrarySubChild ever gets rendered) is fetched after Library componentDidMount (as it should). This leads the child component to change after componentDidMount, specifically, an instance of an LibrarySubChild is created and after that, a new instance of Library should be created with the new LibrarySubChild.
The question is, why isn't LibrarySubChild ever getting rendered - rather than an infinite componentDidMount loop in Library?
Answer: Hinted at above, it's because (1) a new instance of Library with the libraries state value reset (in the constructor) to 0 is created rather than simply running componentDidMount once due to React's diffing algorithm (more below) and (2) the state libraries value, which determines what's rendered, is executed at the return/render stage of component lifecycle which necessarily does not have any value in state for libraries because of (1.) - remount is because the root element of Library begins as a p tag and changes to LibrarySubChild being the root.
Put another way, notice that render is executed before componentDidMount in diagram above. Whenever Library is instantiated (or re-mounted), fetchData is executed, which changes libraries state to having > 0 elements, and in turn results in the root element change from a p tag to the LibrarySubChild, which as we know results in a re-mount, and then back to square one (there's the logic behind infinite Library loop and never calling/rendering LibrarySubChild - the question at hand).
Now...you may be thinking "componentDidMount runs only once for any component...Fetching data is always recommended to happen there, right...?"
Generally, sure --- but it's crucial to note React's diffing algorithm (or reconciliation) is the core of whether or any component will remount. So, when will a component will have to remount (thereby triggering componentDidMount)?:
Whenever the root elements have different types, React will tear down the old tree and build the new tree from scratch. Going from to , or from to , or from to - any of those will lead to a full rebuild.
Is root value of Library changing? Indeed, from a p tag to a more-than-p-tag LibrarySubChild component.
import React, { Component } from 'react';
import LibrarySubChild from './Library';
class Library extends Component {
state = {
libraries: []
};
//componentDidMount RUNS **AFTER RETURN/RENDER**
componentDidMount() {
//Infinitely loops because new instances repeatedly made
//As React's diffing algorithm notes change in underlying
//root element types.
console.log("library componentDidMount method called");
this.fetchData();
}
fetchData = () => {
fetch('/library/get-all-library',{ mode: 'no-cors'})
.then(res => res.json())
.then((data) => {
//Side note: Try not to mutate state directly, like below:
Suggestion: this.setState({libraries [...this.state.libraries, data]})
this.setState({ libraries: data })
console.log(this.state.libraries)
})
.catch(console.log);
};
render() {
console.log("library render method called");
const arrLength=this.state.libraries.length;
return (
this.state.libraries.length>0 ?
<LibrarySubChild libraries={this.state.libraries} /> : 0
);
}
}
export default Library;
Good Luck,
Mo

Related

How read array/object from get Axios in React function [duplicate]

I have recently moved from Angular to ReactJs. I am using jQuery for API calls. I have an API which returns a random user list that is to be printed in a list.
I am not sure how to write my API calls. What is best practice for this?
I tried the following but I am not getting any output. I am open to implementing alternative API libraries if necessary.
Below is my code:
import React from 'react';
export default class UserList extends React.Component {
constructor(props) {
super(props);
this.state = {
person: []
};
}
UserList(){
return $.getJSON('https://randomuser.me/api/')
.then(function(data) {
return data.results;
});
}
render() {
this.UserList().then(function(res){
this.state = {person: res};
});
return (
<div id="layout-content" className="layout-content-wrapper">
<div className="panel-list">
{this.state.person.map((item, i) =>{
return(
<h1>{item.name.first}</h1>
<span>{item.cell}, {item.email}</span>
)
})}
<div>
</div>
)
}
}
In this case, you can do ajax call inside componentDidMount, and then update state
export default class UserList extends React.Component {
constructor(props) {
super(props);
this.state = {person: []};
}
componentDidMount() {
this.UserList();
}
UserList() {
$.getJSON('https://randomuser.me/api/')
.then(({ results }) => this.setState({ person: results }));
}
render() {
const persons = this.state.person.map((item, i) => (
<div>
<h1>{ item.name.first }</h1>
<span>{ item.cell }, { item.email }</span>
</div>
));
return (
<div id="layout-content" className="layout-content-wrapper">
<div className="panel-list">{ persons }</div>
</div>
);
}
}
You may want to check out the Flux Architecture. I also recommend checking out React-Redux Implementation. Put your api calls in your actions. It is much more cleaner than putting it all in the component.
Actions are sort of helper methods that you can call to change your application state or do api calls.
Use fetch method inside componentDidMount to update state:
componentDidMount(){
fetch('https://randomuser.me/api/')
.then(({ results }) => this.setState({ person: results }));
}
This discussion has been for a while and #Alexander T.'s answer provided a good guide to follow for newer of React like me. And I'm going to share some additional know-how about calling the same API multiple times to refresh the component, I think it's probably a common question for beginners.
componentWillReceiveProps(nextProps), from official documentation :
If you need to update the state in response to prop changes (for
example, to reset it), you may compare this.props and nextProps and
perform state transitions using this.setState() in this method.
We could conclude that here is the place we handle props from the parent component, have API calls, and update the state.
Base on #Alexander T.'s example:
export default class UserList extends React.Component {
constructor(props) {
super(props);
this.state = {person: []};
}
componentDidMount() {
//For our first load.
this.UserList(this.props.group); //maybe something like "groupOne"
}
componentWillReceiveProps(nextProps) {
// Assuming parameter comes from url.
// let group = window.location.toString().split("/")[*indexParameterLocated*];
// this.UserList(group);
// Assuming parameter comes from props that from parent component.
let group = nextProps.group; // Maybe something like "groupTwo"
this.UserList(group);
}
UserList(group) {
$.getJSON('https://randomuser.me/api/' + group)
.then(({ results }) => this.setState({ person: results }));
}
render() {
return (...)
}
}
Update
componentWillReceiveProps() will be deprecated.
Here are only some methods (all of them in Doc) in the life cycle I think that they are related to deploying API in the general cases:
By referring to the diagram above:
Deploy API in componentDidMount()
The proper scenario to have API call here is that the content (from the response of API) of this component will be static, componentDidMount() only fire once while the component is mounting, even new props are passed from the parent component or have actions to lead re-rendering.
The component do check difference to re-render but not re-mount.
Quote from doc:
If you need to load data from a remote endpoint, this is a good place to
instantiate the network request.
Deploy API in static getDerivedStateFromProps(nextProps, prevState)
We should notice that there are two kinds of component updating, setState() in current component would not trigger this method but re-rendering or new props from parent component would.
We could find out this method also fires while mounting.
This is a proper place to deploy API if we want to use the current component as a template, and the new parameters to make API calls are props coming from parent component.
We receive a different response from API and return a new state here to change the content of this component.
For example:
We have a dropdown list for different Cars in the parent component, this component needs to show the details of the selected one.
Deploy API in componentDidUpdate(prevProps, prevState)
Different from static getDerivedStateFromProps(), this method is invoked immediately after every rendering except the initial rendering. We could have API calling and render difference in one component.
Extend the previous example:
The component to show Car's details may contain a list of series of this car, if we want to check the 2013 production one, we may click or select or ... the list item to lead a first setState() to reflect this behavior (such as highlighting the list item) in this component, and in the following componentDidUpdate() we send our request with new parameters (state). After getting the response, we setState() again for rendering the different content of the Car details. To prevent the following componentDidUpdate() from causing the infinity loop, we need to compare the state by utilizing prevState at the beginning of this method to decide if we send the API and render the new content.
This method really could be utilized just like static getDerivedStateFromProps() with props, but need to handle the changes of props by utilizing prevProps. And we need to cooperate with componentDidMount() to handle the initial API call.
Quote from doc:
... This is also a good place to do network requests as long as you
compare the current props to previous props ...
I would like you to have a look at redux
http://redux.js.org/index.html
They have very well defined way of handling async calls ie API calls, and instead of using jQuery for API calls, I would like to recommend using fetch or request npm packages, fetch is currently supported by modern browsers, but a shim is also available for server side.
There is also this another amazing package superagent, which has alot many options when making an API request and its very easy to use.
You can also fetch data with hooks in your function components
full example with api call: https://codesandbox.io/s/jvvkoo8pq3
second example: https://jsfiddle.net/bradcypert/jhrt40yv/6/
const Repos = ({user}) => {
const [repos, setRepos] = React.useState([]);
React.useEffect(() => {
const fetchData = async () => {
const response = await axios.get(`https://api.github.com/users/${user}/repos`);
setRepos(response.data);
}
fetchData();
}, []);
return (
<div>
{repos.map(repo =>
<div key={repo.id}>{repo.name}</div>
)}
</div>
);
}
ReactDOM.render(<Repos user="bradcypert" />, document.querySelector("#app"))
1) You can use Fetch API to fetch data from Endd Points:
Example fetching all Github repose for a user
/* Fetch GitHub Repos */
fetchData = () => {
//show progress bar
this.setState({ isLoading: true });
//fetch repos
fetch(`https://api.github.com/users/hiteshsahu/repos`)
.then(response => response.json())
.then(data => {
if (Array.isArray(data)) {
console.log(JSON.stringify(data));
this.setState({ repos: data ,
isLoading: false});
} else {
this.setState({ repos: [],
isLoading: false
});
}
});
};
2) Other Alternative is Axios
Using axios you can cut out the middle step of passing the results of
the http request to the .json() method. Axios just returns the data
object you would expect.
import axios from "axios";
/* Fetch GitHub Repos */
fetchDataWithAxios = () => {
//show progress bar
this.setState({ isLoading: true });
// fetch repos with axios
axios
.get(`https://api.github.com/users/hiteshsahu/repos`)
.then(result => {
console.log(result);
this.setState({
repos: result.data,
isLoading: false
});
})
.catch(error =>
this.setState({
error,
isLoading: false
})
);
}
Now you can choose to fetch data using any of this strategies in componentDidMount
class App extends React.Component {
state = {
repos: [],
isLoading: false
};
componentDidMount() {
this.fetchData ();
}
Meanwhile you can show progress bar while data is loading
{this.state.isLoading && <LinearProgress />}
Render function should be pure, it's mean that it only uses state and props to render, never try to modify the state in render, this usually causes ugly bugs and decreases performance significantly. It's also a good point if you separate data-fetching and render concerns in your React App. I recommend you read this article which explains this idea very well. https://medium.com/#learnreact/container-components-c0e67432e005#.sfydn87nm
This part from React v16 documentation will answer your question, read on about componentDidMount():
componentDidMount()
componentDidMount() is invoked immediately after a component is
mounted. Initialization that requires DOM nodes should go here. If you
need to load data from a remote endpoint, this is a good place to
instantiate the network request. This method is a good place to set up
any subscriptions. If you do that, don’t forget to unsubscribe in
componentWillUnmount().
As you see, componentDidMount is considered the best place and cycle to do the api call, also access the node, means by this time it's safe to do the call, update the view or whatever you could do when document is ready, if you are using jQuery, it should somehow remind you document.ready() function, where you could make sure everything is ready for whatever you want to do in your code...
As an addition/update to Oleksandr T.'s excellent answer:
If you use class components, backend calls should happen in componentDidMount.
If you use hooks instead, you should use the effect hook
For example:
import React, { useState, useEffect } from 'react';
useEffect(() => {
fetchDataFromBackend();
}, []);
// define fetchDataFromBackend() as usual, using Fetch API or similar;
// the result will typically be stored as component state
Further reading:
Using the Effect Hook in the official docs.
How to fetch data with React Hooks? by Robin Wieruch
A clean way is to make an asynchronous API call inside componentDidMount with try/catch function.
When we called an API, we receive a response. Then we apply JSON method on it, to convert the response into a JavaScript object. Then we take from that response object only his child object named "results" (data.results).
In the beginning we defined "userList" in state as an empty array. As soon as we make the API call and receive data from that API, we assign the "results" to userList using setState method.
Inside the render function we tell that userList will be coming from state. Since the userList is an array of objects we map through it, to display a picture, a name and a phone number of each object "user". To retrieve this information we use dot notation (e.g. user.phone).
NOTE: depending on your API, your response may look different. Console.log the whole "response" to see which variables you need from it, and then assign them in setState.
UserList.js
import React, { Component } from "react";
export default class UserList extends Component {
state = {
userList: [], // list is empty in the beginning
error: false
};
componentDidMount() {
this.getUserList(); // function call
}
getUserList = async () => {
try { //try to get data
const response = await fetch("https://randomuser.me/api/");
if (response.ok) { // ckeck if status code is 200
const data = await response.json();
this.setState({ userList: data.results});
} else { this.setState({ error: true }) }
} catch (e) { //code will jump here if there is a network problem
this.setState({ error: true });
}
};
render() {
const { userList, error } = this.state
return (
<div>
{userList.length > 0 && userList.map(user => (
<div key={user}>
<img src={user.picture.medium} alt="user"/>
<div>
<div>{user.name.first}{user.name.last}</div>
<div>{user.phone}</div>
<div>{user.email}</div>
</div>
</div>
))}
{error && <div>Sorry, can not display the data</div>}
</div>
)
}}
As best place and practice for external API calls is React Lifecycle method componentDidMount(), where after the execution of the API call you should update the local state to be triggered new render() method call, then the changes in the updated local state will be applied on the component view.
As other option for initial external data source call in React is pointed the constructor() method of the class. The constructor is the first method executed on initialization of the component object instance. You could see this approach in the documentation examples for Higher-Order Components.
The method componentWillMount() and UNSAFE_componentWillMount() should not be used for external API calls, because they are intended to be deprecated. Here you could see common reasons, why this method will be deprecated.
Anyway you must never use render() method or method directly called from render() as a point for external API call. If you do this your application will be blocked.
You must try "axios" library for API call.
Instead of direct using jQuery.
Thanks.
It would be great to use axios for the api request which supports cancellation, interceptors etc. Along with axios, l use react-redux for state management and redux-saga/redux-thunk for the side effects.

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 do I take a screenshot of a React component without mounting it in the DOM?

My use case is: I have a web site editor and a list of available web pages for users. Each page in this list is represented by a thumbnail. Every time a user makes a change to a page using the editor, the thumbnail of the respective site has to be updated to reflect the change. The way I'm doing is by mounting a ThumbnailSandbox component in the page, passing the props from the Redux store and then using dom-to-png to create the screenshot and use it in the list. But I wanted to do it without mounting the component on the page, because I think it would be a cleaner solution and with less chances of being affected by other interactions happening. So, I created a CodeSanbox to illustrate what I'm trying to achieve.
My logic is this:
import React from "react";
import ReactDOMServer from "react-dom/server";
import html2canvas from "html2canvas";
import MyComp from "./component.jsx";
export const createScrenshot = () => {
const el = (
<div>
test component <MyComp />
</div>
);
const markup = ReactDOMServer.renderToString(el);
let doc = new DOMParser().parseFromString(markup, "text/html");
let target = doc.body.getElementsByClassName("my-comp")[0];
console.log(markup, target);
html2canvas(target, {
useCORS: true,
allowTaint: true,
scale: 1,
width: 500,
height: 500,
x: 0,
y: 0,
logging: true,
windowWidth: 500,
windowHeight: 500
})
.then(function(canvas) {
console.log(">> ", canvas);
})
.catch(error => {
console.log(error);
});
};
So, I'm passing the component to ReactDOM, then creating a DOM node using the string from first step and passing the node to html2canvas. But at this point I get the error Uncaught TypeError: Cannot read property 'pageXOffset' of null. Because the ownerDocument of the element passed to html2canvas is null and it doesn't have the properties: devicePixelRation, innerWidth, innerHeight, pageYOffset, and pageXOffset. As I understand, that's because the node element is not part of the DOM.
Now, my questions are:
1) Is there a way to solve this problem using html2canvas?
2) Is there any other way to take a screenshot of a React component, in the browser, without mounting the component in the DOM?
Thank you in advance!!
For point 1:
Why don't you mount the component and then after your handling delete the component in the ref? (can be done in ComponentDidMount too but ref would come before DidMount) That's the most standard solution to perform downloads (create an a tag do a click and then remove it)
This is a sample untested code using ref call back
export class CreateScrenshot extends React.Component {
constructor() {
super() {
this._reactRef = this._reactRef.bind(this);
this.state = {
removeNode: false
};
}
}
_reactRef(node) {
if(node) {
// your html2Canvas handling and in the returned promise remove the node by
this.setState({removeNode: true});
}
}
render() {
let childComponent = null;
if(!this.state.removeNode) {
{/*pass the ref of the child node to the parent component using the ref callback*/}
childComponent = (
<div>
test component <MyComp refCallBack={this._reactRef}/>
</div>
);
}
return childComponent;
}
}
the limitation however is that this will be asynchronous and might cause a flicker.
So if possible try using a sync library so that the node can be removed on the next render.
For point 2: https://reactjs.org/docs/react-component.html#componentdidmount
From react's componentDidMount() doc section:
"It can, however, be necessary for cases like modals and tooltips when you need to measure a DOM node before rendering something that depends on its size or position."
This makes it clear that you can only get the nodes' measurements after it has been mounted.
Set the react element to z-index and bottom -9999px

Context API consume from anywhere

I am trying to implement a shared state into my application using the React context api.
I am creating an errorContext state at the root of my tree. The error context looks like so:
// ErrorContext.js
import React from 'react';
const ErrorContext = React.createContext({
isError: false,
setError: (error) => {}
});
export default ErrorContext;
Desired Result
I would like to update (consume) this context from anywhere in the app (specifically from within a promise)
Ideally the consume step should be extracted into a exported helper function
Example Usage of helper function
http.get('/blah')
.catch((error) => {
HelperLibrary.setError(true);
})
Following the react context docs:
I can create a provider like so :
class ProviderClass {
state = {
isError: false,
setError: (error) => {
this.state.isError = error;
}
}
render() {
return (
<ErrorContext.Provider value={this.state}>
{this.props.children}
</ErrorContext.Provider>
)
}
}
Then I can consume this provider by using the Consumer wrapper from inside a render call:
<ErrorContext.Consumer>
{(context) => {
context.setError(true);
}}
</ErrorContext.Consumer>
The Problem with this approach
This approach would require every developer on my team to write lots of boilerplate code every-time they wish to handle a web service error.
e.g. They would have to place ErrorContext.Consumer inside the components render() method and render it conditionally depending on the web service response.
What I have tried
Using ReactDOM.render from within a helper function.
const setError = (error) =>{
ReactDOM.render(
<ErrorContext.Consumer>
// boilerplate that i mentioned above
</ErrorContext.Consumer>,
document.getElementById('contextNodeInDOM')
) }
export default setError;
Why doesn't this work?
For some reason ReactDOM.render() always places this code outside the React component tree.
<App>
...
<ProviderClass>
...
<div id="contextNodeInDOM'></div> <-- even though my node is here
...
</ProviderClass>
</App>
<ErrorContext.Consumer></ErrorContext.Consumer> <-- ReactDOM.render puts the content here
Therefore there is no context parent found for the consumer, so it defaults to the default context (which has no state)
From the docs
If there is no Provider for this context above, the value argument
will be equal to the defaultValue that was passed to createContext().
If anyone can assist me on my next step, I am coming from Angular so apologies if my terminology is incorrect or if I am doing something extremely stupid.
You can export a HOC to wrap the error component before export, eliminating the boilerplate and ensuring that the context is provided only where needed, and without messing with the DOM:
// error_context.js(x)
export const withErrorContext = (Component) => {
return (props) => (
<ErrorContext.Consumer>
{context => <Component {...props} errorContext={context} />}
</ErrorContext.Consumer>
)
};
// some_component.js(x)
const SomeComponent = ({ errorContext, ...props }) => {
http.get('/blah')
.catch((error) => {
errorContext.setError(true);
})
return(
<div></div>
)
};
export default withErrorContext(SomeComponent);
Now that React 16.8 has landed you can also do this more cleanly with hooks:
const SomeComponent = props => {
const { setError } = useContext(ErrorContext)
http.get("/blah").catch(() => setError(true))
return <div />
}
Following the react context docs:
I can create a provider like so :
class ProviderClass {
state = {
isError: false,
setError: (error) => {
this.state.isError = error;
}
}
I don't think so - there should be setState used. There is a general rule in react "don't mutate state - use setState()" - abusing causes large part of react issues.
I have a feeling you don't understand context role/usage. This is more like a shortcut to global store eliminating the need of explicitly passing down props to childs through deep components structure (sometimes more than 10 levels).
App > CtxProvider > Router > Other > .. > CtxConsumer > ComponentConsumingCtxStorePropsNMethods
Accessing rendered DOM nodes with id is used in some special cases, generally should be avoided because following renders will destroy any changes made externally.
Use portals if you need to render sth somewhere outside of main react app html node.

Prevent react component from rendering twice when using redux with componentWillMount

I have a React component that dispatches a redux state change in its componentWillMount function. The reason is that when the component is loaded, it needs to get the id from the url (powered by react-router), and trigger an action that sets up the state with that id's data.
Here is the component:
class Editor extends React.Component {
componentWillMount() {
const { dispatch, params } = this.props
dispatch(editItem(params.id))
}
render() {
const item = this.props.item
console.log("Editing", item)
}
}
export default connect(state => ({item: state.item}))(Editor)
Here's the catch: render is getting called twice. item is undefined on the first call, and valid on the second. Ideally, it should only be called once this.props.item actually exists (after the editItem action has been dispatched and run).
According to the React docs: "If you call setState within this method, render() will see the updated state and will be executed only once despite the state change."
In redux, dispatch is the equivalent of calling setState, as it results in a state change. However, I'm guessing something in the way connect works is still causing render to be called twice.
Is there a way around this besides adding a line like if (!item) return; ?
One thing you might do is create a higher order component that handles the basic pattern of loading a different component (or no component) before the required props are loaded.
export const LoaderWrapper = function(hasLoaded, Component, LoaderComponent, onLoad) {
return props => {
if (hasLoaded(props)) {
return <Component {...props} />
}
else {
if (onLoad) onLoad(props)
return { LoaderComponent ? <LoaderComponent /> : null }
}
}
}
Then you can wrap your component before connecting it to get the desired behaviour.
export default connect(state => ({item: state.item}))(LoaderWrapper(
((props) => !!props.item),
Editor,
null,
(props) => props.dispatch(editItem(props.params.id))
))
You might want to add some currying magic to make sure you can compose these kinds of wrapper functions more nicely. Take a look at recompose for more info.
It looks like there's already an issue in the react-redux library.
https://github.com/rackt/react-redux/issues/210
What does editItem do? Does it add item to the redux state or is it there already?
If it is adding I imagine what is happening is that a render cycle happens with the current props, ie item being blank.
Then it gets rendered again when the props have changed, via setting the item.
One approach to fixing this sort of thing is to create a higher order component that wraps Editor and calls the dispatch action the rendering though is set either to a loading screen or and empty div until item is set. That way you can be assured that Editor will have an item.
But without knowing what editItem does it's sort of hard to know. Maybe you could paste the code for that?

Resources