Render methods should be a pure function of props and state - javascript

I am receiving the error of Warning: Cannot update during an existing state transition (such as within render). Render methods should be a pure function of props and state..
It appears that it is because of this code
const LoginAuth = () => {
navigate(routes.INDEX);
return null;
};
removing navigate(routes.INDEX); stops the error.
What is wrong with the code? Should I be using another method to redirect an authUser? Any help is appreciated.
It is a part of
import React from 'react';
import { navigate } from 'gatsby';
import AuthUserContext from './AuthUserContext';
import withAuthentication from './withAuthentication';
import Layout from './Layout';
import LoginForm from './LoginForm';
import * as routes from '../../constants/routes';
const LoginAuth = () => {
navigate(routes.INDEX);
return null;
};
const LoginPage = () => (
<Layout>
<Transition>
<AuthUserContext.Consumer>
{authUser => (authUser ? <LoginAuth /> : <LoginNonAuth />)}
</AuthUserContext.Consumer>
</Transition>
</Layout>
);
const LoginNonAuth = () => <LoginForm />;
export default withAuthentication(LoginPage);

Stateless functional components are expected to be pure functions, i.e. contain no side effects, while navigate() provides side effects.
Side effects are supposed to be applied after the component is mounted, that's the purpose of componentDidMount hook.
It should be:
class LoginAuth extends Component {
componentDidMount() {
navigate(routes.INDEX);
}
render() {
return null;
}
}
With the introduction of stateful functional components, side effects belong to useEffect hook:
const LoginAuth = () => {
useEffect(() => {
navigate(routes.INDEX);
}, []);
return null;
};

Related

Props alternative to pass the component state to all the components of application in next.js

I want to pass the setState method of the component (SnackBar) to all the child components of the _app.js. If I pass the setState method of SnackBar to all the child components of _app.js then it will be a very tedious task. Because, there are approx 4 levels of hierarchy from _app.js to the single component node. It includes,
_app.js -> pages -> layouts -> sections -> components
The snippet of _app.js is here.
function MyApp({ Component, pageProps }) {
const [ toastOpen, setToastOpen ] = React.useState({
msg: '',
open: false
});
React.useEffect(() => {
pageProps = { ...pageProps, setToastOpen };
}, []);
return (
<React.Fragment>
<ToastMessage
message={ toastOpen.msg }
setOpenState={ setToastOpen }
openState={ toastOpen.open }
/>
<Component {...pageProps} />
</React.Fragment>
)
}
Is there any way that I can directly import the setToastOpen method in the child component and use it whenever I need it?
React have a special Feature called Context Api , using that you can skip the props chain passed into your components..
I recomend you to checkout below resources to learn about context Api -
https://reactjs.org/docs/context.html
https://www.freecodecamp.org/news/react-context-in-5-minutes
Example of ContextAPI
Create a seperate file for Context Toast-context.js , You can use any name you want.
import React, { useState } from "react"
const ToastContext = React.createContext({
message: "",
toastOpen: false,
toggleToast: () => { },
changeMessage: () => { }
})
export const ToastContextProvider = ({children}) => {
/*you need to use
this component to wrap App.js so that the App.js and all of its children
and their children components and so on will get the access to the
context*/
const [toastOpen, setToastOpen] = useState(false);
const [message, setMessage] = useState("");
const toggleToast = () => {
setToastOpen(true)
}
const changeMessage = (message) => {
setMessage(message);
}
return (
<ToastContext.Provider value={
toastOpen,
message,
toggleToast,
changeMessage
}>
{children}
</ToastContext.Provider>
)
}
now in the App.js file you need to wrap your components with ToastContextProvider component
import React, { useContext } from "react";
import { ToastContextProvider } from "./Toast-context";
import { ToastContext } from "./Toast-context";
function MyApp({ Component, pageProps }) {
const { message, toastOpen, toggleToast, changeMessage } =
useContext(ToastContext);
return (
<ToastContextProvider>
{toastOpen && <div className="toast">{message}</div>}
</ToastContextProvider>
);
}
just import the context using useContext Hook in any component you want. you don't need to wrap with <ToastContextProvider> in every component.
just use useContext hook and then you can see the state and as well as call the functions methods to change the state.
Also make sure to refer the above links to learn more about Context Api. Thank You

Cannot update a component (`App`) error in component

I have the following component:
import React, { useState, useEffect, useContext } from 'react';
import PropTypes from 'prop-types';
import { Redirect } from 'react-router-dom';
import DashboardContext from '../../contexts/DashboardContext';
import authorizeWorker from '../../workers/authorize-worker';
/**
* A protected route that calls the authorize API and redirects to login
* on fail
* #param {Function} component The component to redirect to on success
*/
const ProtectedRoute = ({ component }) => {
const [isAuthenticated, setIsAuthenticated] = useState(null);
const dashboardContext = useContext(DashboardContext);
dashboardContext.setIsDashboard(true);
const Component = component;
useEffect(() => {
authorizeWorker({})
.then(() => setIsAuthenticated(true))
.catch(() => setIsAuthenticated(false));
}, []);
if (isAuthenticated === true) {
return <Component />;
}
if (isAuthenticated === false) {
return <Redirect to="/login" />;
}
return null;
}
ProtectedRoute.propTypes = {
component: PropTypes.func
};
export default ProtectedRoute;
I use this for my router e.g.
<ProtectedRoute path="/projects" component={Projects} />
I am recently seeing a warning in the console: Warning: Cannot update a component (App) while rendering a different component (ProtectedRoute). To locate the bad setState() call insideProtectedRoute, follow the stack trace as described. Why am I seeing this error and how can I fix it?
The error is because on initial render phase, you render the component with setIsDashboard(true);, usually, you want to do it on mount (useEffect with empty dep array).
There is an initial render phase, then mount phase, see component's lifecycle diagram.
Be sure that setIsDashboard is persistent, meaning it is created by React API (like useState).
Or its memoized with useMemo/useCallback, or you will get inifite loop because on every render a new instance of setIsDashboard will be created and the dep array ([setIsDashboard]) will cause another execution.
const ProtectedRoute = ({ component }) => {
const { setIsDashboard } = useContext(DashboardContext);
// Make sure `setIsDashboard` persistant
useEffect(() => {
setIsDashboard(true);
}, [setIsDashboard]);
...
};
The error is because you can't set state when you are rendering:
dashboardContext.setIsDashboard(true); is probably the problem.
You don't post your stack trace or line numbers so it's hard to tell exactly what the issue is:
https://stackoverflow.com/help/minimal-reproducible-example

HoC with React Hooks

I'm trying to port from class component to react hooks with Context API, and I can't figure out what is the specific reason of getting the error.
First, my Codes:
// contexts/sample.jsx
import React, { createContext, useState, useContext } from 'react'
const SampleCtx = createContext()
const SampleProvider = (props) => {
const [ value, setValue ] = useState('Default Value')
const sampleContext = { value, setValue }
return (
<SampleCtx.Provider value={sampleContext}>
{props.children}
</SampleCtx.Provider>
)
}
const useSample = (WrappedComponent) => {
const sampleCtx = useContext(SampleCtx)
return (
<SampleProvider>
<WrappedComponent
value={sampleCtx.value}
setValue={sampleCtx.setValue} />
</SampleProvider>
)
}
export {
useSample
}
// Sends.jsx
import React, { Component, useState, useEffect } from 'react'
import { useSample } from '../contexts/sample.jsx'
const Sends = (props) => {
const [input, setInput ] = useState('')
const handleChange = (e) => {
setInput(e.target.value)
}
const handleSubmit = (e) => {
e.preventDefault()
props.setValue(input)
}
useEffect(() => {
setInput(props.value)
}, props.value)
return (
<form onSubmit={handleSubmit}>
<input value={input} onChange={handleChange} />
<button type="submit">Submit</button>
</form>
)
}
Error I got:
Invariant Violation: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons: 1. You might have mismatching versions of React and the renderer (such as React DOM) 2. You might be breaking the Rules of Hooks 3. You might have more than one copy of React in the same app See https://reactjs.org/warnings/invalid-hook-call-warning.html for tips about how to debug and fix this problem.
Explanation for my code:
I used Context API to manage the states, and previously I used class components to make the views. I hope the structure is straightforward that it doesn't need any more details.
I thought it should work as well, the <Sends /> component gets passed into useSample HoC function, and it gets wrapped with <SampleProvider> component of sample.jsx, so that <Sends /> can use the props provided by the SampleCtx context. But the result is failure.
Is it not valid to use the HoC pattern with React hooks? Or is it invalid to hand the mutation function(i.e. setValue made by useState()) to other components through props? Or, is it not valid to put 2 or more function components using hooks in a single file? Please correct me what is the specific reason.
So HOCs and Context are different React concepts. Thus, let's break this into two.
Provider
Main responsibility of the provider is to provide the context values. The context values are consumed via useContext()
const SampleCtx = createContext({});
export const SampleProvider = props => {
const [value, setValue] = useState("Default Value");
const sampleContext = { value, setValue };
useEffect(() => console.log("Context Value: ", value)); // only log when value changes
return (
<SampleCtx.Provider value={sampleContext}>
{props.children}
</SampleCtx.Provider>
);
};
HOC
The consumer. Uses useContext() hook and adds additional props. Returns a new component.
const withSample = WrappedComponent => props => { // curry
const sampleCtx = useContext(SampleCtx);
return (
<WrappedComponent
{...props}
value={sampleCtx.value}
setValue={sampleCtx.setValue}
/>
);
};
Then using the HOC:
export default withSample(Send)
Composing the provider and the consumers (HOC), we have:
import { SampleProvider } from "./provider";
import SampleHOCWithHooks from "./send";
import "./styles.css";
function App() {
return (
<div className="App">
<SampleProvider>
<SampleHOCWithHooks />
</SampleProvider>
</div>
);
}
See Code Sandbox for full code.
Higher order Components are functions that takes a Component and returns another Component, and the returning Components can be class component, a Functional Component with hooks or it can have no statefull logic.
In your example you're returning jsx from useSample.
const useSample = (WrappedComponent) => {
const sampleCtx = useContext(SampleCtx)
return ( // <-- here
<SampleProvider>
<WrappedComponent
value={sampleCtx.value}
setValue={sampleCtx.setValue} />
</SampleProvider>
)
}
if you want to make a HOC what you can do is something like this
const withSample = (WrappedComponent) => {
return props => {
const sampleCtx = useContext(SampleCtx)
<WrappedComponent
value={sampleCtx.value}
setValue={sampleCtx.setValue} {...props} />
}
}

Higher Order Component in DOM to Wrap functionality

I need to wrap functionality in a, lets say button. However when I call the HOC in the render method of another component I get nothing.
I have this HOC
import React,{Component,PropTypes} from 'react';
export let AddComment = (ComposedComponent) => class AC extends React.Component {
render() {
return (
<div class="something">
Something...
<ComposedComponent {...this.props}/>
</div>
);
}
}
and trying to do this
import {AddComment} from '../comments/add.jsx';
var Review = React.createClass({
render: function(){
return (
<div className="container">
{AddComment(<button>Add Comment</button>,this.props)}
</div>
});
module.exports = Review;
I want AddComment to open a Dialog and submit a comments form when I click the button. I need AddComment to be available other components throughtout the app.
Is the HOC pattern correct? How can I easily accomplish this?
Thanks
To summarize really quick: What are higher-order components?
Just a fancy name for a simple concept: Simply put: A component that takes in a component and returns you back a more enhanced version of
the component.
We are essentially enhancing a component.
Accepts a function that maps owner props to a new collection of props
that are passed to the base component.
We are basically passing the props down from that BaseComponent down
to the Wrapped Component so that we can have them available in that
child component below:
Use to compose multiple higher-order components into a single
higher-order component.
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { AddComment } from '../comments/add.jsx';
const mapProps = propFunction => Component => (props) => {
return React.createFactory(Component)(propFunction(props));
};
const compose = (propFunction, ComponentContainer) => (BaseComponent) => {
return propFunction(ComponentContainer(BaseComponent));
};
const Review = AddComment(({ handleReviewToggle }) => (
<div className="container">
<ReviewButton
primaryText="Add Comment"
_onClick={handleReviewToggle}
/>
</div>
));
export default Review;
// ================================================================== //
const EnhanceReview = compose(withProps, AddComment)(Review);
const withProps = mapProps(({ ...props }) => ({ ...props }));
The AddComment Container that will have the button and the dialog itself.
export function AddComment(ComposedComponent) {
class AC extends React.Component {
constructor() {
super();
this.state = {open: false};
}
handleReviewToggle = () => {
this.setState({ open: !this.state.open })
}
render() {
return (
<ComposedComponent
{...this.props}
{...this.state}
{...{
handleReviewToggle: this.handleReviewToggle,
}}
/>
);
}
}
export default AddComment;
// ==================================================================
The ReviewButton Button that will fire an event to change state true or false.
const ReviewButton = ({ _onClick, primaryText }) => {
return (
<Button
onClick={_onClick}
>
{primaryText || 'Default Text'}
</Button>
);
};
export default ReviewButton;
// ================================================================== //
However this was all done without using a library. There's one out called recompose here: https://github.com/acdlite/recompose. I highly suggest that you try it out without a library to get a good understanding of Higher Order Components.
You should be able to answer these questions below after playing with Higher Order components:
What is a Higher Order Component?
What are the disadvantages of using HOC? What are some use cases?
How will this improve performance? And how can I use this to optimize for performance?
When is the right time to use a HOC?

React & Redux : connect() to multiple components & best practices

I'm working on my first React/Redux project and I have a little question. I've read the documentation and watched the tutorials available at https://egghead.io/lessons/javascript-redux-generating-containers-with-connect-from-react-redux-visibletodolist.
But I still have one question. It's about a login page.
So I have a presentational component named LoginForm :
components/LoginForm.js
import { Component, PropTypes } from 'react'
class LoginForm extends Component {
render () {
return (
<div>
<form action="#" onSubmitLogin={(e) => this.handleSubmit(e)}>
<input type="text" ref={node => { this.login = node }} />
<input type="password" ref={node => { this.password = node }} />
<input type="submit" value="Login" />
</form>
</div>
)
}
handleSubmit(e) {
e.preventDefault();
this.props.onSubmitLogin(this.login.value, this.password.value);
}
}
LoginForm.propTypes = {
onSubmitLogin: PropTypes.func.isRequired
};
export default LoginForm;
And a container component named Login which pass data to my component. Using react-redux-router, I call this container (and not the presentationnal component) :
containers/Login.js
import { connect } from 'react-redux'
import { login } from '../actions/creators/userActionCreators'
import LoginForm from '../components/LoginForm'
const mapDispatchToProps = (dispatch) => {
return {
onSubmitLogin: (id, pass) => dispatch(login(id, pass))
}
};
export default connect(null, mapDispatchToProps)(LoginForm);
As you can see, I'm using the connect method provide by redux to create my container.
My question is the following one :
If I want my Login container to use multiple views (for example : LoginForm and errorList to display errors), I need to do it by hand (without connect because connect take only one argument). Something like :
class Login extends Component {
render() {
return (
<div>
<errorList />
<LoginForm onSubmitLogin={ (id, pass) => dispatch(login(id, pass)) } />
</div>
)
}
}
Is it a bad practice ? Is it better to create another presentational component (LoginPage) which use both errorList and LoginForm and create a container (Login) which connect to LoginPage ?
EDIT: If I create a third presentational component (LoginPage), I'll have to pass data twice. Like this : Container -> LoginPage -> LoginForm & ErrorList.
Even with context, it don't seems to be the way to go.
I think that what you have in your second example is very close. You can create just one container component that's connected and render multiple presentational components.
In your first example, there actually isn't a separate container component:
import { connect } from 'react-redux'
import { login } from '../actions/creators/userActionCreators'
import LoginForm from '../components/LoginForm'
const mapDispatchToProps = (dispatch) => {
return {
onSubmitLogin: (id, pass) => dispatch(login(id, pass))
}
};
// `LoginForm` is being passed, so it would be the "container"
// component in this scenario
export default connect(null, mapDispatchToProps)(LoginForm);
Even though it's in a separate module, what you're doing here is connecting your LoginForm directly.
Instead, what you can do is something like this:
containers/Login.js
import { connect } from 'react-redux'
import { login } from '../actions/creators/userActionCreators'
import LoginForm from '../components/LoginForm'
import ErrorList from '../components/ErrorList'
class Login extends Component {
render() {
const { onSubmitLogin, errors } = this.props;
return (
<div>
<ErrorList errors={errors} />
<LoginForm onSubmitLogin={onSubmitLogin} />
</div>
)
}
}
const mapDispatchToProps = (dispatch) => {
return {
onSubmitLogin: (id, pass) => dispatch(login(id, pass))
}
};
const mapStateToProps = (state) => {
return {
errors: state.errors
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Login);
Note that the Login component is now being passed to connect, making it the "container" component and then both the errorList and LoginForm can be presentational. All of their data can be passed via props by the Login container.
I truly believe that you need to build all your components as Presentational Components. At the moment you need a Container Component, you might use {connect} over one of the existing Presentational and convert into a Container one.
But, that is only my view with short experience in React so far.

Categories