React Context is not working as expected: Unable to change the value of shared variable - reactjs

I made a context to share the value of the variable "clicked" throughout my nextjs pages, it seems to give no errors but as you can see the variable's value remains FALSE even after the click event. It does not change to TRUE. This is my first time working with context, what am I doing wrong?
I'm using typescript
PS: After the onClick event the log's number shoots up by 3 or 4, is it being executed more than once, but how?
controlsContext.tsx
import { createContext, FC, useState } from "react";
export interface MyContext {
clicked: boolean;
changeClicked?: () => void;
}
const defaultState = {
clicked: false,
}
const ControlContext = createContext<MyContext>(defaultState);
export const ControlProvider: FC = ({ children }) => {
const [clicked, setClicked] = useState(defaultState.clicked);
const changeClicked = () => setClicked(!clicked);
return (
<ControlContext.Provider
value={{
clicked,
changeClicked,
}}
>
{children}
</ControlContext.Provider>
);
};
export default ControlContext;
Model.tsx
import ControlContext from "../contexts/controlsContext";
export default function Model (props:any) {
const group = useRef<THREE.Mesh>(null!)
const {clicked, changeClicked } = useContext(ControlContext);
const handleClick = (e: MouseEvent) => {
//e.preventDefault();
changeClicked();
console.log(clicked);
}
useEffect(() => {
console.log(clicked);
}, [clicked]);
useFrame((state, delta) => (group.current.rotation.y += 0.01));
const model = useGLTF("/scene.gltf ");
return (
<>
<TransformControls enabled={clicked}>
<mesh
ref={group}
{...props}
scale={clicked ? 0.5 : 0.2}
onClick={handleClick}
>
<primitive object={model.scene}/>
</mesh>
</TransformControls>
</>
)
}
_app.tsx
import {ControlProvider} from '../contexts/controlsContext';
function MyApp({ Component, pageProps }: AppProps) {
return (
<ControlProvider>
<Component {...pageProps}
/>
</ControlProvider>
)
}
export default MyApp

Issues
You are not actually invoking the changeClicked callback.
React state updates are asynchronously processed, so you can't log the state being updated in the same callback scope as the enqueued update, it will only ever log the state value from the current render cycle, not what it will be in a subsequent render cycle.
You've listed the changeClicked callback as optional, so Typescript will warn you if you don't use a null-check before calling changeClicked.
Solution
const { clicked, changeClicked } = useContext(ControlContext);
...
<mesh
...
onClick={(event) => {
changeClicked && changeClicked();
}}
>
...
</mesh>
...
Or declare the changeClicked as required in call normally. You are already providing changeClicked as part of the default context value, and you don't conditionally include in in the provider, so there's no need for it to be optional.
export interface MyContext {
clicked: boolean,
changeClicked: () => void
}
...
const { clicked, changeClicked } = useContext(ControlContext);
...
<mesh
...
onClick={(event) => {
changeClicked();
}}
>
...
</mesh>
...
Use an useEffect hook in to log any state updates.
const { clicked, changeClicked } = useContext(ControlContext);
useEffect(() => {
console.log(clicked);
}, [clicked]);
Update
After working with you and your sandbox it needed a few tweaks.
Wrapping the index.tsx JSX code with the ControlProvider provider component so there was a valid context value being provided to the app. The UI here had to be refactored into a React component so it could itself also consume the context value.
It seems there was some issue with the HTML canvas element, or the mesh element that was preventing the Modal component from maintaining a "solid" connection with the React context. It wasn't overtly clear what the issue was here, but passing the context values directly to the Modal component as props resolved the issue with the changeClicked callback becoming undefined.

A few things -
setClicked((prev) => !prev);
instead of
setClicked(!clicked);
As it ensures it's not using stale state. Then you are also doing -
changeClicked
But it should be -
changeClicked();
Lastly, you cannot console.log(clicked) straight after calling the set state function, it will be updated in the next render

Related

React, Too many rerenders

Given the following code:
export default function CrearContacto({setOpening}) {
const [open, setOpen] = useState(false);
setOpen(setOpening);
useEffect(() => {
console.log(setOpening);
setOpen(setOpening);
}, [setOpening]);
const handleClose = () =>{setOpening=false};
return (
<div>
<Dialog open={open} onClose={handleClose}>
//code continues
I'm getting a Too many re-renders error, however I'm unable so see where is the loop here. {setOpening} is being used in a button in another component, when clicked, true is sent. setOpen sets new open state as true, so Dialog can pop-up. At the same time useEffect is watching setOpening for changes (probably this is also wrong, I'm trying to make this work without success). If closed, handleClose is triggered and now setOpening equals false, so useEffect detects the change and executes setOpen to false. I don't see the loop, but also the code isn't working as intended.
Edit - Added parent relative code
function Agenda() {
const MiContexto = useOutletContext();
return (<CrearContacto setOpening={MiContexto.modalState}/>
//code continues
MainLayout class:
import Navbar from "./Navbar"
import Topbar from "./Topbar"
import React, {useReducer} from "react";
import { Outlet, useOutletContext } from "react-router-dom";
export const MainContexto = React.createContext({modalState:false});
const initialState=false;
const estadosPosiblesModal = (state, action) =>{
switch(action){
case false:
return false;
case true:
return true;
default:
return state;
}
}
const MainLayout = ()=> {
const [EstadoModal, dispatch] = useReducer(estadosPosiblesModal,initialState);
return (
<MainContexto.Provider value={{modalState:EstadoModal,modalDispatch:dispatch}}>
<div className="mainlayout">
<Navbar/>
<main className="mainlayout_topPlusContent">
<Topbar className="TopBar" prueba={EstadoModal}/>
<Outlet context={{modalState:EstadoModal,modalDispatch:dispatch}}/>
</main>
</div>
</MainContexto.Provider>
);
};
export default MainLayout;
I had to use simultaneously a custom context and the Router outlet context as my context wasn't going through the outlet.
Topbar contains:
const micontext = useContext(MainContexto);
...
<Button variant="contained" onClick={()=>micontext.modalDispatch(true)}>
Your problem is you call setOpen(setOpening) on the component level CrearContacto which is causing the infinite re-renderings on that component. You can imagine this cycle: state update (setOpen(setOpening)) > re-rendering > call state update again > ...
And another problem is you cannot set setOpening=false directly, it needs to be set with a state setter from the parent component.
export default function CrearContacto({setOpening}) {
const [open, setOpen] = useState(setOpening);
//setOpen(setOpening); //remove this part
useEffect(() => {
setOpen(setOpening)
},[setOpening])
const handleClose = () =>{setOpen(false)}; //set `open` state internally instead
return (
<div>
<Dialog open={open} onClose={handleClose}>
</div>)
}
If you want to update the parent component's state, you should pass the state setter and state value to your child component which is CrearContacto too
I'd assume that you have this state on the parent component
const [opening, setOpening] = useState(false);
Note that setOpening is following the naming convention of the state setter, so I'd prefer to do it this way instead
You can try to modify your component like below
export default function CrearContacto({setOpening, opening}) {
const [open, setOpen] = useState(opening);
//setOpen(setOpening); //remove this part
useEffect(() => {
setOpen(opening)
},[opening])
const handleClose = () =>{setOpening(false)};
return (
<div>
<Dialog open={open} onClose={handleClose}>
</div>)
}
If you only use open state for Dialog, you can remove that state declaration too
export default function CrearContacto({setOpening, opening}) {
const handleClose = () =>{setOpening(false)};
return (
<div>
<Dialog open={opening} onClose={handleClose}>
</div>)
}
To expand my comment:
From inside (or a child) component, you can change the open value by using the state, so call the setOpen function.
From a parent component you can change the value of open by changing the prop setOpening (rename this prop as it sounds like it's a function), so you would have:
export default function CrearContacto({setOpening}) {
const [open, setOpen] = useState(setOpening);
useEffect(() => {
console.log(setOpening);
setOpen(setOpening);
}, [setOpening]);
const handleClose = () =>{setOpen(false)};
return (
<div>
<Dialog open={open} onClose={handleClose}>

In my React App getting firebase Google login Warning in the console, how can I fix this Warning? [duplicate]

I am getting this warning in react:
index.js:1 Warning: Cannot update a component (`ConnectFunction`)
while rendering a different component (`Register`). To locate the
bad setState() call inside `Register`
I went to the locations indicated in the stack trace and removed all setstates but the warning still persists. Is it possible this could occur from redux dispatch?
my code:
register.js
class Register extends Component {
render() {
if( this.props.registerStatus === SUCCESS) {
// Reset register status to allow return to register page
this.props.dispatch( resetRegisterStatus()) # THIS IS THE LINE THAT CAUSES THE ERROR ACCORDING TO THE STACK TRACE
return <Redirect push to = {HOME}/>
}
return (
<div style = {{paddingTop: "180px", background: 'radial-gradient(circle, rgba(106,103,103,1) 0%, rgba(36,36,36,1) 100%)', height: "100vh"}}>
<RegistrationForm/>
</div>
);
}
}
function mapStateToProps( state ) {
return {
registerStatus: state.userReducer.registerStatus
}
}
export default connect ( mapStateToProps ) ( Register );
function which triggers the warning in my registerForm component called by register.js
handleSubmit = async () => {
if( this.isValidForm() ) {
const details = {
"username": this.state.username,
"password": this.state.password,
"email": this.state.email,
"clearance": this.state.clearance
}
await this.props.dispatch( register(details) )
if( this.props.registerStatus !== SUCCESS && this.mounted ) {
this.setState( {errorMsg: this.props.registerError})
this.handleShowError()
}
}
else {
if( this.mounted ) {
this.setState( {errorMsg: "Error - registration credentials are invalid!"} )
this.handleShowError()
}
}
}
Stacktrace:
This warning was introduced since React V16.3.0.
If you are using functional components you could wrap the setState call into useEffect.
Code that does not work:
const HomePage = (props) => {
props.setAuthenticated(true);
const handleChange = (e) => {
props.setSearchTerm(e.target.value.toLowerCase());
};
return (
<div key={props.restInfo.storeId} className="container-fluid">
<ProductList searchResults={props.searchResults} />
</div>
);
};
Now you can change it to:
const HomePage = (props) => {
// trigger on component mount
useEffect(() => {
props.setAuthenticated(true);
}, []);
const handleChange = (e) => {
props.setSearchTerm(e.target.value.toLowerCase());
};
return (
<div key={props.restInfo.storeId} className="container-fluid">
<ProductList searchResults={props.searchResults} />
</div>
);
};
I just had this issue and it took me a bit of digging around before I realised what I'd done wrong – I just wasn't paying attention to how I was writing my functional component.
I was doing this:
const LiveMatches = (props: LiveMatchesProps) => {
const {
dateMatches,
draftingConfig,
sportId,
getDateMatches,
} = props;
if (!dateMatches) {
const date = new Date();
getDateMatches({ sportId, date });
};
return (<div>{component stuff here..}</div>);
};
I had just forgotten to use useEffect before dispatching my redux call of getDateMatches()
So it should have been:
const LiveMatches = (props: LiveMatchesProps) => {
const {
dateMatches,
draftingConfig,
sportId,
getDateMatches,
} = props;
useEffect(() => {
if (!dateMatches) {
const date = new Date();
getDateMatches({ sportId, date });
}
}, [dateMatches, getDateMatches, sportId]);
return (<div>{component stuff here..}</div>);
};
please read the error message thoroughly, mine was pointing to SignIn Component that had a bad setState. which when i examined, I had an onpress that was not an Arrow function.
it was like this:
onPress={navigation.navigate("Home", { screen: "HomeScreen" })}
I changed it to this:
onPress={() => navigation.navigate("Home", { screen: "HomeScreen" }) }
My error message was:
Warning: Cannot update a component
(ForwardRef(BaseNavigationContainer)) while rendering a different
component (SignIn). To locate the bad setState() call inside
SignIn, follow the stack trace as described in
https://reactjs.org/link/setstate-in-render
in SignIn (at SignInScreen.tsx:20)
I fixed this issue by removing the dispatch from the register components render method to the componentwillunmount method. This is because I wanted this logic to occur right before redirecting to the login page. In general it's best practice to put all your logic outside the render method so my code was just poorly written before. Hope this helps anyone else in future :)
My refactored register component:
class Register extends Component {
componentWillUnmount() {
// Reset register status to allow return to register page
if ( this.props.registerStatus !== "" ) this.props.dispatch( resetRegisterStatus() )
}
render() {
if( this.props.registerStatus === SUCCESS ) {
return <Redirect push to = {LOGIN}/>
}
return (
<div style = {{paddingTop: "180px", background: 'radial-gradient(circle, rgba(106,103,103,1) 0%, rgba(36,36,36,1) 100%)', height: "100vh"}}>
<RegistrationForm/>
</div>
);
}
}
I think that this is important.
It's from this post that #Red-Baron pointed out:
#machineghost : I think you're misunderstanding what the message is warning about.
There's nothing wrong with passing callbacks to children that update state in parents. That's always been fine.
The problem is when one component queues an update in another component, while the first component is rendering.
In other words, don't do this:
function SomeChildComponent(props) {
props.updateSomething();
return <div />
}
But this is fine:
function SomeChildComponent(props) {
// or make a callback click handler and call it in there
return <button onClick={props.updateSomething}>Click Me</button>
}
And, as Dan has pointed out various times, queuing an update in the same component while rendering is fine too:
function SomeChildComponent(props) {
const [number, setNumber] = useState(0);
if(props.someValue > 10 && number < 5) {
// queue an update while rendering, equivalent to getDerivedStateFromProps
setNumber(42);
}
return <div>{number}</div>
}
If useEffect cannot be used in your case or if the error is NOT because of Redux
I used setTimeout to redirect one of the two useState variables to the callback queue.
I have one parent and one child component with useState variable in each of them. The solution is to wrap useState variable using setTimeout:
setTimeout(() => SetFilterData(data), 0);
Example below
Parent Component
import ExpenseFilter from '../ExpensesFilter'
function ExpensesView(props) {
const [filterData, SetFilterData] = useState('')
const GetFilterData = (data) => {
// SetFilterData(data);
//*****WRAP useState VARIABLE INSIDE setTimeout WITH 0 TIME AS BELOW.*****
setTimeout(() => SetFilterData(data), 0);
}
const filteredArray = props.expense.filter(expenseFiltered =>
expenseFiltered.dateSpent.getFullYear().toString() === filterData);
return (
<Window>
<div>
<ExpenseFilter FilterYear = {GetFilterData}></ExpenseFilter>
Child Component
const ExpensesFilter = (props) => {
const [filterYear, SetFilterYear] = useState('2022')
const FilterYearListener = (event) => {
event.preventDefault()
SetFilterYear(event.target.value)
}
props.FilterYear(filterYear)
return (
Using React and Material UI (MUI)
I changed my code from:
<IconButton onClick={setOpenDeleteDialog(false)}>
<Close />
</IconButton>
To:
<IconButton onClick={() => setOpenDeleteDialog(false)}>
<Close />
</IconButton>
Simple fix
If you use React Navigation and you are using the setParams or setOptions you must put these inside method componentDidMount() of class components or in useEffects() hook of functional components.
Minimal reproducing example
I was a bit confused as to what exactly triggers the problem, having a minimal immediately runnable example helped me grasp it a little better:
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<script src="https://unpkg.com/react#17/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#17/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/#babel/standalone#7.14.7/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
function NotMain(props) {
props.setN(1)
return <div>NotMain</div>
}
function Main(props) {
const [n, setN] = React.useState(0)
return <>
<NotMain setN={setN} />
<div>Main {n}</div>
</>
}
ReactDOM.render(
<Main/>,
document.getElementById('root')
);
</script>
</body>
</html>
fails with error:
react-dom.development.js:61 Warning: Cannot update a component (`Main`) while rendering a different component (`NotMain`). To locate the bad setState() call inside `NotMain`, follow the stack trace as described in https://reactjs.org/link/setstate-in-render
followed by a stack trace:
at NotMain (<anonymous>:16:9)
at Main (<anonymous>:21:31)
Presumably 16:9 would be the exact line where props.setN(1) is being called from, but the line numbers are a bit messed up because of the Babel JSX translation.
The solution like many other answers said is to do instead:
function NotMain(props) {
React.useEffect(() => { props.setN(1) }, [])
return <div>NotMain</div>
}
Intuitively, I think that the general idea of why this error happens is that:
You are not supposed to updat state from render methods, otherwise it could lead to different results depending on internal the ordering of how React renders things.
and when using functional components, the way to do that is to use hooks. In our case, useEffect will run after rendering is done, so we are fine doing that from there.
When using classes this becomes slightly more clear and had been asked for example at:
Calling setState in render is not avoidable
Calling setState() in React from render method
When using functional components however, things are conceptually a bit more mixed, as the component function is both the render, and the code that sets up the callbacks.
I was facing same issue, The fix worked for me was if u are doing
setParams/setOptions
outside of useEffect then this issue is occurring. So try to do such things inside useEffect. It'll work like charm
TL;DR;
For my case, what I did to fix the warning was to change from useState to useRef
react_devtools_backend.js:2574 Warning: Cannot update a component (`Index`) while rendering a different component (`Router.Consumer`). To locate the bad setState() call inside `Router.Consumer`, follow the stack trace as described in https://reactjs.org/link/setstate-in-render
at Route (http://localhost:3000/main.bundle.js:126692:29)
at Index (http://localhost:3000/main.bundle.js:144246:25)
at Switch (http://localhost:3000/main.bundle.js:126894:29)
at Suspense
at App
at AuthProvider (http://localhost:3000/main.bundle.js:144525:23)
at ErrorBoundary (http://localhost:3000/main.bundle.js:21030:87)
at Router (http://localhost:3000/main.bundle.js:126327:30)
at BrowserRouter (http://localhost:3000/main.bundle.js:125948:35)
at QueryClientProvider (http://localhost:3000/main.bundle.js:124450:21)
The full code for the context of what I did (changed from the lines with // OLD: to the line above them). However this doesn't matter, just try changing from useState to useRef!!
import { HOME_PATH, LOGIN_PATH } from '#/constants';
import { NotFoundComponent } from '#/routes';
import React from 'react';
import { Redirect, Route, RouteProps } from 'react-router-dom';
import { useAccess } from '#/access';
import { useAuthContext } from '#/contexts/AuthContext';
import { AccessLevel } from '#/models';
type Props = RouteProps & {
component: Exclude<RouteProps['component'], undefined>;
requireAccess: AccessLevel | undefined;
};
export const Index: React.FC<Props> = (props) => {
const { component: Component, requireAccess, ...rest } = props;
const { isLoading, isAuth } = useAuthContext();
const access = useAccess();
const mounted = React.useRef(false);
// OLD: const [mounted, setMounted] = React.useState(false);
return (
<Route
{...rest}
render={(props) => {
// If in indentifying authentication state as the page initially loads, render a blank page
if (!mounted.current && isLoading) return null;
// OLD: if (!mounted && isLoading) return null;
// 1. Check Authentication is one step
if (!isAuth && window.location.pathname !== LOGIN_PATH)
return <Redirect to={LOGIN_PATH} />;
if (isAuth && window.location.pathname === LOGIN_PATH)
return <Redirect to={HOME_PATH} />;
// 2. Authorization is another
if (requireAccess && !access[requireAccess])
return <NotFoundComponent />;
mounted.current = true;
// OLD: setMounted(true);
return <Component {...props} />;
}}
/>
);
};
export default Index;
My example.
Code with that error:
<Form
initialValues={{ ...kgFormValues, dataflow: dataflows.length > 0 ? dataflows[0].df_tpl_key : "" }}
onSubmit={() => {}}
render={({values, dirtyFields }: any) => {
const kgFormValuesUpdated = {
proj_key: projectKey,
name: values.name,
description: values.description,
public: values.public,
dataflow: values.dataflow,
flavours: flavoursSelected,
skipOCR: values.skipOCR
};
if (!_.isEqual(kgFormValues, kgFormValuesUpdated)) {
setNewKgFormValues(kgFormValuesUpdated);
}
Working Code:
<Form
initialValues={{ ...kgFormValues, dataflow: dataflows.length > 0 ? dataflows[0].df_tpl_key : "" }}
onSubmit={() => {}}
render={({ values, dirtyFields }: any) => {
useEffect(() => {
const kgFormValuesUpdated = {
proj_key: projectKey,
name: values.name,
description: values.description,
public: values.public,
dataflow: values.dataflow,
flavours: flavoursSelected,
skipOCR: values.skipOCR
};
if (!_.isEqual(kgFormValues, kgFormValuesUpdated)) {
setNewKgFormValues(kgFormValuesUpdated);
}
}, [values]);
return (
I had the same problem. I was setting some state that was storing a function like so:
// my state definition
const [onConfirm, setOnConfirm] = useState<() => void>();
// then I used this piece of code to update the state
function show(onConfirm: () => void) {
setOnConfirm(onConfirm);
}
The problem was from setOnConfirm. In React, setState can take the new value OR a function that returns the new value. In this case React wanted to get the new state from calling onConfirm which is not correct.
changing to this resolved my issue:
setOnConfirm(() => onConfirm);
I was able to solve this after coming across a similar question in GitHub which led me to this comment showing how to pinpoint the exact line within your file causing the error. I wasn't aware that the stack trace was there. Hopefully this helps someone!
See below for my fix. I simply converted the function to use callback.
Old code
function TopMenuItems() {
const dispatch = useDispatch();
function mountProjectListToReduxStore(projects) {
const projectDropdown = projects.map((project) => ({
id: project.id,
name: project.name,
organizationId: project.organizationId,
createdOn: project.createdOn,
lastModifiedOn: project.lastModifiedOn,
isComplete: project.isComplete,
}));
projectDropdown.sort((a, b) => a.name.localeCompare(b.name));
dispatch(loadProjectsList(projectDropdown));
dispatch(setCurrentOrganizationId(projectDropdown[0].organizationId));
}
};
New code
function TopMenuItems() {
const dispatch = useDispatch();
const mountProjectListToReduxStore = useCallback((projects) => {
const projectDropdown = projects.map((project) => ({
id: project.id,
name: project.name,
organizationId: project.organizationId,
createdOn: project.createdOn,
lastModifiedOn: project.lastModifiedOn,
isComplete: project.isComplete,
}));
projectDropdown.sort((a, b) => a.name.localeCompare(b.name));
dispatch(loadProjectsList(projectDropdown));
dispatch(setCurrentOrganizationId(projectDropdown[0].organizationId));
}, [dispatch]);
};
My case was using setState callback, instead of setState + useEffect
BAD ❌
const closePopover = useCallback(
() =>
setOpen((prevOpen) => {
prevOpen && onOpenChange(false);
return false;
}),
[onOpenChange]
);
GOOD ✅
const closePopover = useCallback(() => setOpen(false), []);
useEffect(() => onOpenChange(isOpen), [isOpen, onOpenChange]);
I got this when I was foolishly invoking a function that called dispatch instead of passing a reference to it for onClick on a button.
const quantityChangeHandler = (direction) => {
dispatch(cartActions.changeItemQuantity({title, quantityChange: direction}));
}
...
<button onClick={() => quantityChangeHandler(-1)}>-</button>
<button onClick={() => quantityChangeHandler(1)}>+</button>
Initially, I was directly calling without the fat arrow wrapper.
Using some of the answers above, i got rid of the error with the following:
from
if (value === "newest") {
dispatch(sortArticlesNewest());
} else {
dispatch(sortArticlesOldest());
}
this code was on my component top-level
to
const SelectSorting = () => {
const dispatch = useAppDispatch();
const {value, onChange} = useSelect();
useEffect(() => {
if (value === "newest") {
dispatch(sortArticlesNewest());
} else {
dispatch(sortArticlesOldest());
}
}, [dispatch, value]);

React Context value gets updated, but component doesn't re-render

This Codesandbox only has mobile styles as of now
I currently have a list of items being rendered based on their status.
Goal: When the user clicks on a nav button inside the modal, it updates the status type in context. Another component called SuggestionList consumes the context via useContext and renders out the items that are set to the new status.
Problem: The value in context is definitely being updated, but the SuggestionList component consuming the context is not re-rendering with a new list of items based on the status from context.
This seems to be a common problem:
Does new React Context API trigger re-renders?
React Context api - Consumer Does Not re-render after context changed
Component not re rendering when value from useContext is updated
I've tried a lot of suggestions from different posts, but I just cannot figure out why my SuggestionList component is not re-rendering upon value change in context. I'm hoping someone can give me some insight.
Context.js
// CONTEXT.JS
import { useState, createContext } from 'react';
export const RenderTypeContext = createContext();
export const RenderTypeProvider = ({ children }) => {
const [type, setType] = useState('suggestion');
const renderControls = {
type,
setType,
};
console.log(type); // logs out the new value, but does not cause a re-render in the SuggestionList component
return (
<RenderTypeContext.Provider value={renderControls}>
{children}
</RenderTypeContext.Provider>
);
};
SuggestionPage.jsx
// SuggestionPage.jsx
export const SuggestionsPage = () => {
return (
<>
<Header />
<FeedbackBar />
<RenderTypeProvider>
<SuggestionList />
</RenderTypeProvider>
</>
);
};
SuggestionList.jsx
// SuggestionList.jsx
import { RenderTypeContext } from '../../../../components/MobileModal/context';
export const SuggestionList = () => {
const retrievedRequests = useContext(RequestsContext);
const renderType = useContext(RenderTypeContext);
const { type } = renderType;
const renderedRequests = retrievedRequests.filter((req) => req.status === type);
return (
<main className={styles.container}>
{!renderedRequests.length && <EmptySuggestion />}
{renderedRequests.length &&
renderedRequests.map((request) => (
<Suggestion request={request} key={request.title} />
))}
</main>
);
};
Button.jsx
// Button.jsx
import { RenderTypeContext } from './context';
export const Button = ({ handleClick, activeButton, index, title }) => {
const tabRef = useRef();
const renderType = useContext(RenderTypeContext);
const { setType } = renderType;
useEffect(() => {
if (index === 0) {
tabRef.current.focus();
}
}, [index]);
return (
<button
className={`${styles.buttons} ${
activeButton === index && styles.activeButton
}`}
onClick={() => {
setType('planned');
handleClick(index);
}}
ref={index === 0 ? tabRef : null}
tabIndex="0"
>
{title}
</button>
);
};
Thanks
After a good night's rest, I finally solved it. It's amazing what you can miss when you're tired.
I didn't realize that I was placing the same provider as a child of itself. Once I removed the child provider, which was nested within itself, and raised the "parent" provider up the tree a little bit, everything started working.
So the issue wasn't that the component consuming the context wasn't updating, it was that my placement of providers was conflicting with each other. I lost track of my component tree. Dumb mistake.
The moral of the story, being tired can make you not see solutions. Get rest.

react useState update not triggering with custom hook

I made a custom hook for my player like this
const usePlayer = () => {
const [volume, setVolume] = useState<number>(100);
const playerRef = useRef<HTMLAudioElement | null>(null);
const [playing, setPlaying] = useState<boolean>(false);
useEffect(() => {
console.log(volume);
}, [volume]);
return {
playing,
volume,
setPlaying,
setVolume,
playerRef,
};
};
export default usePlayer;
I am listening for changes in another component which uses the userPlayer()
const PlayerButton = () => {
const { playerRef, volume } = usePlayer();
useEffect(() => {
console.log(volume);
if (playerRef.current !== null) {
playerRef.current.volume = volume / 100;
}
}, [volume]);
return (
<>
<div className="lg:mr-2 flex align-middle">
<audio ref={playerRef} preload="none">
<source src="" type="audio/ogg"></source>
<source src="" type="audio/mp3"></source>
</audio>
</div>
</>
);
};
export default PlayerButton
the setVolume is called like this in another component
<input
type="range"
className="w-full"
value={volume}
onChange={(e) => setVolume(parseInt(e.target.value))}
min={0}
max={100}
/>
But the useEffect does only trigger inside the usePlayer component and not in the second component who uses the usePlayer()
any idea why ?
If you use setVolume in PlayerButton component, it will trigger the useEffect in PlayerButton.
It will not trigger the useEffect if you are using setVolume in a different component.
Do two components using the same Hook share state? No. Custom Hooks
are a mechanism to reuse stateful logic (such as setting up a
subscription and remembering the current value), but every time you
use a custom Hook, all state and effects inside of it are fully
isolated.
How does a custom Hook get isolated state? Each call to a Hook gets
isolated state. Because we call useFriendStatus directly, from React’s
point of view our component just calls useState and useEffect. And as
we learned earlier, we can call useState and useEffect many times in
one component, and they will be completely independent.
https://reactjs.org/docs/hooks-custom.html
If you want 2 components to share state, you need to use redux or useContext to manage the state in a store.
https://reactjs.org/docs/hooks-reference.html#usecontext
Example use of useContext and Provider.
export const PlayerContext = createContext();
export const usePlayer = () => {
const context = useContext(PlayerContext);
if (!context && typeof window !== 'undefined') {
throw new Error(`usePlayer must be used within a PlayerContext `);
}
return context;
};
export const PlayerProvider = ({ children }) => {
const [ volume, setVolume ] = useState(100);
return <PlayerContext.Provider value={{ volume, setVolume }}>{children}</PlayerContext.Provider>
}
You need to wrap your app with the provider like this.
<PlayerProvider><App /></PlayerProvider>
Then use it like this
const { volume, setVolume } = usePlayer()

How to control the order of rendering components in React?

There is a page contains a booking list and a popup window(A modal with survey questions). To reduce the impact on booking list loading time, I want to render the modal component after the booking list be completely loaded.
ps.there are network data request in both <BookingList/> and <Modal/>.
How should I do with React?
Thanks for help.
export default function Body() {
return (
<>
<BookingList .../>
<Modal .../>
</>
);
}
You can conditionally render the Modal component after the BookingList fetches the results.
For that, you'd need a state variable in the parent component of BookingList and Modal. Code sandbox https://codesandbox.io/s/romantic-cookies-zrpit
import React, { useEffect, useState, useCallback } from "react";
const BookingList = ({ setLoadedBookingList }) => {
useEffect(() => {
setTimeout(() => {
setLoadedBookingList();
}, 1000);
}, [setLoadedBookingList]);
return <h2>BookingList</h2>;
};
const Modal = () => <h1>Modal</h1>;
export default function App() {
const [loadBookingList, setLoadBookingList] = useState(false);
const setLoadedBookingList = useCallback(() => {
setLoadBookingList(true);
}, []);
return (
<div className="App">
<BookingList setLoadedBookingList={setLoadedBookingList} />
{loadBookingList && <Modal />}
</div>
);
}
You can use react hooks useState() and useEffect():
export default function Body() {
// `isLoaded` is a variable with false as initial value
// `setLoaded` is a method to modify the value of `isLoaded`
const [isLoaded, setLoaded] = React.useState(false)
React.useEffect(() => {
// some method used in loading the resource,
// after completion you can set the isLoaded to true
loadBookingList().then(loaded => setLoaded(true));
},
// second argument to `useEffect` is the dependency which is out `setLoaded` function
[setLoaded]);
return (
<>
// conditional rendering based on `isLoaded`
{isLoaded && <BookingList .../>}
<Modal .../>
</>
);
}

Resources