I can see my data the first time I navigate to a different route via a link:
<Link to={{ pathname: `/settings/${door._id}` }}>
Inställningar
And it works fine, but when I refresh the page my application tries to use data that haven’t been fetched yet, therefor it crashes. I tried to add a loading screen for my reducer, but that didn’t do the trick.
Here’s my reducer I created a gist for it: https://gist.github.com/Martinnord/c944f4fd22f4a4ff7e3e0b362e7d4e29
I also reached out to some forums and they gave me a tip to go to the very root of my app before ReactDOM.render and in there, connect to redux state and have an item called dataIsLoading or something and if dataIsLoading is true I will show a loading thing and if it’s not it renders all my components. Because otherwise I will run into this issue where the selector depends on data in redux, and I can't control when mapStateToProps runs outside of just not rendering that component at all.
But the problem is that I have absolutely no idea how to do that, I’ve been trying for hours with no luck. If someone could give me any guidance or tips I would be blessed. And if more code from me is needed, please just let me know.
Thanks for reading.
Update
Got a request from the comments so I will add some code.
mapStateToProps
const mapStateToProps = (state, ownProps, loading, fetchDoorsReducer) => {
const door = getDoorById(state, ownProps.match.params.itemId)
const doorId = ownProps.match.params.itemId
const controller = getControllerById(state, door.controller)
return {
doors: door,
controllers: controller,
isLoading: state.fetchDoors.isLoading
}
}
How I use isLoading: if (this.props.isLoading) return <p>laddar</p>
It's a bit unclear what piece of the functionality you describe you're having difficulty translating to code. But in case this is it, here's an example that might help you;
LoadingWrapper = React.createClass({
render() {
if (this.props.isLoading) {
return <SomeLoadingIconHere/>;
} else {
return this.props.children;
}
}
});
and then elsewhere...
render() {
return <LoadingWrapper isLoading={this.props.somePropertyFromRedux}>
<SomeComponent/>
</LoadingWrapper>;
}
Related
After building the homepage of my website I finally figured out how to dynamically navigate to other pages. I wanted the browser to render the State homepage when a user clicked on a dropdown and selected a state. The navigation works, but it re-renders the component 50 times which I do not understand. I suspect it is due to the map function that is creating the menuitems. I could build out 50 individual menuitems but that is really ugly.
I am just starting out learning React. I have 7 YRS experience in backend development, but I am still trying to get a handle on React development. I have created a wepage with Material UI that has a dropdown that looks like this
<FormControl>
<InputLabel>Select a State</InputLabel>
<Select value={location} onChange={selectionChangeHandler}>
{locations.map((value) => (
<MenuItem value={value.toLowerCase()} key={value.toLowerCase()} component={Link} to={`${value.toLowerCase()}/home`} >
{value}
</MenuItem>
))}
</Select>
</FormControl>
This returns a dropdown with the 50 states in it. When the user clicks on a state I want the program to route to that page on click. This dynamic routing works BUT. It re-renders my component 50 times. I think this is happening because the dropdown is being built inside of a .map functions and there are 50 entries in that list.
I can remove the map function and hardcode in 50 menuitems but that is ugly.
Here is my onChange Function
const selectionChangeHandler = (event) => {
console.log(event.target.value)
}
I have also tried removing the component={Link} and using the useNavigate hook in the selectionChangeHandler like so
const selectionChangeHandler = (event) => {
console.log(event.target.value)
setlocation(event.target.value)
link = `${event.target.value}/home`
navigate(link)
}
This works but also renders 50 times. I am at a loss.
I cross posted the above to reddit and then I researched a little more. It turns out in React. When a parent component's state is updated it re-renders all child components. This may be what is going on here, but I do not know how to fix it. Even if I pass the state as a prop to the child component I still have to link to the correct page.
I am kind of nervous about posting I really tried to put work into solving my own problem before reaching out for help, and I might be reaching out for help a lot as I learn. I am committed to learning, but some problems I just cannot figure out on my own.
Link to Code Link to Code
The problem here is inside StateHome.js. You use a naked axios.get call directly in your component so it's going to re-render anytime the component changes from state change.
When you get the results you set state inside the same component which re-renders the component, which then re-runs the axios.get call, causing the loop.
I understand you want to call that endpoint when someone lands on the /alabama/home page. To tell React "Do this one time when the component loads" you must use a useEffect with an empty dependency array. So instead of this:
const StateHome = () => {
const { location } = useParams();
const [PageData, SetPageData] = useState();
axios.get(`http://localhost:4000/${location}/home`).then((response) => {
console.log(response.data);
console.log(response.status);
console.log(response.statusText);
console.log(response.headers);
console.log(response.config);
SetPageData(response.data);
});
return <h1>This is my State Home Page Component for State {location}</h1>;
};
You need to use this:
const StateHome = () => {
console.log("StateHome");
const { location } = useParams();
const [PageData, SetPageData] = useState();
useEffect(() => {
axios.get(`http://localhost:4000/${location}/home`).then((response) => {
console.log(response.data);
console.log(response.status);
console.log(response.statusText);
console.log(response.headers);
console.log(response.config);
SetPageData(response.data);
});
}, []); // <--- indicates "on mount", loads once regadless of other side-effects (setState)
return <h1>This is my State Home Page Component for State {location}</h1>;
};
I am trying to learn React by building a web application. Since I want to learn it step by step, for now I don't use Redux, I use only the React state and I have an issue.
This is my components architecture:
App.js
|
_________|_________
| |
Main.js Side.js
| |
Game.js Moves.js
As you can see, I have the main file called App.js, in the left side we have the Main.js which is the central part of the application which contains Game.js where actually my game is happening. On the right side we have Side.js which is the sidebar where I want to display the moves each player does in the game. They will be displayed in Moves.js.
To be more clear think at the chess game. In the left part you actually play the game and in the right part your moves will be listed.
Now I will show you my code and explain what the problem is.
// App.js
const App = React.memo(props => {
let [moveList, setMovesList] = useState([]);
return (
<React.Fragment>
<div className="col-8">
<Main setMovesList={setMovesList} />
</div>
<div className="col-4">
<Side moveList={moveList} />
</div>
</React.Fragment>
);
});
// Main.js
const Main = React.memo(props => {
return (
<React.Fragment>
<Game setMovesList={props.setMovesList} />
</React.Fragment>
);
});
// Game.js
const Game= React.memo(props => {
useEffect(() => {
function executeMove(e) {
props.setMovesList(e.target);
}
document.getElementById('board').addEventListener('click', executeMove, false);
return () => {
document.getElementById('board').removeEventListener('click', executeMove, false);
};
})
return (
// render the game board
);
});
// Side.js
const Side= React.memo(props => {
return (
<React.Fragment>
<Moves moveList={props.moveList} />
</React.Fragment>
);
});
// Moves.js
const Moves= React.memo(props => {
let [listItems, setListItems] = useState([]);
useEffect(() => {
let items = [];
for (let i = 0; i < props.moveList.length; i++) {
items.push(<div key={i+1}><div>{i+1}</div><div>{props.moveList[i]}</div></div>)
}
setListItems(items);
return () => {
console.log('why this is being triggered on each move?')
};
}, [props.moveList]);
return (
<React.Fragment>
{listItems}
</React.Fragment>
);
});
As you can see on my code, I have defined the state in App.js. On the left side I pass the function which updates the state based on the moves the player does. On the right side I pass the state in order to update the view.
My problem is that on each click event inside Game.js the component Moves.js unmounts and that console.log is being triggered and I wasn't expected it to behave like that. I was expecting that it will unmount only when I change a view to another.
Any idea why this is happening ? Feel free to ask me anything if what I wrote does not make sense.
Thanks for explaining your question so well - it was really easy to understand.
Now, the thing is, your component isn't actually unmounting. You've passed props.movesList as a dependency for the usEffect. Now the first time your useEffect is triggered, it will set up the return statement. The next time the useEffect gets triggered due to a change in props.movesList, the return statement will get executed.
If you intend to execute something on unmount of a component - shift it to another useEffect with an empty dependency array.
answering your question
The answer to your question
"why this is being triggered on each move"
would be:
"because useEffect wants to update the component with the changed state"
But I would be inclined to say:
"you should not ask this question, you should not care"
understanding useEffect
You should understand useEffect as something that makes sure the state is up to date, not as a kind of lifecycle hook.
Imagine for a moment that useEffect gets called all the time, over and over again, just to make sure everything is up to date. This is not true, but this mental model might help to understand.
You don't care if and when useEffect gets called, you only care about if the state is correct.
The function returned from useEffect should clean up its own stuff (e.g. the eventlisteners), again, making sure everything is clean and up to date, but it is not a onUnmount handler.
understanding React hooks
You should get used to the idea that every functional component and every hook is called over and over again. React decides if it might not be necessary.
If you really have performance problems, you might use e.g. React.memo and useCallback, but even then, do not rely on that anything is not called anymore.
React might call your function anyway, if it thinks it is necessary. Use React.memo only as kind of a hint to react to do some optimization here.
more React tips
work on state
display the state
E.g. do not create a list of <div>, as you did, instead, create a list of e.g. objects, and render that list inside the view. You might even create an own MovesView component, only displaying the list. That might be a bit too much separation in your example, but you should get used to the idea, also I assume your real component will be much bigger at the end.
Don’t be afraid to split components into smaller components.
It seems the problem is occurred by Game element.
It triggers addEventListener on every render.
Why not use onClick event handler
/* remove this part
useEffect(() => {
function executeMove(e) {
props.setMovesList(e.target);
}
document.getElementById('board').addEventListener('click', executeMove, false);
})
*/
const executeMove = (e) => {
props.setMovesList(e.target);
}
return (
<div id="board" onClick={executeMove}>
...
</div>
)
If you want to use addEventListener, it should be added when the component mounted. Pass empty array([]) to useEffect as second parameter.
useEffect(() => {
function executeMove(e) {
props.setMovesList(e.target);
}
document.getElementById('board').addEventListener('click', executeMove, false);
}, [])
I have been able to find limited information on this error and was hoping someone could take a deep dive into explaining exactly what causes this. I haven't changed any of the code that appears to be showing up in the call stack recently, so I was wondering if this is from a newer update?
In my case, The error/warning was casued by the react-block-ui package. Currently there is an opened issue at github of that package. The issue hasn't been solved so far.
It's a react issue. You can check if any third-party-packages are causing this. You can check this to see exactly where the error is coming from. I found these comments from there -
// We're already rendering, so we can't synchronously flush pending work.
// This is probably a nested event dispatch triggered by a lifecycle/effect,
// like `el.focus()`. Exit.
I hope this helps.
My problem was putting the debugger inside the code. As soon as I removed it, the error went away. So just in case
I spent quite some time debugging a similar issue on my project. In the end, we were calling focus inside a setState function, but this can be quite hidden by callbacks. In our case this was looking at this:
class ChildComponent extends React.Component {
func() {
this.setState(state => {
// ... Doing something
this.props.onStateChange();
// ... Returning some state
});
}
}
And then elsewhere:
onStateChange = () => {
this.element.focus();
};
render() {
return <ChildComponent onStateChange={this.onStateChange} />;
}
I solved the problem by calling the callback in componentDidUpdate:
class ChildComponent extends React.Component {
func() {
this.setState(state => {
// ... Doing something
// ... Returning some state
});
}
componentDidUpdate(prevProps, prevState) {
if (compare(prevState, this.state)) {
this.props.onStateChange();
}
}
}
Another possible solution: A colleague of mine also suggested to use requestAnimationFrame inside setState so that the call would be happening out of the render cycle.
Hope this will help some people coming here!
The Problem
I have an application that uses this React Redux Boilerplate: https://github.com/flexdinesh/react-redux-boilerplate
I created a new page that is connected to the injected reducer + saga.
I receive following props: posts, loading, error, loadPosts and match
When I use these directly the app is working as expected. But as soon as I start to destructure the props, the app is behaving unexpectedly.
Especially with the match props.
When I do it like this:
const SubforumPage = (props) => {
useEffect(() => {
const { id: subId } = props.match.params;
console.log('props: ', subId);
}, []);
// .... other code
}
No problem everything works.
But when I do it like this:
const SubforumPage = ({match}) => {
useEffect(() => {
const { id: subId } = match.params;
console.log('props: ', subId);
}, []);
// .... other code
}
match suddenly gets undefined!
I have really no clue what so ever why this is happening. It's the first time that I see an error like this.
This specific page is set up like this in the routing file:
<Route path="/sub/:id" component={SubforumPage} />
And it's clearly working when using (props) in the function arguments but not with ({match})
Why is this? Can please someone help me out here.
What have I tried?
I continuesly started destructuring one prop after another. At first this approach works and it's still not undefined but when I get to some props, it's different which ones, it will stop working.
I think it has to do something with how I use my useEffect() hook?
I pass an empty array so it does just run when mounting. It seems like when I refresh the page, the posts are cleared out but the useEffect doesn't run anymore, so the new posts doesn't get fetched. Because hen also the console.log inside the useEffect hook is undefined doesn't even run. But for example the loading prop in console.log outside of useEffect is indeed not undefined
(But that still does not explain why it's working with (props) as argument).
Am I just using useEffect wrong?
Many thanks
Ok guys that was completely my fault. Guess I'm too tired :D. Here is what caused the problem:
I fetch my post in the useEffect hook. I also render a component where I pass in the posts. But the posts are not available because the component has to wait for the data to come in. So I completely forgot that I have to wait for the data.
Before:
return <PostsGroup posts={posts} />;
After: (correct)
return <PostsGroup posts={posts || []} />;
I had a check in place looking like this:
if (loading) return <CircularProgress />;
(before the other return). But it doesn't matter because loading is false when the component initially renders.
So I also set the initial value from loading to true (in my initialState of the reducer). So I have now two checks in place.
Sorry guys. So stupid.
In my componentDidMount() I am making an API call to fetch some data, this call then sets a state object that I use in my render.
componentDidMount() {
const { actions } = this.props;
this.increase = this.increase.bind(this);
// api call from the saga
actions.surveyAnswersRequest();
// set breadcrumb
actions.setBreadcrumb([{ title: 'Score' }]);
actions.setTitle('Score');
this.increase();
}
In my render function I pass some prop values onto the view file:
render() {
const { global, gallery, survey_answers, survey, survey_actual_answers } = this.props;
if (global.isFetching) {
return <Loading />;
}
return this.view({ gallery, survey_answers, survey, survey_actual_answers });
}
The problem I am having is that the survey_actual_answers prop is not being set the first time that the page is loaded, however when I refresh the page the prop returns the data fine and the rest of the script will run. It's only the first time that it returns an empty array for that prop value.
This is how I have passed my props in:
Score.propTypes = {
actions: PropTypes.object.isRequired,
global: PropTypes.object.isRequired,
survey: PropTypes.object.isRequired,
survey_answers: PropTypes.object.isRequired,
gallery: PropTypes.object.isRequired,
survey_actual_answers: PropTypes.array.isRequired,
survey_score_system: PropTypes.array.isRequired,
survey_styles: PropTypes.object.isRequired,
survey_general_doc_data: PropTypes.object.isRequired
};
function mapStateToProps(state, ownProps) {
return {
...ownProps,
global: state.global,
gallery: state.gallery,
survey: state.survey,
survey_actual_answers: state.survey.survey_actual_answers,
survey_answers: state.survey.survey_answers,
survey_score_system: state.survey.survey_score_system,
survey_styles: state.survey.survey_styles,
survey_general_doc_data: state.survey.survey_general_doc_data,
isFetching: state.isFetching
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
...globalActions,
...galleryActions,
...surveyActions
}, dispatch)
};
}
Does anyone know why this is happening? It's almost as if it's not calling componentDidMount at all.
This is happening because of how React works fundamentally. React is supposed to feel fast, fluent and snappy; the application should never get clogged up with http requests or asynchronous code. The answer is to use the lifecycle methods to control the DOM.
What does it mean when a component mounts?
It might be helpful to understand some of the React vocabularies a little better. When a component is mounted it is being inserted into the DOM. This is when a constructor is called. componentWillMount is pretty much synonymous with a constructor and is invoked around the same time. componentDidMount will only be called once after the first render.
componentWillMount --> render --> componentDidMount
How is that different than rerendering or updating?
Now that the component is in the DOM, you want to change the data that is displayed. When calling setState or passing down new props from the parent component a component update will occur.
componentWillRecieveProps --> shouldComponentUpdate-->componentWillUpdate
-->render-->componentDidUpdate
It is also good to note that http requests are usually done in componentDidMount and componentDidUpdate since these are places that we can trigger a rerender with setState.
So how do I get the data before the render occurs?
Well, there are a couple of ways that people take care of this. The first one would be to set an initial state in your component that will ensure that if the data from the http request has not arrived yet, it will not break your application. It will use a default or empty state until the http request has finished.
I usually don't like to have a loading modal, but sometimes it is necessary. For instance, when a user logs in you don't want to take them to a protected area of your site until they are finished authenticating. What I try to do is use that loading modal when a user logs in to front load as much data as I possibly can without affecting the user experience.
You can also make a component appear as loading while not affecting the user experience on the rest of the site. One of my favorite examples is the Airbnb website. Notice that the majority of the site can be used, you can scroll, click links, but the area under 'experiences' is in a loading state. This is the correct way to use React and is the reason why setState and HTTP requests are done in componentDidMount/componentDidUpdate.
Using setState in componentdidmount. This my code:
async componentDidMount() {
danhSachMon = await this.getDanhSachMon();
danhSachMon=JSON.parse(danhSachMon);
this.setState(danhSachMon);
}
render() {
return (
<View>
<FlatList
data={danhSachMon}
showsVerticalScrollIndicator={false}
renderItem={({ item }) =>
<View >
<Text>{item.title}</Text>
</View>
}
keyExtractor={(item, index) => index.toString()}
/>
</View>
)
}
componentWillMount is deprecated. Now you need to use "DidMount" and as soon as it finishes and changes the DOM on render, react will handle everything else.
Make sure you update and use the correct variables/state/props in the render.
componentDidMount() {
const { applicationId } = this.props;
if (applicationId) {
ApplicationService.getQuotesByApplicationId(applicationId).then((response) => {
const quotes = _.get(response, 'data.data');
this.setState({
quotes,
});
});
....
}
render() {
const quotes = _.get(this.state, 'quotes', null);
return (
<div className="application">
<h4 className="application__sub">Application</h4>
<h1 className="application__title">Quote Comparison</h1>
<div className="form-wrapper">
{this.renderQuotes(quotes)}
</div>
<div />
</div>
);
}
Here I get the quotes from the API and as soon as it finishes it set a variable in the state, then the render and react do their work.