Variable passed trough state in Link can't update - javascript

I am updating my theme in my App per useState. This is passed to Topbar-Component per prop. console.log() gets triggered every time it changes. From Topbar theme is passed into a link to AboutMe-Copmponent as state, which works, but when i now change the state of theme it only updates in Topbar. I even tried Useeffect. Only when I refresh the site the change is noticed. I read hours about this but I cant solve it somehow.
AppComponent (not all code just the necessary):
function App() {
const [theme, setTheme] = useState('dark')
return (
<Topbar theme={theme}></Topbar>
<ToggleButton variant='light' onClick={() => setTheme('light')}>Light</ToggleButton>
<ToggleButton variant='dark' onClick={() => setTheme('dark')}>Dark</ToggleButton>
TopbarComponent:
export default function Topbar({theme}) {
console.log('Topbar',theme)
React.useEffect(()=>{
console.log('changed')
},[theme])
Output when I press the buttons:
Topbar light
changed
Topbar dark
changed
AboutMeComponent:
export default function AboutMe() {
const location = useLocation()
console.log(location.state)
React.useEffect(() => {
console.log('About-Me',location.state)
},[location])
Initial output:
dark
About-Me dark
When I now press the other Button I only get the Topbar Output
Only when refreshing I get the AboutMe Outputs again.
PS
The theme is changed anyway from dark to light but i need this state to change fonts etc.

I would suggest sticking with documentation's recommendation which is to use useContext for very this example of setting theme using context.
Check out: https://beta.reactjs.org/apis/react/useContext
Usage : Passing data deeply into the tree
import { useContext } from 'react';
function Button() {
const theme = useContext(ThemeContext);
useContext returns the context value for the context you passed. To determine the context value, React searches the component tree and finds the closest context provider above for that particular context.
To pass context to a Button, wrap it or one of its parent components into the corresponding context provider:
function MyPage() {
return (
<ThemeContext.Provider value="dark">
<Form />
</ThemeContext.Provider>
);
}
function Form() {
// ... renders buttons inside ...
}
It doesn’t matter how many layers of components there are between the provider and the Button. When a Button anywhere inside of Form calls useContext(ThemeContext), it will receive "dark" as the value.

I have it working now with the useContext hook. Thank you i somehow forgot about it.
App:
export const ThemeContext = React.createContext()
function App() {
const [theme, setTheme] = useState('black')
console.log(theme)
return (
<ThemeContext.Provider value={{backgroundColor:theme}}>
<BrowserRouter>
<div className='App' id={theme}>
<Topbar/>
<div className="position-absolute top-0 start-0">
<ToggleButton variant='light' onClick={() => setTheme('white')}>Light</ToggleButton>
<ToggleButton variant='dark' onClick={() => setTheme('black')}>Dark</ToggleButton>
</div>
Topbar:
export default function Topbar() {
const {user,logout} = UserAuth()
const [error, setError] = useState('')
const navigate = useNavigate()
const style = useContext(ThemeContext)
console.log(style)
AboutMe:
export default function AboutMe() {
const style = useContext(ThemeContext)
console.log(style)
return (
<>
<div className='d-flex' style={style}>
I had to move my Routing from Index.js to App.js because it had to be wrapped in the Context provider, but now my theme gets passed into every single component.

Related

How to avoid re-renders at the App level when I make a change to a child component (like a searchbar)?

I'm trying to make a searchbar React component that doesn't trigger an App-wide re-render when I type, yet allows me to use the query in other components/to make an API call.
Background:
I learned that stateless input components are good for reusability and creating controlled components. So state stays at parent (or App) level and the component's value gets passed in via props.
On the other hand, tracking the query's state at the App level causes ALL components to re-render (when the input's handleChange calls setQuery) and feels like a needless use of resources.
What am I missing here? Do I leave the query piece of state at the SearchBar level instead? Should I use React.memo or useCallback?
SearchBar component:
import React from 'react';
const Searchbar = ({ query, handleQueryChange }) => {
return (
<div className="field">
<label>Enter search term</label>
<input type="text" onChange={handleQueryChange} value={query}></input>
</div>
);
};
export default Searchbar;
And the App component
const App = () => {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const handleQueryChange = (e) => {
setQuery(e.currentTarget.value);
};
useEffect(() => {
function search() {
...makeAPIcallwith(query).then((result) => {setResults(result)})
};
if (query) {
const timer = setTimeout(() => {
search()}, 1000);
return () => clearTimeout(timer);
}
}, [query]);
return (
<div className="content-container">
<SearchBar query={query} handleQueryChange={handleQueryChange} />
<...Other React component not needing to re-render... />
</div>
);
};
export default App;
The tiniest optimization that you could make here is this:
const handleQueryChange = useCallback((e) => {
setQuery(e.currentTarget.value);
},[]);
It's not worth making. What you've shown is good idomatic react code.
I guess the other thing that you could do, if you haven't already because you haven't shown the code, is to help React out by encapsulating the results in a component like this:
return (
<div className="content-container">
<SearchBar query={query} handleQueryChange={handleQueryChange} />
<ListOfThings results={results}/>
</div>
);
Super tiny components, so tiny they seem almost trivially simple, is the name of the game in React. If your components are over 30-40 lines long, then 👎

Prevent modal from closing after re-render in react

Inside a Component, I have a Modal from which the user can do an update to some data through an API and then change the state of that main Component. Because there is a change in state, everything will re-render.
I would like to keep the modal open, so that I can show a success message.
The code would be something like this.
const Main = () => {
const [state, setState()] = useState();
return (
<Component state={state}>
<Modal onButtonClick={() => {
updateThroughApi().then(() => setState())} />
</Component>
)
}
When user clicks on modal's button, the state changes, and Component is re-rendered. Modal is rendered too, as it's inside.
I have thought of two possible solutions:
Move the modal outside of the component. This is a problem, as my actual code is not as simple as the example I posted. In my code, the modal opens on the click of a button B, which is deep inside Component. So, if I move the modal out from Component, I would have to pass the status and the action to change status (e.g. [open, setOpen]) through several components until button B (prop drilling).
Another solution: On the action onButtonClick I just do the API update, and use a new state updated = true; then, onModalClose, only if updated is true, I run setState so Component is rendered just after the modal is closed. But this solution seems a hack to me. There must be a better way.
Is there any better solution?
Something is obviously going very wrong here, the Modal should not close. As a workaround you could do something like this:
const Main = () => {
const [state, setState()] = useState();
const modal = useMemo(() => (
<Modal onButtonClick={() => {
updateThroughApi().then(() => setState())} />
), [])
return (
<Component state={state}>{modal}</Component>
)
}
Your Modal is re-rendering because your function passed as onButtonClick is redefined at every render.
So you have 2 options here:
1/ Keep your Modal inside your Component and use useMemo
import { useMemo } from 'react'
const Main = () => {
const [state, setState] = useState();
const modal = useMemo(() => (
<Modal onButtonClick={() => (
updateThroughApi().then(() => setState())
)}
/>
), [])
return (
<Component state={state}>
{modal}
</Component>
)
}
Or 2/ Move your Modal outside your component and use combination of memo and useCallback
import { memo, useCallback } from 'react'
const Main = () => {
const [state, setState] = useState();
const onButtonClick = useCallback(() => updateThroughApi().then(() => setState()), []);
return (
<Component state={state}>
<Modal onButtonClick={onButtonClick} />
</Component>
)
}
const Modal = memo(({onButtonClick}) => {
})
So in this case, at every render, memo will compare if all Modal props are === from previous render, which is now the case, because memoization of onButtonClick with useCallback, and so your Modal component will not re-render
https://reactjs.org/docs/hooks-reference.html#usememo

React / Functional component / Conditional render on callback / Not Working

Why this does not work ?
import React from 'react';
function Room() {
let check = null;
const ibegyouwork = () => {
check = <button>New button</button>;
}
return (
<div>
<button onClick={ibegyouwork}>Display my button now !!!!</button>
{check}
</div>
);
}
export default Room;
And this works fine ?
import React from 'react';
function Room() {
let check = null;
return (
<div>
<button>No need for this button because in this case the second button is auto-displayed</button>
{check}
</div>
);
}
export default Room;
Basically I try to render a component based on a condition. This is a very basic example. But what I have is very similar. If you wonder why I need to update the check variable inside that function is because in my example I have a callback function there where I receive an ID which I need to use in that new component.
The example that I provided to you is basically a button and I want to show another one when I press on this one.
I am new to React and despite I searched in the past 2 hours for a solution I couldn't find anything to address this issue.
Any tips are highly appreciated !
Your component has no idea that something has changed when you click the button. You will need to use state in order to inform React that a rerender is required:
import React, {useState} from 'react'
function Room() {
const [check, setCheck] = useState(null);
const ibegyouwork = () => {
setCheck(<button>New button</button>);
}
return (
<div>
<button onClick={ibegyouwork}>Display my button now !!!!</button>
{check}
</div>
);
}
export default Room;
When you call setCheck, React basically decides that a rerender is required, and updates the view.
The latter is working because there are no changes to the check value that should appear on the DOM.
If check changes should impact and trigger the React render function, you would want to use a state for show/hide condition.
import React from 'react';
const Check = () => <button>New button</button>;
function Room() {
const [show, setShow] = React.useState(false);
const ibegyouwork = () => {
setShow(true);
}
return (
<div>
<button onClick={ibegyouwork}>Display my button now !!!!</button>
{show && <Check />}
</div>
);
}
export default Room;

Trigger usePosition() custom hook onClick element

I'm facing some troubles when I try to trigger usePosition hook in a onClick event.
What I want to achieve is to delay the geolocation permission prompt triggered by the browser until the user clicks some element.
What I've tried so far is a bunch of variations of the following code, but without success:
const IndexPage = () => {
const [geolocation, setGeolocation] = useState({});
const [isGeolocationActive, triggerGeolocation] = useState(false);
let currentPosition = usePosition();
function handleGeolocation() {
if (isGeolocationActive) {
setGeolocation(currentPosition)
console.log('geolocation', geolocation)
} else triggerGeolocation(true);
}
return (
<Layout>
<Wrapper type={`list`} classNames={`wrapper pet__cards cards__list`}>
<Card>
<Image/>
<div onClick={() => handleGeolocation()}>Click me to share my location</div>
I've tried to set a useState hook (initially set to false) which should control and handle the usePosition hook if is set to true but the browser still asking for geolocation permission as soon as I land on the page.
Edit: I've also tried to put the usePosition hook in another component and call it onClick event. However, in this case, I face some hooks rules error such as:
"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..."
Finally I've solved the issue using usePosition hook in another component, something like this:
import React from "react";
import {usePosition} from "use-position";
const Geolocation = () => {
let currentPosition = usePosition();
return (
<div>
<p>{currentPosition.latitude}</p>
</div>
)
};
export default Geolocation;
While in my main component I've used a useState hook which controls the rendering like this:
const IndexPage = () => {
const [geolocation, setGeolocation] = useState('');
function handleGeolocation() {
setGeolocation(<Geolocation/>);
}
return (
<Layout>
<Card>
<Image/>
<div onClick={() => handleGeolocation()}>Click me to share my location</div>
{geolocation}
...

Why isn't useContext updating my value when imported in ReactJS?

I'm trying to use the createContext and useContext features of ReactJS to create a notification icon that displays a number of notifications increasing by 1 each second (essentially trying to figure out how to pass a state between files/components), but am unable to get the value to transfer over.
The timer appears to be working fine and the seconds variable in the timer file is definitely increasing, it's just the secs in the NavBar that isn't updating correctly.
--- App File ---
function App() {
return (
<div>
<NavBar />
<Timer />
</div>
);
}
export default App;
---Timer File---
...
export const secContext = createContext()
const Timer = () => {
const [seconds, setSeconds] = useState(0);
...
useEffect(() => {
...
if (isActive) {
interval = setInterval(() => {
setSeconds(seconds => seconds + 1);
}, 1000);}
...
return (
...
<secContext.Provider value={seconds}>
<div className="time">
{seconds}s
</div>
...
</secContext.Provider>
...
);
};
---Navigation Bar File---
...
import {secContext} from './Timer'
export default function NavBar() {
const secs = useContext(secContext)
return (
...
<Badge badgeContent={secs}>
<NotificationsIcon />
</Badge>
...
);
}
I was expecting {secs} in the Navigation Bar file to update to be equal the value of the seconds value from the Timer, but instead it appears to remain null.
Here is a screenshot of the interface: https://gyazo.com/d283360091c9d4ea8d9b2785419ad665
In this case the notification icon should have a badge with the number 10.
Context values are only accessible if they are rendered within the hierarchy i.e.
<secContext.Provider value={x}>
<NavBar />
</secContext.Provider>
NavBar doesn't appear to be a descendant of your context, your context is created inside Timer and therefore is only accessible from descendants of that component e.g.
<Timer>
<NavBar />
</Timer>
Update
As per the comments discussion, if you simply want to share data between a couple of siblings, it would probably make more sense to just move the common up to the parent (App) and then pass the data into the siblings as props - see example.

Categories

Resources