How to change context value in functional component? - reactjs

I have a context named StatusContext like this:
export const statusCtxInit = {
open: false,
toggleOpen() {
this.open = !this.open;
}
};
const StatusContext = React.createContext(statusCtxInit);
export default StatusContext
The whole app is wrapping with the provider:
// ...
<StatusContext.Provider value={statusCtxInit}>
// ...
To use the values of my context I use useContext in my FC and it works when I get the value.
function MyComp() {
const status = useContext(StatusContext);
return (
<div>
{status.open
? `It's Open`
: `It's Closed`}
<button onClick={() => status.toggleOpen()}>
Toggle
</button>
</div>
)
}
export default MyComp
On the other hand, I also want to change the context by calling the toggleOpen but, it does not work as I want. Actually the value changes but not effect the MyComp.
What I did wrong? What shall I do?

import React from 'react';
const statusContext = React.createContext();
const {Provider} = statusContext;
// custom provider
export const StatusProvider = ({children}) => {
const [isOpen, setOpen] = React.useState(false)
const toggle = () => setOpen(v => !v)
return (
<Provider value={{isOpen, toggle}}>
{children}
</Provider>
)
}
//custom hook
export const useStatus = () => React.useContext(StatusContext)
//usage
function MyComp() {
const status = useStatus()
return (
<div>
{status.isOpen
? `It's Open`
: `It's Closed`}
<button onClick={() => status.toggle()}>
Toggle
</button>
</div>
)
}
export default MyComp

Related

React Context Not Updating when clicking open modal button

I am trying to open a modal by updating the state of the component. The component is wrapped in a Context Provider.
Although the button seems to be clicking successfully, the Modal will not open
Here is the code with the container which contains the "Open Modal Button"
import { type FC, useRef } from 'react'
import infomation from '#/assets/icons/infomation.svg'
import { useModal } from '#/providers/ModalProvider'
import styles from './Instructions.module.scss'
const Instructions: FC = () => {
const card = useRef<HTMLDivElement>(null)
const { openDemoModal } = useModal()
const onOpenModalClick = () => {
openDemoModal()
console.log(openDemoModal)
}
return (
<section ref={card} className={`card ${styles.card}`}>
<div className={styles.background} />
<div>OPEN THE MODAL DOWN BELOW</div>
<button variant="outlined" fullWidth onClick={onOpenModalClick}>
Open Modal
</button>
</section>
)
}
export default Instructions
Here is the file which contains the Context for the Modal, I have tried setting up the context in INITIAL_STATE and tried updating it using the "onOpenModalClick" function - but it doesn't seem to be able to update the ShowModal.current value below.
import { type FC, type PropsWithChildren, createContext, useContext, useRef } from 'react'
import Modal from '#/components/modal/Modal'
type ContextValue = {
showModal: boolean
openDemoModal: () => void
}
const INITIAL_STATE: ContextValue = {
showModal: false,
openDemoModal: () => {},
}
export const ModalContext = createContext(INITIAL_STATE)
export const ModalProvider: FC<PropsWithChildren> = ({ children }) => {
const showModal = useRef(INITIAL_STATE.showModal)
const openDemoModal = () => {
showModal.current = true
}
console.log(showModal.current)
return (
<ModalContext.Provider value={{ showModal: showModal.current, openDemoModal }}>
{children}
<Modal show={showModal.current} setShow={(shouldShow: boolean) => (showModal.current = shouldShow)} />
</ModalContext.Provider>
)
}
export function useModal() {
const context = useContext(ModalContext)
if (!context) {
throw new Error('useModal must be used within a ModalProvider')
}
return context
}
Is there any way to update the onOpenModalClick button to make it change the value of showModal.current in the Provider file?
Sorry if the post is unclear, this is my first Stack Overflow post. Let me know if I need to post anymore of the components.
I tried to add a button to the component which updates the Context, however the state failed to update
The useRef does not trigger a re-render when the value changes. Instead you can use a useState hook. Which would look something like this.
type ContextValue = {
showModal: boolean;
openDemoModal: () => void;
};
const INITIAL_STATE: ContextValue = {
showModal: false,
openDemoModal: () => console.warn('No ModalProvider'),
};
export const ModalContext = createContext(INITIAL_STATE);
export const ModalProvider: FC<PropsWithChildren> = ({ children }) => {
const [showModal, setShowModal] = useState(false);
const openDemoModal = () => {
setShowModal(true)
};
console.log(showModal);
return (
<ModalContext.Provider
value={{ showModal, openDemoModal }}
>
{children}
<Modal
show={showModal}
setShow={setShowModal}
/>
</ModalContext.Provider>
);
};

How can I send state to another component in react?

I would like to ask how can I send state from component to another component?
I have 3 components and I want to send data between them. So I have an input component where I handle an IP call and I want to send this shortedLink state to another component, so I can render that data. I don't know that is it clear what I want to do, but I hope so :D
import ShortedLinks from './ShortedLinks'
const testimonials = () => {
return (
<div>
<ShortedLinks />
</div>
);
};
export default testimonials;
const shortedLinks = () => {
return (
<div>
<h1>I want to get the state here</h1>
</div>
);
};
export default shortedLinks;
const InputSection = () => {
const [shortedLink, setShortedLink] = useState("")
return (...);
};
export default InputSection;
You can use the props to achieve it like this :
import ShortedLinks from './ShortedLinks'
const Testimonials = () => {
const [shortedLink, setShortedLink] = useState("")
return (
<div>
<ShortedLinks shortedLink={shortedLink} /> // Pass props here
</div>
);
};
export default Testimonials;
And then in your ShortedLinks component
const ShortedLinks = ({shortedLink}) => { // Get props here
return (
<div>
<h1>{shortedLink}</h1>
</div>
);
};
export default ShortedLinks;
And if you can't use the props like this you can use the useContext like this :
import React,{ useState, createContext } from "react";
export const ShortedLinkContext = createContext('');
const InputSection = () => {
const [shortedLink, setShortedLink] = useState("")
return (
<ShortedLinkContext.Provider value={shortedLink}>
....
</ShortedLinkContext.Provider>
);
};
export default InputSection;
And finally you can comsume the context here :
import {ShortedLinkContext} from ....
const ShortedLinks = () => {
const shortedLink = useContext(ShortedLinkContext);
return (
<div>
<h1>{shortedLink}</h1>
</div>
);
};
export default shortedLinks;
Enjoy :)

Is possible to build compound component with separated files? REACT

I'm trying to understand React's compound pattern. In all exercises all components are in one file. Is it possible to build component with that pattern with external components?
I would achieve that scenario:
src:
components:
Main
Component1
Component2
Component3
// ONE FILE Main.js
import {CompoundComponent1, CompoundComponent2, CompoundComponent3} './foo'
const Main = () => {
const [on, setOn] = React.useState(false)
const toggle = () => setOn(!on)
const CompoundComponent1 = Component1;
const CompoundComponent2 = Component2;
const CompoundComponent3 = Component3;
return <Switch on={on} onClick={toggle} />
}
Main.C1 = CompoundComponent1
Main.C2 = CompoundComponent2
Main.C3 = CompoundComponent3
// ONE FILE END
App.js
const App = () => {
<Main>
<Main.C1>FOO</Main.C1>
// etc.
</Main>
}
I think that i found solution.
import * as React from 'react'
import {Switch} from '../switch'
import {ToggleOn} from './02/ToggleOn'
import {ToggleOff} from './02/ToggleOff'
import {ToggleButton} from './02/ToggleButton'
function Toggle() {
const [on, setOn] = React.useState(false)
const toggle = () => setOn(!on)
return <Switch on={on} onClick={toggle} />
}
Toggle.ToggleOn = ToggleOn
Toggle.ToggleOff = ToggleOff
Toggle.ToggleButton = ToggleButton
function App() {
return (
<div>
<Toggle>
<Toggle.ToggleOn>Turn ON</Toggle.ToggleOn>
<Toggle.ToggleOff>Turn OFF</Toggle.ToggleOff>
<Toggle.ToggleButton />
</Toggle>
</div>
)
}
export default App
In separated files:
export const ToggleButton = ({on, toggle}) => (
<Switch on={on} onClick={toggle} />
)
export const ToggleOn = ({on, children}) => {
if (on) {
return children
}
return null
}

React: Is there a way to access component state from function in another file?

I've a react component which includes a large function that updates the component state, the function is large so I want to move it to a separate file and export it in the react component. But I don't find anyway to access the component state if I move the function to its own file.
Is there anyway to do this ?
example:
component.tsx
import { myFunction } from './function.ts'
const [toggle, setToggle] = useState(false)
const my_component = () => {
return (
<div>
<button onClick={myFunction}>Run function</button>
</div>
)
}
export default my_component
function.ts
export const myFunction = () => {
// do something that updates `toggle`
}
you can do the logic apart from the component and return the result to the component. have a look at the code below.
https://codesandbox.io/s/hopeful-dubinsky-930p7?file=/src/App.js
This is just a raw example of what you can do with custom state hooks (reference: https://dev.to/spukas/react-hooks-creating-custom-state-hook-300c)
import React from 'react';
export function useMyFunction(value) {
const [toggle, setToggle] = React.useState(value || false);
const myFunction = () => {
// do something that updates `toggle` with setToggle(...)
}
return { toggle, myFunction };
}
import { useMyFunction } from './function.ts'
const my_component = () => {
const [toggle, myFunction] = useMyFunction(false)
return (
<div>
<button onClick={myFunction}>Run function</button>
</div>
)
}
export default my_component
This can be achieved by 2 different ways one using HOC components and another just by using functions.
Approach 1: Using HOC
handler.js
const withHandlers = (WrappedComponent) => {
class HandlerComponent extends Component {
state = {toggle:false};
myFunction = () => {
//Do your update here
}
render() {
return <WrappedComponent
toggle={this.state.toggle
myFunction={this.myFunction}
/>
}
};
my_component.js
const my_component = (props) => {
return (
<div>
<button onClick={props.myFunction}>Run function</button>
</div>
}
export default withHandlers(my_component);
Approach 2: Using Functions
handler.js
export const myFunction(toggle) => {
return !toggle; //return the changed value
}
my_component.js
const my_component = () => {
const [toggle, setToggle] = useState(false);
const myFunction = () => {
setToggle(handler.myFunction); //the state will be passed as a parameter by default
};
return(
<div>
<button onClick={myFunction}>Run function</button>
</div>
);
};
For the toggle to work, it must be passed to the function as a props then for update it used state management (redux or react context).
The best solution is to define the toggle in the function itself and pass it a Boolean props to control it.
import { myFunction } from './function.ts'
const my_component = () => {
return (
<div>
<button onClick={myFunction(false)}>Run function</button>
</div>
)
}
export default my_component
function.ts
export const myFunction = (props) => {
const [toggle, setToggle] = useState(props || false);
// your codes
};

mobx-react-lite useObserver hook outside of render

I've seen examples of the useObserver hook that look like this:
const Test = () => {
const store = useContext(storeContext);
return useObserver(() => (
<div>
<div>{store.num}</div>
</div>
))
}
But the following works too, and I'd like to know if there's any reason not to use useObserver to return a value that will be used in render rather than to return the render.
const Test = () => {
const store = useContext(storeContext);
var num = useObserver(function (){
return store.num;
});
return (
<div>
<div>{num}</div>
</div>
)
}
Also, I don't get any errors using useObserver twice in the same component. Any problems with something like this?
const Test = () => {
const store = useContext(storeContext);
var num = useObserver(function (){
return store.num;
});
return useObserver(() => (
<div>
<div>{num}</div>
<div>{store.num2}</div>
</div>
))
}
You can use observer method in the component. And use any store you want.
import { observer } from "mobx-react-lite";
import { useStore } from "../../stores/StoreContext";
const Test = observer(() => {
const { myStore } = useStore();
return() => (
<div>
<div>{myStore.num}</div>
<div>{myStore.num2}</div>
</div>
)
}
);
StoreContext.ts
import myStore from './myStore'
export class RootStore{
//Define your stores here. also import them in the imports
myStore = newMyStore(this)
}
export const rootStore = new RootStore();
const StoreContext = React.createContext(rootStore);
export const useStore = () => React.useContext(StoreContext);

Resources