How to share stateSetter functions using the useContext hook? - reactjs

I have the following code high up in the component hierarchy:
import VisibilityContext from '../visibilityContext';
...
const [showEditModal, setEditModalVisibility] = useState(false);
...
<VisibilityContext.Provider value={{setEditModalVisibility}}>
<EditModal/>
</VisibilityContext.Provider>
And within the EditModal I have this piece of code:
import VisibilityContext from '../visibilityContext';
...
const {setEditModalVisibility} = useContext(VisibilityContext);
But the function setEditModalVisibility is empty when I console it out in the EditModal component. If I pass showEditModal instead of setEditModalVisibility, useContext gives me its correct value. I even tried putting setEditModalVisibility in the state using another useState, and passed it down, in case useContext required references to be stored in a state.
I just want components down the tree to be able to call the setEditModalVisibility function. And I want to be able to share this function without having to pass it down the tree as a prop.

Here is a sample code how you can effectively manage your state using Context.
import React, { createContext, useState, useContext } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
const VisibilityContext = createContext();
const Provider = props => {
const [visible, setVisible] = useState(false);
const value = { state: { visible }, actions: { setVisible } };
return (
<VisibilityContext.Provider value={value}>
{props.children}
</VisibilityContext.Provider>
);
};
function App() {
const { state, actions } = useContext(VisibilityContext);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<button onClick={() => actions.setVisible(!state.visible)}>
{state.visible ? "ON" : "OFF"}
</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(
<Provider>
<App />
</Provider>,
rootElement
);

Related

why components state data are gone in conditional rendering

I am new to react and come from a background of functional component only.
In my react project,
When I conditionally rendering , ie from false to true, the data inside child component will be gone.
Then I wonder why is that.
Then I heard a concept called unmounting. It means, when my condition change from true to false, the component will get unmounting. And in unmounting, the state inside will gone.
But then, it doesn't add up.
Q: Whenever we re-render any other components, just like the normal situation, we will also unmount component in order to do re-rendering. And our state value would not be gone.
Why this problem was happened especially on having conditional statement in react?
Edit:
My emphsis is not on how to avoid state loss. My question is that why data will be gone in conditional rendering. And why unmounts will cause such problem, but re rendering would not cause such ( both also involves unmounting)
Here is my code
In parent:
import React, { useEffect, useState } from "react";
import ReactDOM from "react-dom";
import Child1 from "./child";
import "./styles.css";
function Parent() {
const [message, setMessage] = useState("initial text");
const [showChild,setShowChild] = useState(true);
useEffect(() => {
console.log("useeffect in parent");
});
return (
<div className="App">
<button onClick={() => setShowChild(!showChild)}>show child</button>
{showChild?
<Child1 />
:
null
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<Parent />, rootElement);
In child:
import React, { useEffect, useState } from "react";
function Child1() {
useEffect(() => {
console.log("useeffect in child");
console.log("newMessage: " + newMessage);
});
const [newMessage, setNewMessage] = useState("");
return (
<div>
<input onChange={(event) => setNewMessage(event.target.value)} />
</div>
);
}
export default Child1;
Add some picture to illurste what I mean by data lose in conidtional rendering
enter
https://i.stack.imgur.com/UrIhT.png
click to not show it
https://i.stack.imgur.com/0OC87.png
click to show again
https://i.stack.imgur.com/4zlWk.png
Try moving all state management to the parent component and leave the child component 'dumb'. You can pass the setMessage and any other state variables to the child as props.
Parent:
import React, { useEffect, useState } from "react";
import ReactDOM from "react-dom";
import Child1 from "./child";
import "./styles.css";
function Parent() {
const [message, setMessage] = useState("initial text");
const [showChild,setShowChild] = useState(true);
useEffect(() => {
console.log("useeffect in parent");
});
return (
<div className="App">
<button onClick={() => setShowChild(!showChild)}>show child</button>
{showChild?
<Child1 setMessage={setMessage}/>
:
null
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<Parent />, rootElement);
Child:
import React from "react";
function Child1({setMessage}) {
return (
<div>
<input onChange={(event) => setMessage(event.target.value)} />
</div>
);
}
export default Child1;
The answer for your question is very simple, While unmounting you are removing the component itself from react-dom. The state, props and all the data's handled inside your component will be active only If the component is inside the react-dom. If the particular component is unmounted, all the states and props that was created and processed will also be removed from react-dom along with the component. And the fresh component, state and props will be mounted in the react-dom if you conditionally render the component again.

How to use react-router-dom with Context API V6?

I am changing the value in PC component but it is not reflected in the BR1 component. If I don't use react-router-dom, everything works fine, but I need the routes.
App.js code
import React, { createContext, useState } from 'react';
import './App.css';
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import BR1 from './Components/BR1';
import PC from './Components/P_c1'
import BR from './Components/BR';
export const BRcontext = createContext();
function App() {
const [value, setValue] = useState(false)
return (
<div>
<BRcontext.Provider value={{value, setValue}}>
<Router>
<Routes>
<Route path='/PC' element={<PC/>} />
<Route path='/BR1' element={<BR1/>} />
<Route path='/BR' element={<BR/>} />
</Routes>
</Router>
</BRcontext.Provider>
</div>
);
}
export default App;
PC code
import React, { useContext } from 'react'
import './Profile.css';
import { BRcontext } from '../App';
export default function Profile() {
const {value, setValue} = useContext(BRcontext);
return (
<div>
<div className='container mt-5'>
<div className='row'>
<div>
<h3 className='mt-5'>Send Request</h3>
<button className='btn btn-success mt-3 ps-3 pe-3' onClick={()=>{setValue(true)}}>Request</button>
</div>
</div>
</div>
</div>
)
}
BR1 code
import React, { useContext } from 'react'
import BR from './BR'
import { BRcontext } from '../App'
import { Link } from 'react-router-dom';
export default function BR1() {
const {value} = useContext(BRcontext);
// let navigate = useNavigate();
return (
<div>
{console.log(value)} //this remains false
{value ? <Link to="/BR"/>: console.log('hello there!')}
</div>
)
}
In BR1 code, I want the value to become true when a button in the PC component is clicked
Link - https://codesandbox.io/s/great-star-bzhuvw?file=/src/App.js
It seems there's no way to navigate from /PC to /BR1 unless changing the browser URL directly, and by doing this, you lose the current context value because it's in memory. If you intend to keep this behaviour, you should consider persisting the context value every time you change it and initialize it with the previously persisted one.
An example using the browser's local storage:
// Helper function to read the storaged value if it exists
function getPersistedValue() {
const serializedValue = localStorage.getItem('value')
try {
if (!serializedValue) {
throw new Error('No previously persisted value found')
}
return JSON.parse(serializedValue)
} catch {
return false
}
}
// Using the helper function to initialize the state
const [value, setValue] = useState(getPersistedValue())
// Synchronizing the persisted value on local storage with the in-memory one
useEffect(() => {
localStorage.setItem('value', JSON.stringify(value))
}, [value])
If you want, I forked your Code Sandbox and applied these changes: https://codesandbox.io/s/router-context-forked-uqhzye.

How do i get data from react hook.?

Im trying to get data , from an API and then consume it from another hook component.
But I have this error
This is my App.js
import React, {useEffect, useState} from 'react';
import {BrowserRouter as Router, Link} from 'react-router-dom';
import MainRouter from "./core/MainRouter";
import {MovieProvider} from "./context/MovieContext";
import axios from 'axios';
function App() {
const [tagCat,setTagCat] = useState([]);
const getMovieInfo = async ()=>{
const res = await axios.get('/get/tag_category');
//for testing
setTagCat(res.data.data)
};
useEffect(()=>{
getMovieInfo();
},[]);
return (
<MovieProvider value={tagCat}>
<Router>
<MainRouter/>
</Router>
</MovieProvider>
);
}
export default App;
MovieContext.js
import React, {useContext} from "react";
const MovieContext = React.createContext({});
const MovieProvider = MovieContext.Provider;
const MovieConsumer = MovieContext.Consumer;
export default MovieContext;
export {MovieProvider,MovieConsumer};
consume from that PageNav component
PageNav.js
import React, {useContext, useEffect, useState} from 'react';
import {Link} from 'react-router-dom';
import MovieContext,{MovieConsumer} from "../../context/MovieContext";
const PageNav = ()=>{
const tagCat = useContext(MovieContext);
const [category,setCategory] = useState([]);
useEffect(()=>{
setCategory(tagCat.main_category);
})
return (
<React.Fragment>
<div>
<header className="content__title">
<h1>Welcome! (Mingalarpar) <small>
Feel Free to use any data , btw we need more suggest from you.
</small></h1>
</header>
<div className="toolbar">
<nav className="toolbar__nav">
**{console.log(category[0])}**
<a className="active" href="#">Following</a>
Groups
</nav>
</div>
</div>
</React.Fragment>
)
};
export default PageNav;
I see the data in devtool,
but i cant map the data
i.e.
category.map()//error
Thank you
The value in ContextProvider is fetched asynchronously and hence won't be available on initial render of the component. If you try to set a specific field from it in state of PageNav component, it will throw you can error
You need to check for its existence before using, also you need not store the value obtained from context in state since you can directly derive it
const PageNav = ()=>{
const tagCat = useContext(MovieContext);
const [category,setCategory] = useState([]);
return (
<React.Fragment>
<div>
{/* othercode */}
<div className="toolbar">
<nav className="toolbar__nav">
{tatCat.main_category && tatCat.main_category.map(() => {})}
<a className="active" href="#">Following</a>
Groups
</nav>
</div>
</div>
</React.Fragment>
)
};
export default PageNav;
You are not passing object in Context provider in App.js. value={tagcat} will be replaced by value={{tagcat}} Try this
<MovieProvider value={{tagCat}}>
<Router>
<MainRouter/>
</Router>
</MovieProvider>

How to use react-hooks with redux and immutable js?

I am using react, redux with immutable js. I am facing a problem when I use useEffect or similar hooks with non-primitive data that effect is running even though it hasn't changed as I am using hoc recommended by redux docs.
Does anyone has a solution or best practice for using immutable js and redux with react hooks?
This is my parent component.
import { useSelector, useDispatch } from "react-redux";
import { setValue, getValue, getData } from "./store";
import "./styles.css";
import Child from "./Child";
export default function App() {
const data = useSelector(getData);
const value = useSelector(getValue);
const dispatch = useDispatch();
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<button onClick={() => dispatch(setValue(value + 1))}>
Change Value
</button>
<Child value={value} data={data} />
</div>
);
}
This is child component which is wrapped with toJS hoc
import { toJS } from "./hoc";
function Child(props) {
useEffect(() => {
// this runs even if only props.value has changed.
console.log(props.data, "changed");
}, [props.data]);
return <div>{props.value}</div>;
}
export default toJS(Child);
Reproducible example:

Is there a way to force multiple Context Consumers to share state?

I'm using the React Context API with the main intent of avoiding prop drilling. Right now my Context includes a useState and various functions that update the state - these are put into a const object that is passed as the value prop of ActionsContext.Provider. This is an abstraction of my current component hierarchy:
Header
---NavPanel
ContentContainer
---Content (Context.Consumer being returned in this component)
where Header and ContentContainer are sibling elements and NavPanel and ContentContainer are their respective children.
I initially put the Context.Consumer in Content because the other elements did not need it. However I'm building a feature now where NavPanel needs to know about the state that's managed by the Context. So I put another Consumer in NavPanel, only to find that a separate Consumer means a separate instance of the state.
Is there any smart workaround that gives NavPanel and Content access to the same state, that doesn't involve putting the Consumer in the parent component of Header and Content? That would result in a lot of prop drilling with the way my app is currently structured.
Codesandbox example of multiple instances: https://codesandbox.io/s/context-multiple-consumers-v2wte
Several things:
You should have only one provider for every state you want to share.
<ContextProvider>
<PartOne />
<hr />
<PartTwo />
</ContextProvider>
It is better to split your context in several contexts so you pass values instead of objects. This way when you update your state React will detect it is different instead of comparing the same object.
Your input should be a controlled component https://reactjs.org/docs/forms.html
Consider using the useContext API for better ergonomics if you are using React 16.8 instead of ContextConsumer.
With these changes, your code would be:
MyContext.js
import React, { useState } from "react";
export const MyItemContext = React.createContext();
export const MySetItemContext = React.createContext();
export const MyHandleKeyContext = React.createContext();
const ContextProvider = props => {
const [itemBeingEdited, setItemBeingEdited] = useState("");
const handleKey = event => {
if (event.key === "Enter") {
setItemBeingEdited("skittles");
} else if (event.key === "K") {
setItemBeingEdited("kilimanjaro");
} else {
setItemBeingEdited("");
}
};
const editFunctions = {
itemBeingEdited,
setItemBeingEdited,
handleKey
};
return (
<MyItemContext.Provider value={itemBeingEdited}>
<MyHandleKeyContext.Provider value={handleKey}>
<MySetItemContext.Provider value={setItemBeingEdited}>
{props.children}
</MySetItemContext.Provider>
</MyHandleKeyContext.Provider>
</MyItemContext.Provider>
);
};
export default ContextProvider;
PartOne.js
import React, { useContext } from "react";
import ContextProvider, {
MyContext,
MyItemContext,
MySetItemContext,
MyHandleKeyContext
} from "./MyContext";
const PartOne = () => {
// blah
const itemBeingEdited = useContext(MyItemContext);
const handleKey = useContext(MyHandleKeyContext);
const setItem = useContext(MySetItemContext);
return (
<React.Fragment>
<span>{itemBeingEdited}</span>
<input
placeholder="Type in me"
onKeyDown={handleKey}
value={itemBeingEdited}
onChange={e => setItem(e.target.value)}
/>
</React.Fragment>
);
};
export default PartOne;
PartTwo.js
import React, { useContext } from "react";
import ContextProvider, {
MyContext,
MyItemContext,
MySetItemContext,
MyHandleKeyContext
} from "./MyContext";
const PartTwo = () => {
// blah
const itemBeingEdited = useContext(MyItemContext);
const handleKey = useContext(MyHandleKeyContext);
const setItem = useContext(MySetItemContext);
return (
<React.Fragment>
<span>{itemBeingEdited}</span>
<input
value={itemBeingEdited}
type="text"
placeholder="Type in me"
onChange={e => setItem(e.target.value)}
onKeyDown={handleKey}
/>
</React.Fragment>
);
};
export default PartTwo;
index.js
import React from "react";
import ReactDOM from "react-dom";
import PartOne from "./PartOne";
import PartTwo from "./PartTwo";
import ContextProvider from "./MyContext";
import "./styles.css";
function App() {
return (
<div className="App">
<ContextProvider>
<PartOne />
<hr />
<PartTwo />
</ContextProvider>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
CodeSandbox: https://codesandbox.io/s/context-multiple-consumers-vb9oj?fontsize=14

Resources