I am using react and redux but I'm getting the following Exception:
foo.js:10 Uncaught Error: findComponentRoot(..., .0.$data.0.0.$22): Unable to find element. This probably means the DOM was unexpectedly mutated (e.g., by the browser), usually due to forgetting a when using tables, nesting tags like <form>, <p>, or <a>, or using non-SVG elements in an parent. Try inspecting the child nodes of the element with React ID
when I press on the button below:
let mapStateToProps = (state) => {
const user = state.user.get("user");
return {
data: state.data.get("data"),
loading: state.pausedAlarms.get("loading"),
userId: user&&user.id
}
};
let mapActionsToProps = (dispatch) => {
return {
getData() {
dispatch(getData());
},
selectUser(user){
dispatch(selectUser(user));
},
};
};
let Foo = React.createClass({
componentWillMount() {
this.props["getData"]();
},
shouldComponentUpdate(nextProps, nextState) {
if(nextProps["userId"] !== this.props["userId"]) {
nextProps["getData"]();
}
return true;
},
render() {
const {
data,
selectUser,
loading
} = this.props;
if (loading) {
return (
<div id="3" key="3">LOADING</div>
);
}
else if (data){
return (
<div id="1" key="1">
<div id="11" key="11">
HEADER
</div>
<button
id="2"
key="22"
onClick={
e => {
selectUser(new User({id:1,name:"Foo"}))
}
}
>Click</button>
</div>
);
}
else{
return null;
}
}
});
Pressing the button dispatches the action selectUser, which updates my redux state with a new value for userId. This causes shouldComponentUpdate to call nextProps["getData"]();. The action getData begins by dispatching such that loading is set to true in the reducer.
The code above renders as expected its just that I'm seeing an exception in the console window.
If I do the following instead:
shouldComponentUpdate(nextProps, nextState) {
if(nextProps["userId"] !== this.props["userId"]) {
nextProps["getData"]();
return false;
}
return true;
},
my code works as expected but without the exception.
What am I doing wrong? How might I debug this problem?
I guess the problem is caused that you are dispatching an action in shouldComponentUpdate.
You can dispatch it in componentWillReceiveProps:
componentWillReceiveProps(nextProps) {
if(nextProps["userId"] !== this.props["userId"]) {
nextProps["getData"]();
}
}
shouldComponentUpdate is intended to determine whether a component should be updated, instead of updating the state. Take a look at the following SO discussion:
Is it OK to call setState from within shouldComponentUpdate?
Update 1
Check if the problem is caused by the render structure. Try to simplify it (remove keys, mouniting/unmounting nodes and so on):
getData() {
return (
<div>
HEADER
<button onClick={ e => { selectUser(new User({id:1,name:"Foo"})) } }>Click</button>
</div>
);
}
render() {
const {
data,
selectUser,
loading
} = this.props;
return (
<div>
{ loading ? 'LOADING' : null }
{ (! loading) && data ? this.getData() : null }
</div>
);
}
Related
I like the way in AngularJS of fetching external data before showing webpage. The data will be sent one by one to the frontend before showing the webpage. We are certain that the website and the data on it is good when we see it.
$stateProvider
.state('kpi', {
url:'/kpi',
templateUrl: '/htmls/kpi.html',
resolve: {
getUser: ['lazy', 'auth', function (lazy, auth) { return auth.getUser() }],
postPromise: ['posts', 'getUser', function (posts, getUser) { return posts.getAll() }],
userPromise: ['users', 'postPromise', function (users, postPromise) { return users.getAll() }],
logs: ['kpiService', 'userPromise', function (kpiService, userPromise) { return kpiService.getLogs() }],
subscribers: ['kpiService', 'logs', function (kpiService, logs) { return kpiService.getSubscribers() }]
},
controller: 'KpiCtrl'
})
Now, I would like to achieve this in ReactJS, I tried:
class Kpi extends React.Component {
state = { logs: [] };
getChartOptions1() {
// this.state.logs is used
}
componentDidMount() {
axios.get(`${BACKEND_URL}/httpOnly/kpi/logs`).then(
logs => {
this.setState({ logs.data });
});
};
render() {
return;
<div>
<HighchartsReact
highcharts={Highcharts}
options={this.getChartOptions1()}
{...this.props}
/>
<div>{JSON.stringify(this.state.logs)}</div>
</div>;
}
}
But it seems that it first called getChartOptions1 with unready data, rendered the webpage, then fetched the external data, then called again getChartOptions1 with ready data, rendered the webpage again.
I don't like the fact that getChartOptions was called twice (first with unready data), and the page was rendered twice.
There are several ways discussed: Hooks, React.Suspense, React.Lazy, etc. Does anyone know what's the standard way of fetching external data before showing the webpage in React?
As suggested in comments, conditional rendering might look like
class Kpi extends React.Component {
state = { logs: [] };
getChartOptions1 () {
// this.state.logs is used
}
componentDidMount() {
axios.get(`${BACKEND_URL}/httpOnly/kpi/logs`).then(
logs => {
this.setState({logs.data});
});
};
render() {
return this.state.logs.length ?
(
<div>
<HighchartsReact highcharts={Highcharts} options={this.getChartOptions1()} {...this.props} />
<div>{JSON.stringify(this.state.logs)}</div>
</div>
)
: (<div>'Loading'</div>);
}
}
but it might be better to start with logs: null, in case the fetch returns an empty array
class Kpi extends React.Component {
state = { logs: null };
getChartOptions1 () {
// this.state.logs is used
}
componentDidMount() {
axios.get(`${BACKEND_URL}/httpOnly/kpi/logs`).then(
logs => {
this.setState({logs.data});
});
};
render() {
return this.state.logs ?
(
<div>
<HighchartsReact highcharts={Highcharts} options={this.getChartOptions1()} {...this.props} />
<div>{JSON.stringify(this.state.logs)}</div>
</div>
)
: (<div>'Loading'</div>);
}
}
Like mentioned above, the answer to your problem is is conditional rendering in the Kpi component. But you can take it one step further and make the conditional render directly on the chartoptions since they are the ones you need at the end.
You could also write Kpi as a functional component and go for a hook-solution. And by doing so de-coupling the data fetching from your Kpi component.
You can also make the hook more generic to handle different endpoints so the hook can be reused in multiple places in your app.
export const useBackendData = (initialEndpoint) => {
const [endpoint, setEndpoint] = useState(initialEndpoint);
const [data, setData] = useState([]);
useEffect(() => {
const fetchData = async () => {
try {
const response = await axios.get(endpoint);
setData(response.data);
}catch(e){
// error handling...
}
}
fetchData();
}, [endpoint])
return [data, setEndpoint]
}
const Kpi = (props) => {
[chartOptions, setChartOptions] = useState(null);
[logs] = useBackendData(`${BACKEND_URL}/httpOnly/kpi/logs`);
const getChartOptions1 = () => {
// do stuff with logs...
setChartOptions([PROCESSED LOG DATA]);
}
useEffect(() => {
if(!!logs.length)
setChartOptions(getChartOptions1())
},[logs]);
return !!chartOptions ? (
<div>
<HighchartsReact highcharts={Highcharts} options={chartOptions} {...props} />
<div>{JSON.stringify(logs)}</div>
</div>) : (
<div>Loading data...</div>
);
}
React lifecycle is like this - first, the render method and then the componentDidMount is called (as per your case) during mounting of components, so you are having this issue.
What I do is show a loader, spinner(anything) till the data is being fetched, and once the data is there, show the actual component. The app needs to do something till it gets the data.
I am using react-apollo to query the graphQL server and able to successfully hydrate the client with the data. As there will be more than a single place I will be querying for the data I am trying to create a container (refactor) to encapsulate the useQuery hook so that it can be used in one place.
First Try ( working as expected )
const HomeContainer = () => {
const { data, error, loading } = useQuery(GET_DATA_QUERY, {
variables: DATA_VARIABLES
});
const [transformedData, setTransformedData] = useState();
useEffect(() => {
if(!!data) {
const transformedData = someTransformationFunc(data);
setTransformedData(...{transformedData});
}
}, [data]);
if (loading) {
return <div>Loading data ...</div>;
}
if (error) {
return <p>Error loading data</p>;
}
if (!data) {
return <p>Not found</p>;
}
return <Home transformedData={transformedData} />;
};
I wanted to encapsulate the ceremony around different stages of the query to a new container ( loading, error state) so that I can reduce code duplication.
First stab at refactoring
The Query container gets passed in the query, variables and the callback. This takes the responsibility of returning different nodes based on the state of the query ( loading, error or when no data comes back ).
const HomeContainer = () => {
const {data, error, loading} = useQuery(GET_DATA_QUERY, {
variables: DATA_VARIABLES
});
const [transformedData, setTransformedData] = useState();
const callback = (data) => {
const transformedData = someTransformationFunc(data);
setTransformedData(...{
transformedData
});
};
return (
<QueryContainer
query={GET_DATA_QUERY}
variables={DATA_VARIABLES}
callback ={callback}
>
<Home transformedData={transformedData} />
</QueryContainer>
)
};
const QueryContainer = ({callback, query, variables, children }) => {
const {data, error, loading } = useQuery(query, {
variables: variables
});
// Once data is updated invoke the callback
// The transformation of the raw data is handled by the child
useEffect(() => {
if (!!data) {
callback(data);
}
}, [data]);
if (loading) {
return <div > Loading data... < /div>;
}
if (error) {
return <p > Error loading data < /p>;
}
if (!data) {
return <p > Not found < /p>;
}
return children;
};
QueryContainer is using useEffect and invokes the callback when data comes back. I felt this is a bit messy and defeats the purpose of encapsulating in the parent and using the callback to talk and update the child.
Third Try ( Using children as function )
Got rid of the callback and passing the data as the first argument to the children function.
const HomeContainer = () => {
return (
<QueryContainer
query={GET_DATA_QUERY}
variables={DATA_VARIABLES}
>
{(data) => {
const transformedData = someTransformationFunc(data);
return <Home transformedData={transformedData} />;
}}
</QueryContainer>
)
};
const QueryContainer = ({ query, variables, children }) => {
const { data, error, loading } = useQuery(query, {
variables: variables
});
if (loading) {
return <div>Loading data ...</div>;
}
if (error) {
return <p>Error loading data</p>;
}
if (!data) {
return <p>Not found</p>;
}
return children(data);
};
I expected this to work as nothing really changed and the new render when the data is updated calls the children as a function with data as argument.
But when I navigate to that route I see a black screen ( no errors and I can see the correct data logged into the console )
If I click the link again I can see the component committed to the DOM.
Not really sure what is going on here and wondering if someone can throw light as to what is going on here.
hmmm, should work ...
Try something like this (component injection, a bit like HOC - inspiration) :
const HomeContainer = () => {
return (
<QueryContainer
query={GET_DATA_QUERY}
variables={DATA_VARIABLES}
transformation={someTransformationFunc}
renderedComponent={Home}
/>
)
};
const QueryContainer = ({ query, variables, transformation, renderedComponent: View }) => {
const { data, error, loading } = useQuery(query, { variables });
if (loading) {
return <div>Loading data ...</div>;
}
if (error) {
return <p>Error loading data</p>;
}
if (!data) {
return <p>Not found</p>;
}
// let transformedData = transformation(data);
// return <View transformedData={transformedData} />;
return <View transformedData={transformation ? transformation(data) : data} />;
};
If still not working (!?), pass both data and transformation as props and use them to initialize state (with useState or useEffect).
Do you really/still need <HomeContainer/> as an abstraction? ;)
The code snippets that I have added above is working as expected in isolation.
https://codesandbox.io/s/weathered-currying-4ohh3
The problem was with some other component down the hierarchy tree that was causing the component not to re render.
The 2nd implementation is working as expected as the component is getting rendered again dud to the callback being invoked from the parent.
I have this application that has a deprecated lifecycle method:
componentWillReceiveProps(nextProps) {
if (this.state.displayErrors) {
this._validate(nextProps);
}
}
Currently, I have used the UNSAFE_ flag:
UNSAFE_componentWillReceiveProps(nextProps) {
if (this.state.displayErrors) {
this._validate(nextProps);
}
}
I have left it like this because when I attempted to refactor it to:
componentDidUpdate(prevProps, prevState) {
if (this.state.displayErrors) {
this._validate(prevProps, prevState);
}
}
It created another bug that gave me this error:
Invariant Violation: Maximum update depth exceeded. This can happen
when a component repeatedly calls setState inside componentWillUpdate
or componentDidUpdate. React limits the number of nested updates to
prevent infinite loops.
It starts to happen when a user clicks on the PAY NOW button that kicks off the _handlePayButtonPress which also checks for validation of credit card information like so:
UNSAFE_componentWillReceiveProps(nextProps) {
if (this.state.displayErrors) {
this._validate(nextProps);
}
}
_validate = props => {
const { cardExpireDate, cardNumber, csv, nameOnCard } = props;
const validationErrors = {
date: cardExpireDate.trim() ? "" : "Is Required",
cardNumber: cardNumber.trim() ? "" : "Is Required",
csv: csv.trim() ? "" : "Is Required",
name: nameOnCard.trim() ? "" : "Is Required"
};
if (validationErrors.csv === "" && csv.trim().length < 3) {
validationErrors.csv = "Must be 3 or 4 digits";
}
const fullErrors = {
...validationErrors,
...this.props.validationErrors
};
const isValid = Object.keys(fullErrors).reduce((acc, curr) => {
if (fullErrors[curr]) {
return false;
}
return acc;
}, true);
if (isValid) {
this.setState({ validationErrors: {} });
//register
} else {
this.setState({ validationErrors, displayErrors: true });
}
return isValid;
};
_handlePayButtonPress = () => {
const isValid = this._validate(this.props);
if (isValid) {
console.log("Good to go!");
}
if (isValid) {
this.setState({ processingPayment: true });
this.props
.submitEventRegistration()
.then(() => {
this.setState({ processingPayment: false });
//eslint-disable-next-line
this.props.navigation.navigate("PaymentConfirmation");
})
.catch(({ title, message }) => {
Alert.alert(
title,
message,
[
{
text: "OK",
onPress: () => {
this.setState({ processingPayment: false });
}
}
],
{
cancelable: false
}
);
});
} else {
alert("Please correct the errors before continuing.");
}
};
Unfortunately, I do not have enough experience with Hooks and I have failed at refactoring that deprecated lifecycle method to one that would not create trouble like it was doing with the above error. Any suggestions at a better CDU or any other ideas?
You need another check so you don't get in an infinite loop (every time you call setState you will rerender -> component did update -> update again ...)
You could do something like this:
componentDidUpdate(prevProps, prevState) {
if (this.state.displayErrors && prevProps !== this.props) {
this._validate(prevProps, prevState);
}
}
Also I think that you need to call your validate with new props and state:
this._validate(this.props, this.state);
Hope this helps.
componentDidUpdate shouldn't replace componentWillRecieveProps for this reason. The replacement React gave us was getDerivedStateFromProps which you can read about here https://medium.com/#baphemot/understanding-react-react-16-3-component-life-cycle-23129bc7a705. However, getDerivedStateFromProps is a static function so you'll have to replace all the setState lines in _validate and return an object instead.
This is how you work with prevState and hooks.
Working sample Codesandbox.io
import React, { useState, useEffect } from "react";
import "./styles.css";
const ZeroToTen = ({ value }) => {
const [myValue, setMyValue] = useState(0);
const [isValid, setIsValid] = useState(true);
const validate = value => {
var result = value >= 0 && value <= 10;
setIsValid(result);
return result;
};
useEffect(() => {
setMyValue(prevState => (validate(value) ? value : prevState));
}, [value]);
return (
<>
<span>{myValue}</span>
<p>
{isValid
? `${value} Is Valid`
: `${value} is Invalid, last good value is ${myValue}`}
</p>
</>
);
};
export default function App() {
const [value, setValue] = useState(0);
return (
<div className="App">
<button value={value} onClick={e => setValue(prevState => prevState - 1)}>
Decrement
</button>
<button value={value} onClick={e => setValue(prevState => prevState + 1)}>
Increment
</button>
<p>Current Value: {value}</p>
<ZeroToTen value={value} />
</div>
);
}
We have two components, one to increase/decrease a number and the other one to hold a number between 0 and 10.
The first component is using prevState to increment the value like this:
onClick={e => setValue(prevState => prevState - 1)}
It can increment/decrement as much as you want.
The second component is receiving its input from the first component, but it will validate the value every time it is updated and will allow values between 0 and 10.
useEffect(() => {
setMyValue(prevState => (validate(value) ? value : prevState));
}, [value]);
In this case I'm using two hooks to trigger the validation every time 'value' is updated.
If you are not familiar with hooks yet, this may be confusing, but the main idea is that with hooks you need to focus on a single property/state to validate changes.
I'm sure I'm doing something silly here. I'm trying to write a component which, based on the fetching state of the data, renders one of 3 things.
1) An error message if the fetch has errored.
2) A loading message if the data is fetching.
3) The full child component if the data is fetched.
What's happening now is the fetch succeeds, but the Loading message won't disappear. In the code example below, you can see that I've added a console.log for whether or not the full render should occur. What's perplexing me is that this will eventually log as true, but the Loading message still shows.
This exact pattern seems to work for a sibling component in this app, so I'm confused as to what's missing here... Here's the entire component, it's been anonymized and stripped of styles, but the structure is otherwise identical.
function mapStateToProps(state) {
return {
Data: state.NewDataReducer,
};
}
const mapDispatchToProps = {
fetchNewData: ActionCreators.fetchNewData,
};
const DataError = () => (
<div>
Error fetching new data!
</div>
);
const DataLoading = () => (
<div>
Loading..
</div>
);
class MainContainer extends PureComponent {
static propTypes = {
choice: PropTypes.string,
fetchNewData: PropTypes.func,
Data: ImmutablePropTypes.map.isRequired,
}
state = {
choice: null,
}
componentDidUpdate(nextProps) {
const { choice, fetchNewData, Data } = this.props;
if(!choice) {
return;
}
if(isFetching(Data)) {
return;
}
const newChoiceSelected = choice !== nextProps.choice;
if(newChoiceSelected) {
fetchNewData({choice});
}
}
handleChangeChoice = (choice) => {
this.setState({
choice: { choice }
});
}
render() {
const { choice, Data } = this.props;
const error = hasFetchError(Data);
const loading = !error && !isFetched(Data);
const renderFull = !loading && !error;
console.log(renderFull);
if(!renderFull) {
return (
<div>
Please wait.
</div>
);
}
const { dataBreakdown } = Data.get("dataKey").toJS();
return (
<div>
<ChildComponent
choice={choice}
dataBreakdown={dataBreakdown}
onSetDrillDown={this.handleChangeChoice}
/>
</div>
);
}
}
export default connect(
mapStateToProps,
mapDispatchToProps,
)(MainContainer);
You need to pass your map function the arguments of what it needs to map over from immutable data types. Map creates a new a array and makes a callback function on the items that it iterates through in that array. This is just an example
Data: ImmutabaleProptypes.map((ing, index)=>(
<li key={index}>{Data}</li> // you can write jsx in here.
));
I want to be able to trigger a load animation in React and perform a GET request, and then stop the loading animation after the get request completed. This is how I laid out my code.
export default class Dialog extends Component {
state = {
domIdx: 0
}
loadData = () => {
this.setState({
domIdex: 1
}, $.getJSON('http://google.com', () => {
this.setState({
domIdx: 2
})
})
}
render() {
let arr = [<UploadFile/>, <LoadAnimation/>, <Done/>]
return (
<div>
{arr[this.state.domIdx]}
</div>
)
}
}
However, the above code is not working after the loading animation is triggered. The loading animation is shown, the GET request is completed, but the view doesn't change after calling setState again.
How can I achieve the intended action?
In your component state, you can define a loading piece of state:
state = { loading: false }
Then, when you make your request, flip it to to true:
loadData = () => {
this.setState({
loading: true
}, $.getJSON('http://google.com', () => {
this.setState({
loading: false
})
})
}
Then in your render:
render() {
return (
<div>
{this.state.loading ? (<SomeSpinner />):(<Completed />)}
</div>
)
}