How to re-render a React Component Upon re-direct - javascript

I am creating a clone of SoundCloud, and when I delete a song from the song page, I want to re-direct to the song index component (the /discover page).
I am able to successfully re-direct there upon deletion, but the index page appears empty until I refresh, whereupon the songs are fetched. I've tried using a ComponentDidUpdate on the index page to fetchSongs again when the history.location is different, but haven't had success.
//Song Delete Component
handleDelete() {
this.props.removeSong(this.props.songId)
this.props.history.push("/discover");
}
//Song Index Component
componentDidUpdate(prevProps) {
if (this.props.history.location.pathname !==
prevProps.location.pathname) {
this.props.fetchSongs();
}
}

When your redirect to a new component/route, that component gets re-mounted to the screen. You should use componentDidMount() instead;
Discover Component:
componentDidMount() {
this.props.fetchSongs();
}
Edit ---
Just learned that your second page is actually a modal and that you execute your action-creatorand redirect in there. In that case, the Discover component may have not actually unmount from the screen, hence why componentDidMount() does not execute. But componentDidUpdate() should still work, let's refactor it a bit
So in Discover Component, try something like:
componentDidUpdate(prevProps){
if(this.props.songsOrNameOfReducerKey !== prevProps.songsOrNameOfReducerKey){
this.props.fetchSongs()
}
}
this.props.songsOrNameOfReducerKey just refers to the redux-state that holds the updated list of songs - available as a prop in your component. We compare the updated list to our previous list, and if there is a difference, we execute your action-creator.
Make sure you connected redux to your component via mapStateToProps() to get a hold of this state-value. Replace songsOrNameOfReducerKey with the key that you defined.
When you delete the song from your list, it should give the Discover component, new props, thus triggering componentDidUpdate()

Related

Prevent React Route to re-run componentDidMount() on traversing back on same route

For a given route localhost:3000/my-link, component MyLink is rendered.
In MyLink component, ajax request is fired in componentDidMount() which then sets the state object of the MyLink class and renders the component.
But when navigating to some other route and switching back to /my-link ajax is fired again. In short, whole state previously populated is lost. Can there be a way to check if the previous state is already populated, then prevent componentDidMount to be called?
componentDidMount() is invoked immediately everytime a component is mounted in the DOM. What you're trying to do can be achieved by integrating react redux store. Call the Ajax call according to store state variable. On firing the AJAX requests Dispatch the action to make the variable true.
componentDidMount() {
if(!this.props.variable)
{
//AJAX CALL
dispatch({ type: 'SET_VARIABLE', value: true });
}
}
Once it sets to true, it won't be called next time you navigate to this route.
When you switch from your existing route localhost:3000/my-link to any other page like localhost:3000/other-link, Your MyLink component got unmounted but at the same time, your data fetched in MyLink component is still in your redux store.
Here's what you can do;
If the data which you are fetching in MyLink is frequently changing then it is a good idea to fetch it again when you visit the same link again. Means, You need to clear the state when your component unmounts.
componentWillUnmount() {
// resetData is an action which will clear the data in your MyLink reducer.
this.resetData();
}
So, When you visit the same link again, The component won't have any previous data and it will fetch the fresh data.
If that data is not frequently changing and you're okay with getting it just one time then you can make a conditional call to your action.
componentDidMount() {
// this.props.data is a state from redux store.
if(!this.props.data){
this.getData();
}
}

React good practice from parent state to children props

So i've been reading a lot lately on react state and props.
My app wasn't that big, but now i'm facing a problem that seems to be commun for a lot of people, and i'm trying to find the best way to implement this.
My app is simple. A SearchBar on top, that display a list of contact. My search bar is a component and is updating a react-redux store with the results of the searchBar value (calling a backend with axios). Till here everything works great.
When the results array is populate (in redux store), my container rerender the results array. Like this:
class Suggestions extends Component {
render() {
console.log('before map: ', this.props.contacts);
const {
contacts,
...rest
} = this.props;
const options = contacts.map((contact, index) => (
<Contact
key={contact.id}
renderToaster={renderToasterFunction}
contact={contact}
/>
));
return <div>{options}</div>;
}
}
const mapStateToProps = (state, props) => ({
contacts: state.contact.results,
});
export default connect(mapStateToProps)(Suggestions);
The problem happen in my Contact component, My list is a lirs of sometimes 10 contacts that are display on the same page. So my problem is that each Contact component need to have it's own state (to add or edit info exemple: if you need to add a new phone number).
//contact component
state = {
contactState: ???
}
...
render(){
//exemple for simplicity
return <div>{this.state.contactState.name}</div>
}
I've founded on react website that it's not a good idea to copy props from parent in state of child. And in my case i've seen it, because if i do this
...
state = {
contactState: this.props.contact <--info from parent
}
first search is ok, but second search with an other letter, results list is not updated and i still see some results of first search.
so i've tried to change my contact component to this:
//contact component
state = {
contactState: ???
}
...
render(){
//exemple for simplicity
return <input value={this.props.contact.name} onChange={this.handleChange}/>
}
And this is working great in term of visual update, all my contact are update even if i do 3-4 searches. But my problem is that, now when i want to edit the name i need to store all my contactState somewhere before saving this and second problem, because my component display {this.props.contact.name} when i edit this, the user can't see the new value, because i can't edit props.
So is there a way to render state from props in a child everytime the parent state change. Or is there a way to 1) save the state when the user edit a contact and 2) display the new value he has written ?
What is the best way when dealing with .map() to have one state foreach children that can be re-renderer when the parent state change and rendering all children with their new state.
Thank you for your help.
Don't hesitate if you need more precisions.
I'm not sure to understand everything but if I get what you want to do:
A simple solution could be to dispatch an action on the onChange
The reducer which catch the action will update your redux store
The props will change and the View too.
But that's will make you dispatch A LOT of actions...
Other option :
Use a state in every Contact-Component which duplicates props
state = {...this.props.contact}
Modify the state on the change handler and use it as value too.
Save and dispatch the "final name" to update redux store and call the api at the same moment to update in on your server
Let me know if that's clear enough

How to reset state on props change in an already mounted component?

I have a <BlogPost> component which could've been a Stateless Function Component, but turned out as a Class Stateful Component because of the following:
The blogPost items that it renders (receiving as props) have images embedded in their html marked content which I parse using the marked library and render as a blog post with images in between its paragraphs, h1, h2, h3, etc.
The fact is that I need to preload those images before rendering the post content to my client. I think it's a UX disaster if you start reading a paragraph and all of a sudden it moves down 400px because the image that was being loaded has been mounted to the DOM during the time you were reading it.
So I prefer to hold on by rendering a <Spinner/> until my images are ready. That's why the <BlogPost> is a class component with the following code:
class BlogPost extends React.Component {
constructor(props) {
super(props);
this.state={
pending: true,
imagesToLoad: 0,
imagesLoaded: 0
};
}
preloadImages(blogPostMedia) {
this.setState({
pending: true,
imagesToLoad: 0,
imagesLoaded: 0
});
... some more code ...
// Get images urls and create <img> elements to force browser download
// Set pending to false, and imagesToLoad will be = imagedLoaded
}
UNSAFE_componentWillReceiveProps(nextProps) {
if (this.props !== nextProps) {
this.preloadImages(nextProps.singleBlogPost.media);
}
}
componentDidMount() {
this.preloadImages(this.props.singleBlogPost.media);
}
render() {
return(
this.state.pending ?
<Spinner/>
: (this.state.imagesLoaded < this.state.imagesToLoad) ?
<Spinner/>
: <BlogPostStyledDiv dangerouslySetInnerHTML={getParsedMarkdown(this.props.singleBlogPost.content)}/>
);
}
}
export default BlogPost;
At first I was calling the preloadImages() only inside the componentDidMount() method. And that works flawlessly for the first post I render with it.
But as soon as I would click on the next post link; since my <BlogPost>component is already mounted, componentDidMount() doesn't get called again and all the subsequent posts I would render by clicking on links (this is a Single Page App) wouldn't benefit from the preloadImages() feature.
So I needed a way to reset the state and preload the images of the new blogPost received as props inside an update cycle, since the <BlogPost> component it's already mounted.
I decided to call the same preloadImages() function from inside the UNSAFE_componentWillReceiveProps() method. Basically it is reseting my state to initial conditions, so a <Spinner/> shows up right away, and the blog post only renders when all the images have been loaded.
It's working as intended, but since the name of the method contains the word "UNSAFE", I'm curious if there's a better way to do it. Even though I think I'm not doing anything "unsafe" inside of it. My component is still respectful to its props and doesn't change them in anyway. It just been reset to its initial behavior.
RECAP: What I need is a way to reset my already mounted component to its initial state and call the preloadImages() method (inside an update cycle) so it will behave as it was freshly mounted. Is there a better way or what I did is just fine? Thanks.
I would stop using componentWillReceiveProps()(resource). If you don't want the jarring effect, one way you can avoid it is to load the information from <BlogPost/>'s parent, and only once the information is loaded, to pass it into <BlogPost/> as a prop.
But anyway, you can use keys to reset a component back to its original state by recreating it from scratch (resource).
componentWillReceiveProps is deprecated, it's supposed to be replaced with either getDerivedStateFromProps or componentDidUpdate, depending on the case.
Since preloadImages is asynchronous side effect, it should be called in both componentDidMount and componentDidUpdate:
componentDidMount() {
this.preloadImages(this.props.singleBlogPost.media);
}
componentDidUpdate() {
this.preloadImages(this.props.singleBlogPost.media);
}

React - prevent lifecycle components from rendering the same data twice: WillReceiveProps & componentDidMount

Quick React question for Components specifically. I am using two lifecycle methods:
componentDidMount() -for retrieving data when component is first rendered
componentWillReceiveProps(nextProps) - for updating data when some parameters change
This works great. However, when I refresh the page with the component, both lifecycle methods are executed when one will suffice. The data on page is still correct, however it seems a bit inefficient. How can I combine these to lifecycles if possible?
Below example will call fetchTest() twice when page is refreshed. If I remove componentDidMount, then the data will not initially load if user refreshes the page.
Any ideas on how to have fetchTest() called once no matter how the user gets to the component?
componentDidMount() {
fetchTest(this.props.params.id);
// for initial component render
}
componentWillReceiveProps(nextProps) {
fetchTest(nextProps.params.id);
// for when (params.id) is changed. data within component is updated
}
You probably want to do
componentWillReceiveProps(nextProps) {
if (nextProps.params.id !== this.props.params.id) {
fetchTest(nextProps.params.id);
}
}

ReactJS: How to make a component call a method only the very first it is rendered?

I would like for a component to call a method only once, and only the very first time the component gets rendered. I attempted it in constructor(), thinking that it is supposed to occur only once and only when it is first mounted ever, but looks like whenever that component is rendered again, the constructor() is called again as well.
Is there a way to have a component call a method only once and only the very first time it is rendered?
Thank you
componentWillMount() gets called pre-render and only once until the page refreshes.
https://facebook.github.io/react/docs/react-component.html#componentwillmount
componentDidMount() gets called immediately after render() and the DOM is available at this time. This will happen only the first time it's loaded until the page refreshes.
https://facebook.github.io/react/docs/react-component.html#componentdidmount
you can use getDerivedStateFromProps and pass an empty parameter while you navigate, that triggers the method once after the navigation.
// caller
this.props.navigation.navigate('SOMEWHERE', {})
// SOMEWHERE
static getDerivedStateFromProps(nextProps, prevState){
doSomething()
return null
}
You could set a variable in localStorage when you first load your page. Then, whenever you render, you simply get and test that variable:
async componentDidMount() {
...
if(localStorage.getItem('reRender') !== "true") {
//Processing to do only for the very first render
localStorage.setItem('reRender', "true");
}
}
You could reset the variable depending on your use case afterwards (for example when logging out):
localStorage.removeItem('reRender');

Categories

Resources