I'm dealing with a mix of function components and class components. Every time a click happens in the NavBar I want to trigger the function to validate the Form, it has 5 forms, so each time I'm going to have to set a new function inside the context API.
Context.js
import React, { createContext, useContext, useState } from "react";
const NavigationContext = createContext({});
const NavigationProvider = ({ children }) => {
const [valid, setValid] = useState(false);
const [checkForm, setCheckForm] = useState(null);
return (
<NavigationContext.Provider value={{ valid, setValid, checkForm, setCheckForm }}>
{children}
</NavigationContext.Provider>
);
};
const useNavigation = () => {
const context = useContext(NavigationContext);
if (!context) {
throw new Error("useNavigation must be used within a NavigationProvider");
}
return context;
};
export { NavigationProvider, useNavigation, NavigationContext};
Form.js
import React, { Component } from "react";
import { NavigationContext } from "../hooks/context";
class Something extends Component {
static contextType = NavigationContext;
onClickNext = () => {
// This is the funcion I want to set inside the Context API
if(true){
return true
}
return false;
};
render() {
const { setCheckForm } = this.context;
setCheckForm(() => () => console.log("Work FFS"));
return (
<>
<Button
onClick={this.onClickNext}
/>
</>
);
}
}
export default Something;
The problem when setting the function it throws this error:
Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
And setting like setCheckForm(() => console.log("Work FFS"));, it triggers when rendered.
Render method of React.Component runs whenever state changes and setCheckForm updates the state whenever that render happens. This creates an infinite loop, this is the issue you are having there.
So, this is a lifecycle effect, you have to use that function inside componentDidMount if you want to set it when the component first loads.
While this solves your problem, I wouldn't suggest doing something like this. React's mental model is top to bottom, data flows from parent to child. So, in this case, you should know which component you are rendering from the parent component, and if you know which component to render, that means you already know that function which component is going to provide to you. So, while it is possible in your way, I don't think it is a correct and Reactish way to handle it; and it is probably prone to break.
Related
I am currently stuck employing React's useEffect Hook. The project I am working on is quite complex, but I will try to break it down to the relevant parts in order to describe my issue.
A) The Child Component
import React, {useEffect, useState} from "react";
const ChildComponent = props => {
const [needsConfirmation, setNeedsConfirmation] = useState(true);
useEffect(() => {
setNeedsConfirmation(
!!props.someObject.someArrayOfObjects.find(({status}) => status === "added")
);
}, [props.someObject]);
// I SKIP THE RETURN PART
}
B) Inside The Parent Component
const ParentComponent = props => {
const renderChildComponent = someObject => {
return (
<ChildComponent
someObject={someObject}
/>
) : null;
};
return (
<>
props.someArrayOfObjectsHandledByRedux.map(renderChildComponent)
</>
)
}
C) The Problem
Inside the child component I would like the useEffect hook to take effect every time something about the property props.someObject changes. The latter one is an app-wide state handled by Redux. Of course, I made sure that the corresponding reducer function always returns a brand new object after an action has taken place.
The relevant code inside the reducer function looks something like this:
case ActionTypes.SOME_ACTION_SUCCESS:
return {
...state,
someObject: formattedResponse(data),
};
However, no matter what I did, I just cannot get the useEffect to take effect. Using the ReduxDevTools I could clearly verify that the reducer function has successfully returned a new state object. Nevertheless, the useEffect hook inside the child component did not recognize any changes in the dependencies.
For now, I have implemented a very ugly workaround by always creating a brand new object which is handed to the child component
const renderChildComponent = someObject => {
return (
<ChildComponent
someObject={{...someObject}}
/>
) : null;
};
But this is very inefficient as it automatically re-renders all child components, even though, typically, only the data of one particular child component might have changed.
Does anybody have an idea what the problem in this particular case might be?
Thanks a lot for your input in advance.
I call useEffect inside useContext and I want to know when this useEffect is called.
[settingContext.tsx]
// create context object
export const ColorContext = createContext<ColorContextType>(null);
export const ProductsProvider = (props) => {
const { query } = useRouter();
const [data, setData] = useState<ColorContextType>(null);
useEffect(() => {
async function fetchAPI() {
const res = await fetch(`${env.API_URL_FOR_CLIENT}/page_settings/top`);
const posts = await res.json();
setData(posts);
}
fetchAPI();
}, []);
return <ColorContext.Provider value={data}>{props.children}</ColorContext.Provider>;
};
export const useColorContext = () => {
const colors = useContext(ColorContext);
let themeColor: string = '';
let titleColor: string = '';
if (colors !== null) {
const colorData = colors.response.result_list[3].value;
themeColor = JSON.parse(colorData).theme_color;
titleColor = JSON.parse(colorData).title_color;
}
return { themeColor, titleColor };
};
[_app.tsx]
export default function MyApp({ Component, pageProps }: AppProps) {
return (
<LayoutInitial>
<ProductsProvider>
<Component {...pageProps} />
</ProductsProvider>
</LayoutInitial>
);
}
I use useColorContext on multiple components.
It seems like useEffect is only called on '/' page, which is fine but I'm curious that useEffect should be called every time pages are rendered but it seems that it doesn't.
is this because I use useContext?
The useEffect call is done in the ProductsProvider component which appears to only be rendered once, on page load/refresh. This is because components generally only re-render when a state they subscribe to is changed. If the useEffect were called directly within the <Component> component, it would be called every time the component is mounted (not in re-renders). useEffect is only called multiple times after mounting if one of its dependencies changes, which in your case, there are none.
For example: this sandbox
It's composed of the App component, containing a Router to a homepage, a ComponentA route, and a ComponentB route. When each component mounts, its useEffect is called, creating an alert box. You'll only see the App useEffect alert once per page refresh.
ComponentA will have its useEffect called when the component mounts (every time you hit the /a route from a different route), and when the state in the component changes, since it's in the useEffect dependency array.
ComponentB will only have its useEffect called when the component mounts, and not when its state changes, because the state isn't included in the useEffect dependency array.
EDIT: To clarify, your useColorContext hook is not actually part of the ProductsProvider component, so the useEffect call is not "inherited" by any components that call the hook. Also, keep in mind when experimenting that using Strict Mode will cause components to render twice, allowing react to gather information on the first render, and display it on the second render.
(Edit: I found something else wrong with the code and fixed it. So this question becomes irrelevant.)
How to re-render the MyComponent component when const language changes? Now it works only when I switch the tabs, but I need it working when the language is changed in another component.
import React from "react";
import {useSelector} from "react-redux";
export default function MyComponent(props: any) {
const language: string = useSelector((state: any) => state.config.language);
......
}
Like said, if you are using redux properly it should alrealdy be re-rendering...
But if it dont, in a last case, you can force a re-render in the component by setting a key in parent div and changing its values whenever it config languague changes.
By request an example:
const [rerenderedComponent, setRerenderedComponent] = useState(1);
const forceUpdate = () => {
return setRerenderedComponent(prev => prev + 1);
}
return (
<div onClick={forceUpdate} key={rerenderedComponent}>
...
</div>
)
I'm trying to learn and test React.PureComponent and it keeps rendering even though no state changes for that pure component.
My PureComponent is very simple and it accepts only one Redux Action function via connect hoc
import React from 'react';
import {
Container,
Button
} from 'reactstrap'
import { connect } from 'react-redux'
import { resetWorkouts } from '../actions/workoutApiActions'
class About extends React.PureComponent {
render () {
const { resetWorkouts } = this.props;
console.log('in about render...')
return (
<React.Fragment>
<Container>
<h2>Api Data Reset</h2>
<Button color="danger" onClick={resetWorkouts}>Reset Data</Button>
</Container>
</React.Fragment>
);
}
}
const mapDispatchToProps = dispatch => {
return {
resetWorkouts: () => dispatch(resetWorkouts())
}
}
export default connect(null, mapDispatchToProps)(About);
In the above code, you can see that, there is no state in the component. It only accepts the action function as props from connect. However, whenever I clicks on the Reset Data button, it keeps calling the render method as shown in the screenshot.
In the screenshot, I can see that, global state store has been changed whenever, I click a button. But, that state is not used in my PureComponent and it should be out of the scope and my component should ignore to re-render.
Or Redux Action functions are created every time the global state store has been changed. And passed as a new object to my PureComponent ?
Theoretically, I don't need to write my own shouldComponentUpdate function, right? I'm confused and could you please help me to understand about this behaviour?
My goal is I don't want my PureComponent to render again when a user clicks a button.
Updates:
I have tried like the following according to this article and it's still re-rendering
const mapDispatchToProps = {
resetWorkouts
};
this because react do a shallow comparison between the prevProps and the nextProps,
and you can control that only in the shouldComponentUpdate, react doesn't know that the dispatcher is the same one from the previous render, because you are using return inside the mapDispatchToProps function.
In your component and in your case, while the function will remain the same, you can go with two paths:
path 1:
override the shouldComponentUpdate life cycle hook, to be as the following:
shouldComponentUpdate(){
return false;
}
path 2:
get rid of the return inside mapDispatchToProps and simplify the connect so it be as the following:
`conncect(state => ({}), {
resetWorkouts: resetWorkouts})(YourComponent);`
using one of the above paths should make you good to go
The reason why your component is rendering is because everytime the following function executes:
const mapDispatchToProps = dispatch => {
return {
resetWorkouts: () => dispatch(resetWorkouts())
}
}
your components receives a new instance of a property named resetWorkouts(because you're creating an inline array function). You may look at the ownProps to check if your component already have the resetWorkouts:
const mapDispatchToProps = (dispatch, ownProps) => {
return {
resetWorkouts: ownProps.resetWorkouts || () => dispatch(resetWorkouts())
}
}
Given this component:
import React from 'react'
import ReactDOM from 'react-dom'
import PropTypes from 'prop-types'
const NewGoalInput = props => {
return (
<input type="text" onKeyUp={handleKeyUp}/>
)
}
const handleKeyUp = (e) => {
if (e.key === "Enter") {
// TODO Add goal
}
}
export default NewGoalInput
How do I add a constructor where I can define the state without using the extends React.Component syntax?
Since it's a stateless component it doesn't have the component lifecycle.
Therefor you can't specify a constructor.
You have to extend React.Component to create a stateful component which then will need a constructor and you'll be able to use the state.
Update
Since React 16.8.0 and Hooks got introduced there are more options.
Hooks are a new feature proposal that lets you use state and other React > features without writing a class. They are released in React as a part of > v16.8.0
Stateless:
import React from "react"
const Stateless = ({name}) => (
<div>{`Hi ${name}`}</div>
);
Stateful:
Has access to component lifecycle methods and local state.
class Stateful extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
componentDidMount() {
const { count } = this.state;
document.title = `You've clicked ${count} times.`;
}
componentDidUpdate() {
const { count } = this.state;
document.title = `You've clicked ${count} times.`;
}
render() {
const { count } = this.state;
return (
<div>
<p>You've clicked {count} times.</p>
<button onClick={() => this.setState({ count: count + 1 })}>
Click me
</button>
</div>
);
}
}
Using Hooks:
Able to use State Hook and Effect Hook.
If you’re familiar with React class lifecycle methods, you can think of useEffect Hook as componentDidMount, componentDidUpdate, and componentWillUnmount combined.
import React, { useState, useEffect } from "react";
const UsingHooks = () => {
const [count, setCount] = useState(0);
// Similar to componentDidMount and componentDidUpdate:
useEffect(() => {
// Update the document title using the browser API
document.title = `You've clicked ${count} times.`;
});
return (
// <> is a short syntax for <React.Fragment> and can be used instead of a wrapping div
<>
<p>You've clicked {count} times.</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</>
);
}
Now that we have useState and hooks the answers are kind of out of date. I came across this question because I was doing something wrong. Here's some simplified code of what I was doing.
// set an initial state
const [ value, setValue ] = useState(0)
// gets called after component is re-rendered
useEffect(() => {
// callback to parent that set props
props.update()
})
// if we have an existing value passed in
if (props.value) {
setValue(props.value)
}
This code was converted from a stateful class to a function using hooks, originally setting the default props in the constructor - but functions don't have constructors and that check happens every time the component re-renders:
calls useState
triggers re-render
useEffect is triggerd
parent is called which sets the props
props update so child renders again
GOTO 1
As you can see this results in an infinite loop. The solution is really quite simple. Here's a mock diff from the original.
- const [ value, setValue ] = useState(0)
+ const [ value, setValue ] = useState(props.value || 0)
- if (props.value) {
- setValue(props.value)
- }
Basically, just initialise the state from the props and don't do silly things like calling useState except in response to an event or callback of some type.
You can use useMemo hook (as below) to demonstrate as constructor for functional component. Somebody suggested to use useEffect but it will be invoked after render.
useMemo(() => {
console.log('This is useMemo')
}, []);
you could set a useState as the first line inside of your functional component and add a function as "initial value":
const MyComponentName = props => {
useState(() => {
console.log('this will run the first time the component renders!');
});
return <div>my component!</div>;
};
You don't. The kind of component in your example is called "stateless functional component". It has no state and no lifecycle methods. If you want your component to be stateful you'll have to write it as a class component.
To simulate constructor in FC use useEffect.
useEffect(() => {
... here your init code
}, []);
That's it! EZ! This useEffect runs only once when the component loads and never runs after, just don't forget to add square brackets at the end.
For those who want to run a function once before the component is mounted, here is a hook (written in TypeScript).
Normally useEffect and useLayoutEffect suffice, but they run after the component is mounted, and sometimes you want to run code before that happens (like a constructor).
import React, { useRef } from "react";
function useOnce<Type>(callBack: () => Type): Type {
const result = useRef<Type | null>(null);
if (result.current !== null) {
return result.current;
}
result.current = callBack();
return result.current;
}
const Component: React.FC<{}> = () => {
const result = useOnce(() => {/* Code you would normally put in a constructor */});
return <div />
}
Alternatively, you can use react-afc
import { afc, reactive } from 'react-afc'
function heavyCalc() {/*...*/}
const Conponent = afc(props => {
// Called once, before the first render
const state = reactive({
name: 'Stack',
inputsCount: 0
})
// Without useMemo(..., [])
const result = heavyCalc()
// The function is created once and does not cause
// a re-render of child components
function onInput(e) {
state.inputsCount++
state.name = e.currentTarget.value
}
// Saved between renders (no longer need useRef)
let rendersCount = 0
// Must return the render-function
return () => {
// The function works like a regular react-component
// Here you can use the usual hooks
rendersCount++
return (
<input onChange={onInput} value={state.name}/>
)
}
})
The package has the necessary methods for working with state (including redux), react-hooks, lifecycle methods and context