I want to update the state of my application when a router component is rendered. For example, if I go home "/" I will render homepage, but I want to trigger a state change so that other components like my footer and header are aware we are on the home page vs internal page. Currently, I am parsing the url manually when site is loaded to detect this, however, as the site grows this is unfeasible. I have also seen onEnter as a solution, however, this is not available for reach router.
<Router basepath={process.env.PUBLIC_URL} className="app" >
<LandingPageBody path="/" default/>
//update redux state to show we are on homepage
</Router>
You can use componentDidMount in LandingPageBody component. It is a react lifecycle method it will be triggered when component will mount. You can update state from inside of this method.
If you are using functional components, make use of useEffect function with dependency on props.match.location. Check for condition in that and dispatch action if the condition is true.
(React.useEffect(() => { if(props.match.location === 'desired path') dispatch(action) }, [props.match.location]))
This way when ever your route component loads, it will check for route on every route change and let your redux store know about the current path.
For Stateful component, you can do the same in componentDidUpdate lifecycle.
Related
I'm trying to make a custom cursor component in react, based on this article: https://dev.to/andrewchmr/awesome-animated-cursor-with-react-hooks-5ec3
I'm using react router dom, and the problem is that the hover events are only working on the content within the router components on initial page load, or after refreshing the pages.
The hover is however always working on the nav component links. I've set up a basic CodeSandbox where you can see how the hover works on the nav links, but not on the page content links (after initial page load and when navigating between pages).
https://codesandbox.io/s/dazzling-newton-u9hk5
I'm a beginner with react, so I'm sure I'm going about this the wrong away. Can someone please help?
The issue here is that when the route changes, your Cursor Component does not need to be updated, therefore the useEffect hook is not called. And that's why your listeners are not attached to new anchor tags that appear when you change the route.
However, React Router Dom has introduced a new hook called useLocation that you can use to respond to the route change. This hook returns current url, so when you pass it to the array as below, useEffect will be called everytime when the url changes, therefore it will attach your listeners to the recently rendered anchor tags
Add this to your Cursor component:
import { useLocation } from "react-router-dom";
// then in your functional component
const location = useLocation();
useEffect(() => {
addEventListeners();
handleLinkHoverEvents();
return () => removeEventListeners();
// eslint-disable-next-line
}, [location]);
I want to update all the components in my React application (no matter what is the nesting level or if there is any prop or state change).
I tried using this.forceUpdate() but as some of the components don't use any props or state, those and their children are not updating.
Also, I tried the solution mentioned in the example but this also didn't work.
App.js
render() {
return (
<div>
<Routes />
</div>
);
}
As you can see, Routes don't take any props, so this component is not re-rendering. As a result of which, none of the components inside Routes is updating.
One way is using the store. As this is a small operation only, so I don't want to use the store for this kind of use case.
If you really need to update components on every render, you can use a key prop that's different every time the component renders. This will force React to unmount the previous instance of component and mount the new one with all the state and props being reset. In case of Route the easiest is to assign key as the current page url:
render() {
return (
<div>
<Routes key={window.location.href} />
</div>
);
}
With other components it's a bit more tricky, since you'll need to manually ensure the key is different each time.
If you would like to have more granular control over when the components are updated, you can hook the key prop to the state and change it onClick.
This post explains the approach in more detail.
I couldn't find the answer anywhere because nothing was working for me, so I'm starting a new topic. Please, don't mark it as a duplicate
Router.js:
<Switch>
<Route path="/" exact component={Home} />
<Route exact path="/p/:uid" component={Profile} />
</Switch>
Profile.js
constructor(props) {
super(props);
this.state = {
loading: true,
profile_user_id: this.props.match.params.uid,
};
}
Then, later in Profile.js, I trigger a fetch to get data from backend and save this data to state using this.setState({ ... }). When the Profile component is rendered, everything looks fine.
In Router.js, there is also a Link:
<Link to={"/p/" + this.state.ntuser.tag}>Profile</Link>
.. which should go to your own profile. So when your user ID is 1, and you visit the profile of the user with id 22, your URL will be /p/user22 and the link will point to /p/user1.
The problem is that even though profiles are rendered nicely, Profile component does not become re-rendered when you click the link (which should direct you to /p/user1 and show your profile instead). I tried to save location from react-router to state as well, so every time URL changes it will be caught in componentWillReceiveProps() and inside I update state. But still nothing. Any ideas?
PS: I'm using React.Component
console.log(this.props.match.params.uid) in constructor, componentDidMount() and componentDidUpdate() (UNSAFE_componentWillReceiveProps() is deprecated)
Number and places (of log calls) will tell you if component is recreated (many construcor calls) or updated (cDM calls). Move your data fetching call accordingly (into cDM or cDU ... or sCU).
You can save common data in component above <Router/> (f.e. using context api) - but this shouldn't be required in this case.
Solution
You can update state from changed props using componentDidUpdate() or shouldComponentUpdate(). componentDidUpdate should be protected with conditions to prevent infinite loop. See docs.
Simplest solution:
componentDidUpdate(prevProps) {
if (this.props.some_var !== prevProps.some_var) {
// prop (f.e. route '.props.match.params.uid') changed
// setState() - f.e. for 'loading' conditional rendering
// call api - use setState to save fetched data
// and clearing 'loading' flag
}
}
shouldComponentUpdate() can be used for some optimalizations - minimize rerenderings.
Had same problem in my project, Didn't find any good workable idea exept making somethink like this:
import {Profile} from "../Profile "
<Link to={`your href`}
onClick={userChanged}/>
..........
function userChanged() {
const userCard= new Profile();
userCard.getProfileData();
}
First of all you need to make your Profile component as export class Profile extends React.Component (export without default).
getProfileData is method where i get data from my api and put it state. This will rerender your app
Here is what happens:
You go to /p/user22.
React renders <Route exact path="/p/:uid" component={Profile} />.
The Route renders the Profile component for the first time.
The Profile component calls the constructor, at the time, this.props.match.params.uid is equal to "user22". Therefore, this.state.profile_user_id is now "user22".
You click on the link to /p/user1.
React renders <Route exact path="/p/:uid" component={Profile} />, which is the same Route.
The Route then rerenders the same Profile component but with different props.
Since, its the same Profile component, it does not update the state.
This explains why you still see the profile of user22
<Provider store={this.props.store}>
<ConnectedRouter history={this.props.history}>
<AppWrap>
<Header />
<Main>
<Switch>
<Route exact path="/" component={Component1}/>
<Route path="/component2" component={Component2}/>
</Switch>
</Main>
</AppWrap>
</ConnectedRouter>
</Provider>
Inside App.componentWillMount, the Redux action this.props.requestApplesApiData is triggered. It is handled by a saga, which fetches some data from an api and returns with another action receiveApplesApiData with a payload, which is added to the redux state by the reducer.
componentWillMount(){
// triggers an action
// which is picked up by a saga,
// which is handled by the reducer
// which updates the redux state
this.props.requestApplesApiData();
}
Redux state is updated fine, when I inspect it in the chrome dev tools (w. redux extension), but I noticed that the redux state (with the recently fetched "apples") is not updated in time in Component2, while the race condition was won in Component 1. This means, that Component2 can't display the "apples", because they weren't there at the time of its render function.
A re-render would usually happen automatically, if it was in a nested tree like structure, however "apples" are located in their own root object in the redux store.
How can I have Component1 and Component2 wait for App to fetch the necessary data. Or put in other words, how can I delay/defer the render function in App, until the data is in redux?
Two options:
Conditionally render the component based on whether a certain value is present in the store
Display something intermediary before the value is updated in the Redux store
But it sounds like the main issue here though is getting the component to update based of a change in the store.
The general path is that components will update when any of their props are updated. Once the saga completes, it will update some value in the redux store. Components should have something that watches the store and updates their props from the store. This is what will tell React to re-render these components. General convention is to store all of these updates in a mapStateToProps function.
Your example is fairly stripped down, so I'm not sure how much of the built in redux functionality you're using, but here are the docs that describe how to pass updated props to your component: https://github.com/reactjs/react-redux/blob/master/docs/api.md#connectmapstatetoprops-mapdispatchtoprops-mergeprops-options
Summary from the docs: connect the component to the store with connect() and the first param needs to be a function specifying how the component will update its props from the store.
render(){
if (!this.readyForStep()){
this.props.history.pushState(null, `step2`);
}
// Cannot update during an existing state transition (such as within `render`). Render methods should be a pure function of props and state.
}
So I can't redirect in render()... where in a component lifecycle can/should I?
Depending on the specific semantics you want redirecting before or after render, you can make this check in any of componentDidMount, componentWillReceiveProps, or componentDidUpdate.
Synchronous transitions lead to state updates in the <Router> component, so you can't make them in any place where you normally couldn't call setState.