react hook for multiple component state didn't update - javascript

I have a hook, and 2 components. Component App.js has a function that changes the state in hook, but the value is not updated in Component New.js, why? I think I've missed something but can't figure it out.
App.js
export const useToggle = () => {
const [onOff, setOnOff] = useState(false);
return [onOff, () => setOnOff((prev) => !prev)];
};
export default function App() {
const [onOff, setOnOff] = useToggle();
return (
<div className="App">
<h1>{onOff.toString()}</h1>
<button onClick={setOnOff}>toggle</button>
</div>
);
}
New.js
import { useToggle } from "./App.js";
export default function New() {
const [onOff] = useToggle();
return (
<div className="App">
<hr />
<h1>NEW:</h1>
<pre>{onOff.toString()}</pre>
</div>
);
}
https://codesandbox.io/s/musing-fire-rjude?file=/src/App.js

Each useToggle hook is its own entity with its own state. The useToggle that you are toggling in App isn't the same useToggle that is rendered/used in New.
This means they are toggled independently of any other hooks and state. They don't share "state".
If you are wanting to create a useToggle hook that does have shared state then I would suggest implementing it via a React context and the useContext hook so each useToggle hook can toggle the same shared state held in the context.
Update
Global useToggle hook.
togglecontext.js
import { createContext, useContext, useState } from 'react';
export const ToggleContext = createContext([false, () => {}]);
const ToggleProvider = ({ children }) => {
const [onOff, setOnOff] = useState(false);
const toggle = () => setOnOff(t => !t);
return (
<ToggleContext.Provider value={[onOff, toggle]}>
{children}
</ToggleContext.Provider>
);
}
export const useToggle = () => useContext(ToggleContext);
export default ToggleProvider;
index - provide the context
...
import ToggleProvider from "./toggle.context";
const rootElement = document.getElementById("root");
ReactDOM.render(
<StrictMode>
<ToggleProvider>
<App />
<New />
</ToggleProvider>
</StrictMode>,
rootElement
);
App
import "./styles.css";
import { useToggle } from "./toggle.context";
export default function App() {
const [onOff, setOnOff] = useToggle();
return (
<div className="App">
<h1>{onOff.toString()}</h1>
<button onClick={setOnOff}>toggle</button>
</div>
);
}
New
import { useToggle } from "./toggle.context";
export default function New() {
const [onOff] = useToggle();
return (
<div className="App">
<hr />
<h1>NEW:</h1>
<pre>{onOff.toString()}</pre>
</div>
);
}
Note that the only thing that changed in the App and New components was the import, where the useToggle hook is defined.

Related

Global Invalid Hook Call - React

I recently started working with React, and I'm trying to understand why my context.js is giving me so much trouble. Admittedly I'm not great with JavaScript to start, so I'd truly appreciate any insight.
Thank you, code and the error that it generates:
import React, { useState, useContext } from 'react';
const AppContext = React.createContext(undefined, undefined);
const AppProvider = ({ children }) => {
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
const openSidebar = () => {
setIsSidebarOpen(true);
};
const closeSidebar = () => {
setIsSidebarOpen(false);
};
const toggle = () => {
if (isSidebarOpen) {
closeSidebar();
} else {
openSidebar();
}
};
return (
<AppContext.Provider
value={{
isSidebarOpen,
openSidebar,
closeSidebar,
toggle
}}
>
{children}
</AppContext.Provider>
);
};
export const useGlobalContext = () => {
return useContext(AppContext);
};
export { AppContext, AppProvider };
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:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app
Thank you again for taking the time to look!
EDIT: Sidebar App Added for context (double entendre!)
import React from 'react';
import logo from './logo.svg'
import {links} from './data'
import {FaTimes} from 'react-icons/fa'
import { useGlobalContext } from "./context";
const Sidebar = () => {
const { toggle, isSidebarOpen } = useGlobalContext();
return (
<aside className={`${isSidebarOpen ? 'sidebar show-sidebar' : 'sidebar'}`}>
<div className='sidebar-header'>
<img src={logo} className='logo' alt='NavTask Management'/>
<button className='close-btn' onClick={toggle}>
<FaTimes />
</button>
</div>
<ul className='links'>
{links.map((link) => {
const { id, url, text, icon } = link;
return (
<li key={id}>
<a href={url}>
{icon}
{text}
</a>
</li>
);
})}
</ul>
</aside>
);
};
export default Sidebar;

Why React renders a component even though I don't pass anything to it?

Consider the code :
APP.JS
import React, { useState, useMemo } from 'react';
import Counter from './components/Counter';
import './App.css';
function App() {
const [countA, setCountA] = useState(0);
const incrementA = () => {
setCountA(countA + 1);
};
// const memoCounter = useMemo(() => {
// return <Counter />;
// }, []);
return (
<div className='App'>
<h1>Incrementing CountA from APP.JS : {countA}</h1>
<p>
<button onClick={incrementA}>Increment A</button>
</p>
{/* {memoCounter} */}
<Counter />
</div>
);
}
export default App;
Counter.js :
import React, { useEffect } from 'react';
let renderCount = 1;
const Counter = () => {
useEffect(() => {
renderCount++;
});
return (
<div>
<h1>Rendering Counter component : {renderCount}</h1>
</div>
);
};
export default Counter;
When the user hits the button and increments , React renders Counter component all over again , even though I don't pass anything to it.
However when I put useMemo it doesn't.
Why ?
By default when a parent component renders (App), it renders all its children (Counter).
To overload the default behaviour, use React API like React.memo:
const Counter = () => {...}
export default React.memo(Counter);

What is the correct way of exporting Hooks function as a component in react?

I am new to Hooks and would like to understand better how to do things the right way. I am trying to separate my component into a Home and SignIn. Simple example:
Home.js
import {SignIn} from './SignIn';
export const Home = () => {
return (
<div>
<SignIn />
</div>
)
}
SignIn.js
export const SignIn = () => {
//sign in functionality
return (
//sign in forms
)
}
with this format it works but on the console I'm having error:
Functions are not valid as a React child. This may happen if you return a Component instead of
<Component /> from render. Or maybe you meant to call this function rather than return it.
in SignIn (at Home.js:26)
What would be the correct way of exporting Hooks to be a valid react child?
if u do that. u'll got this "Warning: Functions are not valid as a React child. This may happen if you return a Component instead of from render. Or maybe you meant to call this function rather than return it."
const method = () => {}
...
return (
<div>
{method}
</div>
);
changed
const method = () => {}
...
return (
<div>
{method()}
</div>
);
in your SignIn.js
export const SignIn = () => {
return (
// the problem is here
// do like that {method()}
);
};
============UPDATE==============
SignIn.js
import React from 'react'
export const SignIn = () => {
return <div>SignIn</div>
}
Home.js
import React from 'react';
import {SignIn} from './SignIn';
export const Home = () => {
return (
<div>
<SignIn />
</div>
)
}
You have to make React recognize that it is a component by doing so
In your SignIn.js and your Home.js:
import React from "react";
Updated, try this:
export const SignIn = () => {
return <div>SignIN</div>;
};
export const Home = () => {
return <SignIn />;
};

Context API dispatch (consumer) in _app.js class component Next.js

I need to use dispatch Context API methods in _app.js.
The main limitation is that I use React hooks along with Context API, since _app.js is a Class, I can't use hooks within it.
My code:
// store.js
import React, { createContext, useContext, useReducer } from "react";
import mainReducer from "../store/reducers";
const AppStateContext = createContext();
const AppDispatchContext = createContext();
const initialState = {
filters: {
diet: {
selected: []
}
}
};
const useAppState = () => useContext(AppStateContext);
const useAppDispatch = () => useContext(AppDispatchContext);
const useApp = () => [useAppState(), useAppDispatch()];
const AppProvider = ({ children }) => {
const [state, dispatch] = useReducer(mainReducer, initialState);
return (
<AppStateContext.Provider value={state}>
<AppDispatchContext.Provider value={dispatch}>
{children}
</AppDispatchContext.Provider>
</AppStateContext.Provider>
);
};
export { AppProvider, useAppState, useAppDispatch, useApp };
// _app.js
import App from "next/app";
import React from "react";
import { AppProvider } from "../store";
class MyApp extends App {
componentDidMount() {
/***********************************/
// HERE I WOULD LIKE TO USE DISPATCH
/***********************************/
}
render() {
const { Component, router, pageProps } = this.props;
return (
<AppProvider>
<Component {...pageProps} />
</AppProvider>
);
}
}
export default MyApp;
If you really want to use hooks, then just put a wrapper around _app.js like this:
import React from 'react'
import App from 'next/app'
function MyComponent({ children }) {
// You can use hooks here
return <>{children}</>
}
class MyApp extends App {
render() {
const { Component, pageProps } = this.props
return (
<MyComponent>
<Component {...pageProps} />
</MyComponent>
)
}
}
export default MyApp

React Context Decorator/Subscriber?

Edit: Perhaps this could be referenced as a context subscriber?
I'm not even sure if this is the right concept that I'm trying to achieve. I want to be able to create a component that does the dirty work and just attaches context to the component that can the be consumed..
I've tried to find anything similar with no luck, which, leads me to believe I am not thinking of the right literal context of what it is I'm doing...
I've tried something like this:
import React, { Component } from "react";
export const Context = React.createContext();
export class ContextProvider extends Component {
state = {
scanning: false
};
render() {
return (
<Context.Provider
value={{
state: this.state,
handleClick: () => this.setState({
scanning: !this.state.scanning
})
}}
>
{this.props.children}
</Context.Provider>
);
}
}
And I trying to make it work with this..
import React from "react";
import { Context } from "./Context";
const WithContext = (children) => (props) => {
return (
<Context.Consumer>
{ state => (<children {...props} context={state} />) }
</Context.Consumer>
)
};
and then consuming with...
...
<WithContext>
<MyComponent />
</WithContext>
...
But, it just seems to fail or states that I'm returning a function instead of a react component..
Your WithContext component will not work like that... It needs to be a function that has the same functionality as the render function. like so:
import React from "react";
import { Context } from "./Context";
const WithContext = ({ children, ...props }) => (
<Context.Consumer>{state => React.Children.map(children, (child) => (
React.cloneElement(child, { context: state })
))}</Context.Consumer>
);
note that we traverse every direct child of the withContext children using React.Children.map (docs) and add a context prop to them by making use of React.cloneElement (docs). This keeps the child component's original props and shallow merges them into the second parameter passed to the function.
There are a bunch of little errors in your code for using context... Here is a complete example...
Let's say we have a structure where we have App.js -> Parent.js -> Child.js components... Instead of passing the data via props from App to Parent to Child we want to make use of the context API and avoid the prop drilling and have the Child consume the data directly...
Here is what that will look like:
context.js:
import React from 'react';
const Context = React.createContext();
export class Provider extends React.Component {
state = { name: 'Bob', age: 20 };
handleGrowUp = () => {
this.setState({ age: this.state.age + 1 });
};
render() {
return (
<Context.Provider
value={{
state: {
...this.state,
},
actions: {
growUp: this.handleGrowUp,
},
}}
>
{this.props.children}
</Context.Provider>
);
}
}
export const Consumer = Context.Consumer;
App.js:
import React from 'react';
import Parent from './Parent';
import { Provider } from './context';
const App = () => (
<Provider>
<Parent />
</Provider>
);
export default App;
Parent.js:
import React from 'react';
import Child from './Child';
const Parent = () => (
<div>
<Child />
</div>
);
export default Parent;
Child.js:
import React from 'react';
import { Consumer } from './context';
const Child = () => (
<div>
<Consumer>
{value => (
<div>
<p>{value.state.name}</p>
<p>{value.state.age}</p>
<button onClick={value.actions.growUp}>Grow Up</button>
</div>
)}
</Consumer>
</div>
);
export default Child;
Here is a working demo: https://codesandbox.io/s/9z06xzlyly

Categories