React-redux state changes but no re-renders - javascript

I currently have a single page app that logs a user in and after login should re-render to the appropriate page. On dev tools I see the state change but I have to manually refresh the page. The page re-render on the basis of seeing a current user exists. If there is a current user it should return a different component. Here is the code snippet below.
So far ive double checked to make sure my reducer is not mutating the state and is in fact returning the correct newState. My state is also normalized correctly and the component below is correctly watching the slice of state that changes.
class Greeting extends React.Component {
constructor(props) {
super(props)
debugger
}
render () {
debugger
const sessionLinks = () => (
<div className="login-signup">
<div className="navhead">
<div className="butterflyr">butterflyr</div>
<img className="butterfree" src={window.butterFree}/>
<Route exact path="/" component={LogInFormContainer}/>
</div>
<SignUpFormContainer/>
</div>
);
const homePage = () => (
<hgroup className="header-group">
<NavBar currentUser={this.props.currentUser} logout={this.props.logout}></NavBar>
<Route exact path="/" component={ForFun}/>
</hgroup>
)
return (
<div>{this.props.currentUser ? homePage() : sessionLinks()}</div>
)
}
}

Related

React-router Pass Data

I am using React for my project. I am also using react-router-dom library. However, I am confusing about passing data by react-router-dom.
Here is code for Parent Component:
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
Path: {
pathname: "/child/id/1",
state: {
languageChosen: "English"
}
}
};
this.onChangeLanguage = this.onChangeLanguage.bind(this);
}
onChangeLanguage() {
const { Path } = this.state
Path.state.languageChosen = 'Spanish'
this.setState({Path})
}
render() {
return (
<div>
<NavLink to={this.state.Path}>ToChild</NavLink>
<Route
path={`/child/id/:id`}
render={props => (
<Child {...props} onChangeLanguage={this.onChangeLanguage} />
)}
/>
</div>
);
}
}
It has a state called Path. The Parent Component pass function onChangeLanguage to its Child component.
Here is Child Component:
class Child extends Component {
onChange = () => {
this.props.onChangeLanguage;
};
render() {
const { languageChosen } = this.props.location.state;
return (
<div>
{languageChosen === "English" ? "English" : "Spanish"}
<button onClick={this.onChange}>Change Language</button>
</div>
);
}
}
If I click the Change Lanuguage button in Child Component, the function was called and the state in Parent Component change. The prop LanguageChosen also update in Child component.
However, if I refresh the page, the prop LanguageChosen in Child doesn't update if I press the button. It only works If I don't refresh the page.
How should I fix that. Thanks!
The reason that you get this problem is here
const { languageChosen } = this.props.location.state
languageChose is set from this.props.location,location is set when url change to "/child/id/:id",after refreshing the page,state in Parent element change, but it won't change the location.state.And,I think you can use props in component to solve the problem.
1. set a languageChosen property in route
component={props => (
<Child {...props} onChangeLanguage={this.onChangeLanguage} languageChosen={this.state.Path.state.languageChosen}/>
)}
2. change this in the Child component
const { languageChosen } = this.props.languageChosen;
This code confuses me a bit but, remember when you refresh a page all the javascript gets reloaded, so you hard coded languageChosen to English in your Parent so every time you refresh the page it resets to English. You can either store the value in localStorage and check for its existence on refresh, or add it to your route path child/id/:id/:language
Then if you are using React Router v4, in your child component use this.props.match.params.language and it will render the given language.

Passing function reference to sibling-ish in react + react-router-dom

I have a menu which has routes as siblings and in one of those routes, namely Landing (see below), I have a function, scrollToTop, that I would prefer if the menu has access to. I would strongly prefer to do this with pure reactjs or js but so far I have not been able to do so in a pragmatic way. What approach would you recommend to this problem?
App.js
class App extends Component {
constructor(props) {
super(props);
this.state = {
scrollToTop: undefined,
};
this.landingComp = () => <Landing setScrollToTop={(scrollToTop) => this.setScrollToTop(scrollToTop)} />;
}
setScrollToTop(func) {
this.setState({
scrollToTop: func,
});
}
render() {
return (
<div className="container">
<div className="content">
<Menu scrollToTop={this.state.scrollToTop} />
<Route exact path='/' component={this.landingComp} />
</div>
</div>
);
}
}
Landing (component)
....
componentDidMount() {
if (this.props.setScrollToTop) {
this.props.setScrollToTop(() => this.scrollToTop());
}
}
....
The following key-point is the reason for this Warning: setState(...): Cannot update during an existing state transition:
You are using setState on unmounted component. These are memory leaks.
Make changes in your code like this:
this.landingComp = () => <Landing setScrollToTop={this.setScrollToTop} />;
And
setScrollToTop = func => {
this.setState({
scrollToTop: func,
});
But It would be great if you share code via codesandbox so we can resolve the if this doesn't remove the warning. You need to implement isMounted pattern in that component to avoid memory leak. Further organsie your page navigation It could be cumbersome as your app grows faster.
Feel Free to ask any question. And type of warnings shouldn't occur in our App anywhere.

React router pass callback with current route to child component

I am using react with react router. Some of my components have sub-routes defined so I want to pass them a callback that enables to returning to a specific route/component. I want to avoid passing a string to a specific route (because of the dependency when routing changes happen in the code). So i prefer passing a callback and populating it with the value of match.url.
But this does not work: Instead of passing the value, match.url always refers to the current route.
Parent component (simplified):
export class ParentComponent extends React.Component {
render() {
const { history, match, contentId } = this.props;
return (
<div>
<div>
<div>Block 1</div>
<div>Block 2</div>
<div>Block 3</div>
</div>
{contentId && <MyChildComponent content={contentId} goBack={() => history.push(match.url)} />}
</div>
);
}
}
My child component (simplified):
export class MyChildComponent extends React.PureComponent {
render() {
return (
(
<React.Fragment>
<div role="dialog" onClick={this.props.goBack} />
</React.Fragment>),
);
}
}
My router:
const Routes = () => (
<Router history={createBrowserHistory()}>
<div>
<Route path="/result/:contentId?" component={ParentComponent} />
</div>
</Router>
);
So when I go to /result I see - as expected - all but the child component. When navigating to /result/someId I see the child component but the goBack only refers to the current page instead of the previous one.
constructor(props){
super(props);
this.goBack = this.goBack.bind(this);
}
goBack(){
this.props.history.goBack(); // You're not calling it from history
}
.....
<button onClick={this.goBack}>Go Back</button>
I think that you are using push to navigate to another route. So when you do history.push('/result/someId') you are adding another entry to history stack so goBack will navigate to the previous entry in the stack which is /result. It works as if you were a regular website and clicked a link - you could still go back even if what had changed was some dynamic parameter.
If you don't want to add up to history stack use - history.replace('/result/someId')
See navigating with history.
I figured out my core-problem was that I needed at least one part of the child routes in the parent component. This lead to changing path props also in the parent component when child-routes were changing.
My solution: Store the current location in the constructor of the parent component and pass this as prop to child components to refer back. It works but has the drawback that one can not directly access child component routes because they will not refer back to the right parent path. For my use case this is fine but improvements are welcome.
Parent component
export class ParentComponent extends React.Component {
constructor(props) {
super(props);
this.location = this.props.location.pathname;
}
render() {
return (
{contentId && <MyChildComponent content={contentId} goBack={() =>
history.push(this.location)} />}
)
}

Making react children NOT to refresh when parents state changes

So here is my problem. I have an root component that contains navigation and Switch with every component in my page. Navigation is sliding in and out from the left and the way I'm doing this, I'm changing the state in root component, passing prop to and deciding whether or not, should I add class to my nav. The problem is that every component in my app is re-rendering on opening/closing nav. Here is my root component:
class App extends Component {
constructor(props) {
super(props);
this.state = {
navOpen: false
}
}
toggleNav = () => {
this.setState({
navOpen: !this.state.navOpen
})
}
closeNav = (e) => {
if (this.state.navOpen) {
this.setState({navOpen: false})
}
}
render() {
return (
<main>
<Header/>
<Hamburger navOpen={this.state.navOpen} toggleNav={this.toggleNav}/>
<Navigation navOpen={this.state.navOpen} toggleNav={this.toggleNav}/>
<section className="container-fluid content" onClick={this.closeNav}>
<Switch>
<Route path="/add-recipe/:groupId?" component={NewRecipe}/>
<Route path="/recipes/:page?/:sortType?/:size?" component={RecipeList}/>
<Route path="/recipe/:id" component={Recipe}/>
<Route path="/sign/" component={SignForm}/>
<Route path="/user/:id" component={User}/>
</Switch>
</section>
</main>
);
}
componentDidMount() {
this.props.userActions.getUser(this.props.url);
}
}
function mapStateToProps(state) {
return {url: state.url.url, user: state.user.loggedUser}
}
function mapDispatchToProps(dispatch) {
return {
userActions: bindActionCreators(userActions, dispatch)
}
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(App));
Navigation is the only component ( besides hamburger) that cares about his parents state so I have no idea why everything is re-rendering. Does anyone have some ideas?
EDIT:
I've added sCU to my nested components like that:
shouldComponentUpdate(nextProps, nextState) {
// console.log(this.props)
// console.log("next")
// console.log(nextProps)
if (this.props == nextProps) {
return false
}
return true
}
But it didn't change anything. When I open the navigation props for routes remain the same but they still rerender. I tried to move "navOpen" state to navigation and open it from root component via "ref" but every time I call its method I get "Cannot read property 'toggleNav' of null"
You can prevent re-rendering by implementing shouldComponentUpdate on the affected component. Beware though, that rendering does not mean that the DOM is being changed. Rendering in react means that react creates a new copy of its internal node tree and compares it to the DOM to see if and where it needs to update it. So don't worry about re-renders unless your component is expensive to create (i.e. it performs a request to a backend or is heavily animated)

Communicating between React components

I'm new to react so this is something I don't know. In the app that I
'm working with it has a main component where other components are loaded.
Like this,
render() {
return (
<div className="index">
<HeaderComponent />
<MainHeroComponent />
<AboutComponent />
</div>
);
}
And I want when someone clicks a link in HeaderComponent to show the about component. And hide the MainHeroComponent. How can I do such communication between components in React? Is it possibe?
Thanks
Use React-Router and create routes for this scenario instead of direct communication between components. Sample app structure using react-router
const App = React.createClass({
render() {
return (
<div>
<h1>App</h1>
<HeaderComponent />
</div>
)
}
})
render((
<Router>
<Route path="/" component={App}>
<Route path="hero" component={MainHeroComponent} />
<Route path="about" component={AboutComponent} />
</Route>
</Router>
), document.body)
For more details on router refer: https://github.com/reactjs/react-router/blob/master/docs/guides/RouteConfiguration.md
Aditya's answer is probably a better solution, but if you really want to it your way, you can use state and callbacks.
class Index extends React.Component {
constructor(props) {
super(props);
this.state = {
showHero: true
};
this.toggleShowHero = this.toggleShowHero.bind(this);
}
toggleShowHero() {
this.setState({
showHero: !this.state.showHero
});
}
render() {
return (
<div className="index">
<HeaderComponent onClick={toggleShowHero}/>
{
this.state.showHero ?
<MainHeroComponent /> :
<AboutComponent />
}
</div>
);
}
There are various ways you can achieve this, including React-routers and Redux, but since you're new to React, it'll be good if you get familiar with the basics first. For a start, you have to change the state of the main component to decide which child component to render.
In the main component code snippet you posted, initialize a state in the constructor as follows:
/* in the main component */
constructor(props) {
super(props);
this.state = {
showAbout: true
};
}
Then modify the render function as follows, to pass a reference to your main component, down to your header component:
/* in the main component */
<HeaderComponent mainComponent={this}/>
Then, in HeaderComponent, attach a click event handler to the link on which you want to perform the operation.
/* in HeaderComponent */
<a href="#" ....... onClick={this.showAbout.bind(this)}>Show About</a>
In the same component, define the showAbout function as follows:
/* in HeaderComponent */
showAbout () {
let mainComponent = this.props.mainComponent;
mainComponent.setState({
showAbout: true
)};
}
Finally, back in the render function of the main component:
/* in the main component */
render () {
let mainHeroComponent, aboutComponent;
if (this.state.showAbout) {
aboutComponent = (
<AboutComponent/>
);
} else {
mainHeroComponent = (
<MainHeroComponent/>
);
}
return (
<div className="index">
<HeaderComponent mainComponent={this}/>
{mainHeroComponent}
{aboutComponent}
</div>
);
}
And you're done! Basically, a component gets re-rendered every time its state is changed. So each time you click on the link, the main component's state is changed with a new value of showAbout. This will cause the main component to re-render itself, and, based on the value of showAbout, it will decide whether to render MainHeroComponent or AboutComponent.
But you should make sure you have a similar logic to display MainHeroComponent as well, instead of AboutComponent, just to switch the views.

Categories

Resources