When does useEffect call when I use it with useContext? - reactjs

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.

Related

Why is the updated state variable not shown in the browser console?

I understand that the method returned by useState is asynchronous, however, when I run the this code, I am delaying the console.log by upto 5 seconds, but it still logs the previous value and not the updated value of the state variable. The updated value would be 2, but it still logs 1. In the react developer tools however, I can see the state changing as I press the button, though I am curious to know why after even such a delay the console prints an obsolete value? This is not the case with class components and setState but with function components and useState.
import "./App.css";
import React, { useState, useEffect } from "react";
function App() {
const [variable, setVariable] = useState(1);
const handleClick = () => {
setVariable(2);
setTimeout(() => {
console.log(variable);
}, 2000);
};
return <button onClick={handleClick}>Button</button>;
}
export default App;
In your code your setTimeout is getting the variable value from the closure at the time it was invoked and the callback function to the setTimeout was created. Check this GitHub issue for the detailed explanation.
In the same issue, they talk about utilizing useRef to do what you are attempting. This article by Dan Abramov packages this into a convenient useInterval hook.
State updates are asynchronous. That means, that in order to view the new value, you need to log It on the next render using useEffect and adding it to the dependencies array:
In this example, give a look at the order the logs appear:
First, you will have the current one, and once triggered, you will have the new value, and then it will become the 'old value' until triggered again.
import "./App.css";
import React, { useState, useEffect } from "react";
function App() {
const [counter, setCounter] = useState(0);
useEffect(() => { console.log(`new state rolled: ${counter}`);
}, [counter]);
console.log(`Before rolling new State value: ${counter}`);
const handleClick = () => setCounter(counter++)
return <button onClick={handleClick}>Button</button>;
}
export default App;
Another technic to console.log a value afterward a state change is to attach a callback to the setState:
setCounter(counter++, ()=> console.log(counter));
I hope it helps.
A state take some time to update. The proper way to log state when it updates, is to use the useEffect hook.
setTimeout attaches the timer and wait for that time, but it will keep the value of variable from the beginning of the timer, witch is 1
import "./App.css";
import React, { useState, useEffect } from "react";
function App() {
const [variable, setVariable] = useState(1);
const handleClick = () => {
setVariable(2);
};
useEffect(() => {
console.log(variable);
}, [variable]);
return <button onClick={handleClick}>Button</button>;
}
export default App;
This is not the case with class components and setState but with
function components and useState
In class components, React keep the state in this.state & then call the Component.render() method whenever its need to update due to a setState or prop change.
Its something like this,
// pseudocode ( Somewhere in React code )
const app = MyClassComponent();
app.render();
// user invoke a callback which trigger a setState,
app.setState(10);
// Then React will replace & call render(),
this.state = 10;
app.render();
Even though you cannot do this.state = 'whatever new value', React does that internally with class components to save the latest state value. Then react can call the render() method and render method will receive the latest state value from this.state
So, if you use a setTimeout in a class component,
setTimeout(() => {
console.log(this.state) // this render the latest value because React replace the value of `this.state` with latest one
}, 2000)
However in functional component, the behaviour is little bit different, Every time when component need to re render, React will call the component again, And you can think the functional components are like the render() method of class components.
// pseudocode ( Somewhere in React code )
// initial render
const app = MyFuctionalComponent();
// state update trigger and need to update. React will call your component again to build the new element tree.
const app2 = MyFunctionalComponent();
The variable value in app is 1 & variable value in app2 is 2.
Note: variable is just a classic variable which returned by a function that hooked to the component ( The value save to the variable is the value return by the hook when the component was rendering so it is not like this.state i.e its hold the value which was there when the component is rendering but not the latest value )
Therefore according to the Clouser, at the time your setTimeout callback invoke ( Which was called from app ) it should log 1.
How you can log the latest value ?
you can use useEffect which getting invoke once a render phase of a component is finished. Since the render phase is completed ( that mean the local state variables holds the new state values ) & variable changed your console log will log the current value.
useEffect(() => {
console.log(variable);
}, [variable])
If you need the behaviour you have in class components, you can try useRef hook. useRef is an object which holds the latest value just like this.state but notice that updating the value of useRef doesn't trigger a state update.
const ref = useRef(0);
const handleClick = () => {
setVariable(2); // still need to setVariable to trigger state update
ref.current = 2 // track the latest state value in ref as well.
setTimeout(() => {
console.log(ref.current); // you will log the latest value
}, 2000);
};

Life cycle of props with state in react

I couldn't find any clear information or video about when the props are received in the react component life-cycle. Let's make an example:
export const App = () => {
const [count, setCount] = useState(0);
console.log(A, count)
return (
<Component count={count} justText={'hello'} onClick={() => setState(state => state + 1)}/>
);
}
const Component = ({count, justText}) => {
const [count2, setCount2]=useState(0)
console.log("B ",count)
console.log("C ",justText)
console.log("D",count2)
useEffect(()=> {
console.log("F", count)
console.log("G",count2)
});
return (
<>
{console.log(E,count)}
</>
)
}
I would like to know the execution order and why is that, because sometimes I found that when passing props with state down, the component which received them gets undefined but in the component where it is declared it is already initialized. Sometimes I need to wait the useEffect to get the value, so I am lost in what happens with the props during the lifecycle when mounting, and updating. I tried to find information about it but couldn't see anywhere where they explain the props lifecycle.
Thank you!
PD: Just setted random values for A,B,C...they don't follow any order.
You won't get undefined while console logging the props in the function or console logging the state :
import React from "react";
function Component() {
const [state, setState] = React.useState(false);
console.log(state);
return <Other state={state}></Other>;
}
export default Component;
import React from "react";
function Other({ state }) {
console.log(state);
React.useEffect(() => {
console.log(state);
}, []);
return <div>Other component</div>;
}
export default Other;
React components gets states and props before mounting so they will never be undefined when you want to use them .
TLDR
Lifecycle of a props starts when it gets mount and stays until it gets called or re-rendered by the parent that is using it. When it re-rendered by the parent it gets the props again but it can be unchanged or changed.
Full story
The lifecycle of a props is very straight forward. When your component is being called from a Parent it's props is also passed at that time. Think props as the function parameter. Suppose you have a function like below
const fn = (param1, param2) => {
// your function implementation
}
Here when you call the function you call it with the params like this
fn(val1, val2)
So each time you call the function you pass the params. If any time calling the function any of the param is not passed it will get null
A component and it's props is also same. Here the Component is the function and the props are the params. Every time the components gets called or rendered it is called or rendered with the props.
Now, If your parent that is using the component, pass all the props then the component will get the value. But if it doesn't pass all the props it will get null.
Like function you can also define the initial value of a props like this
const Component1 = ({props1='val1', props2='val2', ...rest}) => {
return()
}
In the code above even if you do not pass props1 and props2 from the parent. Still it will not be null, it will have the initial value.

Pass function to Context API

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.

React Component should mount previous state before fetching new data

DataContext.js
export const DataContext = createContext()
const DataContext = ({ children }) => {
const [userValue, setUserValue] = useState()
const [user, setUser] = useState()
const fetch = useCallback(async () => {
const response = await axios(Url)
setBalance(response.data)
}
}, [])
usePrevious.js
import { useEffect, useRef } from 'react'
function usePrevious(value) {
const ref = useRef()
useEffect(() => {
ref.current = value
})
return ref.current
}
export default usePrevious
UsersData Component
import { DataContext } from '../context/DataContext'
import usePrevious from './usePrevious'
const UsersValue = () => {
const { userValue} = useContext(DataContext)
const prevData = usePrevious(useValue)
useEffect(() => {
console.log('prevData ', prevData)
console.log('userValue', userValue)
}, [useValue, prevData])
return (....)
Following the React documentation using usePrevious example.
When I render the component for the first time it takes few sec to load the data(async), this is fine, but if I route away to another link and come back, I still get the component loading the data and I want to fetch the previous state if the data has not changed
I was specting the console.log for prevData to show the previous state(when linked back to component) while component is fetching, but is just showing the same for userValue, both have the same state undefined, than data comes is available
How can I render a memorized state for the UsersData Component,to avoid loading all the time when component is mounting, I start looking into react.memo, is this the direction I need to take?
If so how can I implement React.memo in this case, thx
I don't think usePrevious will help you if you are unmounting and re-mounting the component, the ref will be recycled. You could pass down the fetch function from the context, and call it in the child component when it mounts. This fetch would run in the context, and if there is a difference between the new and data stored in the context, update the userValue using setUserValue in the context. This change would then be passed down to the child after the state has been updated. If they are the same, do not call setUserValue and no update will occur.
The problem here is if there is a new value, the child component won't know until the fetch has finished. You may be displaying old data off the bat, but if you don't tell the user you are fetching, the child component may update unexpectedly when the fetch is finished and the context is updated.
Caching is difficult, you may just want to always provide a fresh value.
If you decide to go this route, you will need to check if the data you fetched has changed. If the data is an object and doesn't have functions added to it, a quick way is to use JSON.stringify on both objects and compare the stringified values.

Prevent re-rendering when state is changed multiple times during useEffect

In my React app, I will implement useState multiple times in a single component. Then in my useEffect I will change the state of several of these:
import React, { useState, useEffect } from 'react';
const Projects = React.memo(function (props) {
const [someState1, setSomeState1] = useState(false);
const [someState2, setSomeState2] = useState(false);
const [someState3, setSomeState3] = useState(false);
useEffect(() => {
if (someConditionMet) {
setSomeState1(true);
setSomeState2(true);
setSomeState3(true);
}
});
if (initialized) {
return <div>Hello World</div>;
});
What I notice is that each time setSomeState1, setSomeState2, setSomeState3 is called, the entire component gets re-rendered for each of these calls. I really only want it to re-render once when useEffect has completed. Is there a way in React to prevent the rendering from happening multiple times when multiple states are changed within useEffect?
You will need to add a dependency array to your useEffect so that the condition will be called only once
useEffect(() => {
if (someConditionMet) {
setSomeState1(true);
setSomeState2(true);
setSomeState3(true);
}
},[]);
Try useRef instead useState , that won't cause rendering multiple times.

Resources