I need to check whether some props (from redux store) is an empty object or not. If it is empty, I want the page to redirect to another page and not bother to call render().
The current flow is something like:
constructor(props) {
this.checkObject();
}
checkObject() {
if (Object.keys(someObj).length === 0 && someObj.constructor === Object) {
this.props.history.push("/some-other-route");
}
}
render() {
// some code
}
However, when I do a console.log, render() is being called after checkObject() which causes some errors because render() needs a non-empty object to display content properly. That's the reason I don't want react to even call render() if the object is empty (which I check through checkObject()) and just redirect to another page.
So is there a lifecycle method to use that will execute my redirection code before render() is called?
You could use the Redirect component of react-router within render.
import { Redirect } from 'react-router'
render(){
(checkIfObjectEmpty)?<Redirect to = '/some_route'/>:<JSX>
}
Return inside render
constructor(props) {
this.checkObject();
}
checkObject() {
return Object.keys(someObj).length === 0 && someObj.constructor === Object
}
render() {
if(this.checkObject()) return <Redirect to=/some-other-route" />
//rest of code not run if object is empty
}
Update:
Add super(props) to your constructor. I think it should solve the problem. In this case, no need to componentWillMount(). Your code should work just fine.
Unsafe and temporary solution:
You can use componentWillMount().
The first true life cycle method called is componentWillMount(). This method is only called one time, which is before the initial render.
constructor(props) {
}
checkObject() {
if (Object.keys(someObj).length === 0 && someObj.constructor === Object) {
this.props.history.push("/some-other-route");
}
}
componentWillMount() {
this.checkObject();
}
render() {
// some code
}
history.push() is a side effect that won't prevent the component to be initially rendered, so it belongs to componentDidMount.
Since the result depends on props, the check could go to getDerivedStateFromProps to provide redirect flag in a component with local state. In a component that is connected to Redux it can be performed in mapStateToProps:
connect(({ someObj }) => ({ redirect: !Object.keys(someObj) }))(...)
The component shouldn't be rendered if it will redirect:
componentDidMount() {
if (this.props.redirect)
this.props.history.push("/some-other-route");
}
render() {
return !this.props.redirect && (
...
);
}
As another answer correctly mentions, <Redirect> component is a declarative alternative to calling history.push() directly.
Related
I am trying to render my state which updates by pulling data from an api so the initial value of the state is undefined. I am receiving the error TypeError: undefined is not an object (evaluating 'this.state.Pods[0].Name'). From what I understand about react, every time the state is updated the components that use that state re-renders, so the text that initially was undefined will then render the updated state value. My question is could you bypass the initial error from being thrown so it can render the value of state when its updated. Here's how im calling the state:
<Text>{this.state.Pods[0].Name}</Text>
Ive tried using getDerivedStateFromError but I it isn't working for me.
static getDerivedStateFromError(error) {
return { hasError: true };
}
render() {
if (this.state.hasError) {
return <Text>Something went Wrong</Text>
}
Well, verify it before you do anything.
So verify if this.state.Pods is there and this.state.Pods[0] is there.
Having said that, directly accessing an index and then only using that single one, begs the question, why would you need an array?
So, you can first verify if this.state.hasError is true, then return the error message, if it's not true, and you don't have the pods yet, then return null, otherwise return your actual render, like:
render() {
const { hasError, Pods = [] } = this.state;
if (hasError) {
return <div>Error occured</div>;
}
if (Array.isArray(Pods) || !Pods[0]) {
return null;
}
return <Text>{Pods[0].Name}</Text>;
}
When the state gets updated, your component will rerender and it will again check all the conditions, giving you either an error, again null (if Pods is not there yet or not what you expect it to be) or it will render your text element
Please use this
render() {
if (!this.state.Pods || !this.state.Pods[0] ) {
return <Text>Loading</Text>
}
That is not the correct way of using error boundary component. if you are expected
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return { hasError: true };
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
In your component.
render() {
<ErrorBoundary>
<Text>{this.state.Pods[0].Name}</Text>
</ErrorBoundary>
}
though you don't need error boundry in your case.
you should read your state safely as suggested in other answers.
You have to update your code as below.
<Text>{Array.isArray(this.state.Pods) && this.state.Pods.length > 0 && this.state.Pods[0].Name}</Text>
Hope this will work for you!
Try using constructor or instance variable like:
class A extends React.Component {
state = {
Pods: []
}
}
OR
class A extends React.Component {
constructor() {
this.state = {
Pods: []
}
}
}
This way your state will have a value intially, now inside render you can check if your data from the API has arrived:
render(){
if (this.state.Pods.length) {
return <div>{this.state.Pods}</div>
}
return ''
}
I have a complete running code, but it have a flaw. It is calling setState() from inside a render().
So, react throws the anti-pattern warning.
Cannot update during an existing state transition (such as within render or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to componentWillMount
My logic is like this. In index.js parent component, i have code as below. The constructor() calls the graphs() with initial value, to display a graph. The user also have a form to specify the new value and submit the form. It runs the graphs() again with the new value and re-renders the graph.
import React, { Component } from 'react';
import FormComponent from './FormComponent';
import PieGraph from './PieGraph';
const initialval = '8998998998';
class Dist extends Component {
constructor() {
this.state = {
checkData: true,
theData: ''
};
this.graphs(initialval);
}
componentWillReceiveProps(nextProps) {
if (this.props.cost !== nextProps.cost) {
this.setState({
checkData: true
});
}
}
graphs(val) {
//Calls a redux action creator and goes through the redux process
this.props.init(val);
}
render() {
if (this.props.cost.length && this.state.checkData) {
const tmp = this.props.cost;
//some calculations
....
....
this.setState({
theData: tmp,
checkData: false
});
}
return (
<div>
<FormComponent onGpChange={recData => this.graphs(recData)} />
<PieGraph theData={this.state.theData} />
</div>
);
}
}
The FormComponent is an ordinary form with input field and a submit button like below. It sends the callback function to the Parent component, which triggers the graphs() and also componentWillReceiveProps.
handleFormSubmit = (e) => {
this.props.onGpChange(this.state.value);
e.preventdefaults();
}
The code is all working fine. Is there a better way to do it ? Without doing setState in render() ?
Never do setState in render. The reason you are not supposed to do that because for every setState your component will re render so doing setState in render will lead to infinite loop, which is not recommended.
checkData boolean variable is not needed. You can directly compare previous cost and current cost in componentWillReceiveProps, if they are not equal then assign cost to theData using setState. Refer below updated solution.
Also start using shouldComponentUpdate menthod in all statefull components to avoid unnecessary re-renderings. This is one best pratice and recommended method in every statefull component.
import React, { Component } from 'react';
import FormComponent from './FormComponent';
import PieGraph from './PieGraph';
const initialval = '8998998998';
class Dist extends Component {
constructor() {
this.state = {
theData: ''
};
this.graphs(initialval);
}
componentWillReceiveProps(nextProps) {
if (this.props.cost != nextProps.cost) {
this.setState({
theData: this.props.cost
});
}
}
shouldComponentUpdate(nextProps, nextState){
if(nextProps.cost !== this.props.cost){
return true;
}
return false;
}
graphs(val) {
//Calls a redux action creator and goes through the redux process
this.props.init(val);
}
render() {
return (
<div>
<FormComponent onGpChange={recData => this.graphs(recData)} />
{this.state.theData !== "" && <PieGraph theData={this.state.theData} />}
</div>
);
}
}
PS:- The above solution is for version React v15.
You should not use componentWillReceiveProps because in most recent versions it's UNSAFE and it won't work well with async rendering coming for React.
There are other ways!
static getDerivedStateFromProps(props, state)
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.
So in your case
...component code
static getDerivedStateFromProps(props,state) {
if (this.props.cost == nextProps.cost) {
// null means no update to state
return null;
}
// return object to update the state
return { theData: this.props.cost };
}
... rest of code
You can also use memoization but in your case it's up to you to decide.
The link has one example where you can achieve the same result with memoization and getDerivedStateFromProps
For example updating a list (searching) after a prop changed
You could go from this:
static getDerivedStateFromProps(props, state) {
// Re-run the filter whenever the list array or filter text change.
// Note we need to store prevPropsList and prevFilterText to detect changes.
if (
props.list !== state.prevPropsList ||
state.prevFilterText !== state.filterText
) {
return {
prevPropsList: props.list,
prevFilterText: state.filterText,
filteredList: props.list.filter(item => item.text.includes(state.filterText))
};
}
return null;
}
to this:
import memoize from "memoize-one";
class Example extends Component {
// State only needs to hold the current filter text value:
state = { filterText: "" };
// Re-run the filter whenever the list array or filter text changes:
filter = memoize(
(list, filterText) => list.filter(item => item.text.includes(filterText))
);
handleChange = event => {
this.setState({ filterText: event.target.value });
};
render() {
// Calculate the latest filtered list. If these arguments haven't changed
// since the last render, `memoize-one` will reuse the last return value.
const filteredList = this.filter(this.props.list, this.state.filterText);
return (
<Fragment>
<input onChange={this.handleChange} value={this.state.filterText} />
<ul>{filteredList.map(item => <li key={item.id}>{item.text}</li>)}</ul>
</Fragment>
);
}
}
I'm creating a hackernews-clone using this API
This is my component structure
-main
|--menubar
|--articles
|--searchbar
Below is the code block which I use to fetch the data from external API.
componentWillReceiveProps({search}){
console.log(search);
}
componentDidMount() {
this.fetchdata('story');
}
fetchdata(type = '', search_tag = ''){
var url = 'https://hn.algolia.com/api/v1/search?tags=';
fetch(`${url}${type}&query=${search_tag}`)
.then(res => res.json())
.then(data => {
this.props.getData(data.hits);
});
}
I'm making the API call in componentDidMount() lifecycle method(as it should be) and getting the data correctly on startup.
But here I need to pass a search value through searchbar component to menubar component to do a custom search. As I'm using only react (not using redux atm) I'm passing it as a prop to the menubar component.
As the mentioned codeblock if I search react and passed it through props, it logs react once (as I'm calling it on componentWillReceiveProps()). But if I run fetchData method inside componentWillReceiveProps with search parameter I receive it goes an infinite loop. And it goes an infinite loop even before I pass the search value as a prop.
So here, how can I call fetchdata() method with updating props ?
I've already read this stackoverflow answers but making an API call in componentWillReceiveProps doesn't work.
So where should I call the fetchdata() in my case ? Is this because of asynchronous ?
Update : codepen for the project
You can do it by
componentWillReceiveProps({search}){
if (search !== this.props.search) {
this.fetchdata(search);
}
}
but I think the right way would be to do it in componentDidUpdate as react docs say
This is also a good place to do network requests as long as you compare the current props to previous props (e.g. a network request may not be necessary if the props have not changed).
componentDidMount() {
this.fetchdata('story');
}
componentDidUpdate(prevProps) {
if (this.props.search !== prevProps.search) {
this.fetchdata(this.props.search);
}
}
Why not just do this by composition and handle the data fetching in the main HoC (higher order component).
For example:
class SearchBar extends React.Component {
handleInput(event) {
const searchValue = event.target.value;
this.props.onChange(searchValue);
}
render() {
return <input type="text" onChange={this.handleInput} />;
}
}
class Main extends React.Component {
constructor() {
this.state = {
hits: []
};
}
componentDidMount() {
this.fetchdata('story');
}
fetchdata(type = '', search_tag = '') {
var url = 'https://hn.algolia.com/api/v1/search?tags=';
fetch(`${url}${type}&query=${search_tag}`)
.then(res => res.json())
.then(data => {
this.setState({ hits: data.hits });
});
}
render() {
return (
<div>
<MenuBar />
<SearchBar onChange={this.fetchdata} />
<Articles data={this.state.hits} />
</div>
);
}
}
Have the fetchdata function in the main component and pass it to the SearchBar component as a onChange function which will be called when the search bar input will change (or a search button get pressed).
What do you think?
Could it be that inside this.props.getData() you change a state value, which is ultimately passed on as a prop? This would then cause the componentWillReceiveProps function to be re-called.
You can probably overcome this issue by checking if the search prop has changed in componentWillReceiveProps:
componentWillReceiveProps ({search}) {
if (search !== this.props.search) {
this.fetchdata(search);
}
}
I'm having the following class that renders users based on a sort dropdown. Users will be listed in alphabetical order if i choose "alphabetical" and in group order when i choose "group".
render(){
return(
const {members, sort} = this.state
{ sort === "alphabetical" && <SortByAlphabet members={members} /> }
{ sort === "group" && <SortByGroup members={members}/> }
)
)
In <SortByAlphabet /> component I am setting a component state object from props.members in componentWillReceiveProps() function.
componentWillReceiveProps = props => {
this.setState({ members : props.members });
}
When I choose "group" sort, <SortByAlphabet /> component is unmounting and <SortByGroup /> is mounting in the DOM. Again when i switch back to "alphabetical" sort, the state variable (members) that was set previosly in <SortByAlphabet /> component becomes NULL because the component was removed from the DOM.
componentWillReceiveProps function is not triggering the second time when re-rendering <SortByAlphabet /> b'coz the props didn't change. But i want to update the state variable like i did it for the first time in componentWillReceiveProps function.
How to do that?
componentWillMount is called only once in the component lifecycle, immediately before the component is rendered. It is usually used to perform any state changes needed before the initial render, because calling this.setState in this method will not trigger an additional render
So you can update your staate using
componentWillMount ()
{
this.setState({ members : props.members });
}
As #Vikram also said, componentWillReceiveProps is not called for the first time, so when your component is initially mounted your state is not getting set, so you need to set the state with props in the componentWillMount/componentDidMount function(which are called only on the first render) also along with the componentWillReceiveProps function
componentWillReceiveProps = props => {
if(props.members !== this.props.members) {
this.setState({ members : props.members });
}
}
componentWillMount() {
this.setState({ members : this.props.members });
}
From version 16.3.0 onwards, you would make use of getDerivedStateFromProps method to update the state in response to props change,
getDerivedStateFromProps is invoked after a component is instantiated
as well as when it receives new props. It should return an object to
update state, or null to indicate that the new props do not require
any state updates.
static getDerivedStateFromProps(nextProps, prevState) {
if(nextProps.members !== prevState.memebers) {
return { members: nextProps.members };
}
return null;
}
EDIT:
There has been a change in getDerivedStateFromProps API from v16.4 where it receives props, state as arguments and is called on every update along with initial render. In such a case, you can either trigger a new mount of the component by changing the key
<SortByAlphabet key={members} />
and in SortByAlphabet have
componentWillMount() {
this.setState({ members : this.props.members });
}
or use getDerivedStateFromProps like
static getDerivedStateFromProps(props, state) {
if(state.prevMembers !== props.members) {
return { members: nextProps.members, prevMembers: props.members };
}
return { prevMembers: props.members };
}
I want to call a promise based function before dispatching an action to the store.
The problem is that I only want to call the function when the component is going to be displayed. I use a toggle action that turns the component on and off.
Here is a sample of my code:
if ( /*component is going to be displayed*/) {
init().then(function() {
store.dispatch(toggleSomething());
});
}
else {
store.dispatch(toggleSomething());
}
Action:
export const SomethingActions = {
TOGGLE_SOMETHING: 'TOGGLE_SOMETHING'
};
export function toggleSomething() {
return {
type: SomethingActions.TOGGLE_SOMETHING
};
}
Reducer:
export default function somethingState(state = defaultState, action) {
switch (action.type) {
case somethingActions.TOGGLE_SOMETHING
return Object.assign({}, state, { open: !state.open});
default:
return state;
}
}
part of the React component:
Something.propTypes = {
display: React.PropTypes.bool.isRequired
};
function mapStateToProps(state, ownProps) {
return {
display: state.something.open
};
}
I basically want to know the value of open/display of the component above or another way to know whether the component is being displayed or not.
I don't want to pollute the render function or store a bool that changes every time I call dispatch.
Is there a way to do that?
By the sounds of it, you'd want to take advantage of React's lifecycle methods. Particularly the componentWillMount and componentWillReceiveProps.
componentWillReceiveProps does not get triggered for the initial render, so you may want to extract out the logic into a separate function so that it can be reused for both hooks:
function trigger(isDisplayed) {
if (isDisplayed) {
init().then(function() {
store.dispatch(toggleSomething());
});
}
else {
store.dispatch(toggleSomething());
}
}
componentWillMount() {
trigger(this.props.display);
}
componentWillReceiveProps(nextProps) {
trigger(nextProps.display);
}
Q1: "The problem is that I only want to call the function when the component is going to be displayed"
A1: This is definitely a problem for react lifecycle methods, in particular, componentWillMount() & componentDidMount()
Q2: "I basically want to know the value of open/display of the component above or another way to know whether the component is being displayed or not."
A2: The componentDidMount() method will be called when the component is rendered. To prevent an infinite loop where the component calls your promise on render just to call the promise again when the state changes, avoid including the toggled state in your component. Dispatch actions on component mounting that toggle the state in the store, but don't use this state in this component. This way you know whether the component is rendered without having the UI update. I hope that helps!
import React from 'react';
class StackOverFlow extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
toggleSomethingOn();
}
componentWillUnmount() {
toggleSomethingOff();
}
render() {
return (
<div>
The component has been rendered!
<br />
</div>
);
}
}
function toggleSomethingOn() {
//dispatches action to toggle state "open"
}
function toggleSomethingOff() {
//dispatches action to toggle state "closed"
}
export default StackOverFlow;
A2: If you are just looking to find out if a component has been rendered (outside of your code) you could go to your browser's developer tools and search the elements/DOM for your component html.