useContext - Child component loses state after parent state is changed - reactjs

Here is a pseudo code example of my app structure. I am trying to share state globally with react context, but updating state at the top level is causing issues with Child components re-rendering and resetting state (I think).
// My top level where I store the state all other components need
function App() {
const [userData, setUserData] = useState()
const userContext = {
userData,
setUserData
}
return (
<App>
<Context.Provider value={userContext}>
<Child />
<Child />
<Child />
</Context.Context.Provider>
</App>
)
}
// My child component where I want to preserve state
const Child = () => {
const [childState, setChildState] = useState('default value')
// I want to keep this value
setChildState('new value')
// This is causing App.js to re-render, then Child to rerender, and I lose child state. Then on the next render my 'childState' is back to 'default value', when I want it to still be 'new value'
const userContext = useContext(...)
userContext.setUserData('some change to userdata')
return {
...
}
}
My questions:
Is this an okay app structure, or is there a problem with it? Is there something I can do to persist the state in the Child componenet, or do I need to move the shared state out of App.js somehow?

You have structured the useContext wrong. You can check in React hooks how to use it.
One example with full-functionality which it may help you is:
const AppContext = React.createContext();
const AppProvider = ({ children }) => {
const [userData, setUserData]=useState('nothing here')
// any code you want to pass in the code
//e.g. a function
const randomFunction = ()=>{
//do something here
}
return (
<AppContext.Provider
value={{
userData,
setUserData,
randomFunction
}}
>
{children}
</AppContext.Provider>
);
};
export const useGlobalContext = () => {
return useContext(AppContext);
};
export { AppContext, AppProvider };
then all you have to do is to wrap all the components (children) you want, e.g. wrap <App /> so, more or less everything:
<AppProvider>
<App />
</AppProvider>
So now in this case you can use everything from your AppContext in all your code, you can pass more variables and functions if you want, and you import that by using:
import { useGlobalContext } from '/pathYouHaveIt';
function App() {
const {
userData,
setUserData,
randomFunction,
} = useGlobalContext();
// now you can use those like you have them set-up in the App()

Provide state from parent to child like props, it wood be greater than useContext.
function App() {
const [userData, setUserData] = useState()
return (
<div className="App">
<Child userData={userData}, setUserData={setUserData}/>
</div >
)
}
And the child component has the form
const Child = ({userData, setUserData}) => {
const [childState, setChildState] = useState('default value')
setChildState('new value')
setUserData('some change to userdata')
return {
...
}
}

Related

How to change useState from inside of Child Component

I am trying to import CSVs inside a Importer Component and pass on the Data to the Parent and change useState there...
So here i am trying to call said Component and pass on the useState function.
const [database, setDatabase] = useState([]);
useEffect(() => {
<Importer setdata={(data) => setDatabase([...data])} />;
}, []);
and Child Component is importing the CSV and passing on the data to be displayed after changing the State with useState:
const importAllCsv = (props) => {
text("template.csv").then((data) => {
//console.log(data);
const psv = dsvFormat(";");
//console.log(psv.parse(data));
DATABASE = psv.parse(data);
console.log(DATABASE);
props.setdata(DATABASE);
});
};
export default function Importer(props) {
return importAllCsv(props);
}
Components must start with a capital letter, also avoid returning components in useEffect when you can return them in the return part of the parent component.
As Aliyan said, try props.setdata((prevState) => [...prevState, ...DATABASE])
As per my understanding, you want to update the parent's state through a child component, for this you can simply pass the currentState (if required) and the setState function to the child as a prop using the following method :
export default function App() { //Parent Component
const [state, setState] = useState(1);
return (
<div className="App">
<div>{state}</div>
<ChildComponent setParentState={setState} currentState={state}/>
</div>
);
}
function ChildComponent({ setParentState, currentState }) {
function UpdateState() {
setParentState(currentState+1);
}
return <button onClick={() => UpdateState()}>Update State</button>;
}
Try to:
props.setdata((prevState) => [...prevState, ...DATABASE])
and try to include it on the return statement:
return (
<Importer setdata={setDatabase} />
);
not on useEffect hook.

Is useCallback best way to get props data from child component to parent components?

Is useCallback best way to get props data from child component to parent components?
From my perspective there are two common options here:
Passing down a callback function (created using useCallback or otherwise):
...
function Parent() {
const [text, setText] = useState('');
// Can also just pass setText directly
function onClick() {
setText('new text');
}
return (
<Child onClick={onClick} />
);
}
function Child(props) {
return (
<button onClick={props.onClick}>Click Me</button>
);
}
Pros: Simple
Use the Context API and consume the state
If the child is deeply nested, to avoid prop drilling (and to make the state easily available to other components), you could use the ContextAPI:
TextProvider.js
...
const TextContext = createContext('');
export function TextProvider(children) {
const [text, setText] = useState('');
const value = {
text, setText
};
return <TextContext.Provider value={text}>
{children}
</TextContext.Provider>;
};
export function useText() {
const context = useContext(ClientContext);
if (context === undefined) {
throw new Error('useSocket must be used within a SocketProvider');
}
return context;
};
App.js (or whatever file renders the <Parent />) - wrap the Parent in the provider:
function App() {
return (
...
<TextProvider>
<Parent />
</TextProvider>
...
);
}
Child.js
function Child(props) {
const { text, setText } = useText();
function onClick() {
setText('hello'); // Will update "text" state on parent
}
return (
<button onClick={onClick}>Click Me</button>
);
}
Pros: Useful when passing props into a deeply nested component. This post here details some more pros/cons of the ContextAPI

how to handle props between components in react

i have came accross a problem where i am passing three props to a component Landingheader from parent Landing.js now i have another component called Cart and i want to use LandingHeader
as child component inside Cart but then i would also have to pass all the three props again to Landingheader which is very difficult and alot of code to rewrite
here is the code in Landing.js
<div>
<Landingheader
fetchproductResults={fetchproductResults}
user={user}
cartValue={cartValue}
/>
above you can see landingHeader component is getting three differenct props
here is my cart component where i want to resuse landingHeader component
import { Fragment } from "react";
import Landingheader from "./landingHeader";
const Cart = () => {
return (
<Fragment>
<Landingheader />
</Fragment>
);
}
export default Cart;
so above the landingHeader will now require three props so this means i would have to rewrite the whole logic again? how to solve this propblem? thanks
code for fetchproductResults
const fetchproductResults = (keyword) => {
setWord(keyword);
if (keyword !== "") {
const searchedRs = allproducts.filter((eachproduct) => {
return Object.values(eachproduct)
.join("")
.toLowerCase("")
.includes(keyword.toLowerCase());
});
setResult(searchedRs);
} else {
setResult(allproducts);
}
};
In case you don't need to pass any props to Landingheader from Cart you could use default value props in Landingheader. Something like:
const Landingheader = (props) => {
const { fetchproductResults = [], user = "", cartValue = "" } = props;
return (...);
}
export default Landingheader;
You can use context instead of props
in Landing component:
const MyContext=createContext(null)
const Landing=()=>{
.......
return (<MyContext.Provider value={[fetchproductResults,user,cartValue]}>
... all child compoenents
</MyContext.Provider/>
Now in Landingheader :
const [fetchproductResults,user,cartValue]=useContext(MyContext) /// use them as you like
Now you don't need to pass any props to either Cart or LandingHeader, it is receiving the data through context.

React HoC - props are not passed to wrapped component

I have two HoC component. First have to serve as some Layout wrapper which will contain some logic for mobile rendering etc.
const LayoutWrapper = (Component: React.FC<any>): React.FC<any> => {
const Layout = () => {
const [layout, set] = React.useState("layout state");
return <Component
layout={layout}
/>;
}
return Layout;
} export default LayoutWrapper;
Second HoC will take care of if user is logged in.
const Secured = (Component: React.FC<any>): React.FC<any> => {
const Wrapped = () => {
const [securedPagestate, set] = React.useState("secured page state");
const Layout = LayoutWrapper(Component);
return <Layout test={securedPagestate} />
}
return Wrapped;
}
export default Secured;
I have wrapped homepage component which will render actual page, and it needs to have props passed from both HoC components which are shown above, but I only get props passed from LayoutWrapper Hoc and not from Secured Hoc component. What is actually wrong with it?
const HomepageView = (props: HomepageViewProps) => {
return <>HOMEPAGE</>;
}
export default Secured(HomepageView);
If you want to pass props to your wrapped components, you have to do it this way:
const Layout = (props) => {
const Wrapped = (props) => {
In the React world, HOC are functions, not components, therefore they should start with a lower case letter: layoutWrapper and secured
// HIGHER ORDER COMPOENTS IN REACT
// Higher order components are JavaScript functions used for adding
// additional functionalities to the existing component.
// file 1: hoc.js (will write our higher order component logic) -- code start -->
const messageCheckHOC = (OriginalComponent) => {
// OriginalComponent is component passed to HOC
const NewComponent = (props) => {
// business logic of HOC
if (!props.isAllowedToView) {
return <b> Not Allowed To View The MSG </b>;
}
// here we can pass the props to component
return <OriginalComponent {...props} />;
};
// returning new Component with updated Props and UI
return NewComponent;
};
export default messageCheckHOC;
// file 1: hoc.js -- code end -->
// file 2: message.js -- code start -->
// this is the basic component we are wrapping with HOC
// to check the permission isAllowedToView msg if not display fallback UI
import messageCheckHOC from "./hoc";
const MSG = ({ name, msg }) => {
return (
<h3>
{name} - {msg}
</h3>
);
};
export default messageCheckHOC(MSG);
// file 2: message.js -- code end -->
// file 3 : App.js -- code start --->
import MSG from "./message.js";
export default function App() {
return (
<div className="App">
<h3>HOC COMPONENTS </h3>
<MSG name="Mac" msg="Heyy !!! " isAllowedToView={true} />
<MSG name="Robin" msg="Hello ! " isAllowedToView={true} />
<MSG name="Eyann" msg="How are you" isAllowedToView={false} />
</div>
);
}
// file 3 : App.js -- code end --->

Memoize functional component using react-redux, reselect and React.memo()

I have built an app on ReactJS 16.8.5 and React-Redux 3.7.2. When the app loads the app mounts, initial store is set and database subscriptions are set up against a Firebase Realtime Database.
The app contains a header, Sidebar and content section.
I have implemented reselect along with React.memo to avoid rerendring when props change, but the Sidebar component is still re-rendering.
Using React profiler API and a areEqual comparison function in React.memo I can see that the Sidebar is being rendered several times although props are equal.
app.js
//Imports etc...
const jsx = (
<React.StrictMode>
<Provider store={store}>
<AppRouter />
</Provider>
</React.StrictMode>
)
let hasRendered = false
const renderApp = () => {
if (!hasRendered) { //make sure app only renders one time
ReactDOM.render(jsx, document.getElementById('app'))
hasRendered = true
}
}
firebase.auth().onAuthStateChanged((user) => {
if (user) {
// Set initial store and db subscriptions
renderApp()
}
})
AppRouter.js
//Imports etc...
const AppRouter = ({}) => {
//...
return (
<React.Fragment>
//uses Router instead of BrowserRouter to use our own history and not the built in one
<Router history={history}>
<div className="myApp">
<Route path="">
<Sidebar ...props />
</Route>
//More routes here...
</div>
</Router>
</React.Fragment>
)
}
//...
export default connect(mapStateToProps, mapDispatchToProps)(AppRouter)
Sidebar.js
//Imports etc...
export const Sidebar = (props) => {
const onRender = (id, phase, actualDuration, baseDuration, startTime, commitTime) => {
if (id !== 'Sidebar') { return }
console.log('onRender', phase, actualDuration)
}
return (
<Profiler id="Sidebar" onRender={onRender}>
<React.Fragment>
{/* Contents of Sidebar */}
</React.Fragment>
</Profiler>
}
const getLang = state => (state.usersettings) ? state.usersettings.language : 'en'
const getMediaSize = state => (state.route) ? state.route.mediaSize : 'large'
const getNavigation = state => state.navigation
const getMyLang = createSelector(
[getLang], (lang) => console.log('Sidebar lang val changed') || lang
)
const getMyMediaSize = createSelector(
[getMediaSize], (mediaSize) => console.log('Sidebar mediaSize val changed') || mediaSize
)
const getMyNavigation = createSelector(
[getNavigation], (navigation) => console.log('Sidebar navigation val changed') || navigation
)
const mapStateToPropsMemoized = (state) => {
return {
lang: getMyLang(state),
mediaSize: getMyMediaSize(state),
navigation: getMyNavigation(state)
}
}
const areEqual = (prevProps, nextProps) => {
const areStatesEqual = _.isEqual(prevProps, nextProps)
console.log('Sidebar areStatesEqual', areStatesEqual)
return areStatesEqual
}
export default React.memo(connect(mapStateToPropsMemoized, mapDispatchToProps)(Sidebar),areEqual)
Initial render looks ok up until Sidebar navigation val changed - after that the component re-renders a whole bunch of times - why!?
Console output - initial render
onRender Sidebar mount 572
Sidebar mediaSize val changed
Profile Sidebar areEqual true
Sidebar navigation val changed
onRender Sidebar update 153
Sidebar navigation val changed
onRender Sidebar update 142
onRender Sidebar update 103
onRender Sidebar update 49
onRender Sidebar update 5
onRender Sidebar update 2
onRender Sidebar update 12
onRender Sidebar update 3
onRender Sidebar update 2
onRender Sidebar update 58
onRender Sidebar update 2
onRender Sidebar update 4
onRender Sidebar update 5
onRender Sidebar update 4
The subsequent render does not affect any part of the store that is mapped to props (location), but component is still re-rendering.
Console output - subsequent render
Profile Sidebar areEqual true
onRender Sidebar update 76
onRender Sidebar update 4
I expect Sidebar to be memoized and only render/re-render a few times during mount/update of store during initial load.
Why is the Sidebar component being rendered so many times?
Kind regards /K
The React.memo is not needed because react-redux connect will return a pure component that will only re render if you change the props passed or after a dispatched action caused any changes in the state.
Your mapStateToPropsMemoized should work (see update) but probalby better to write it this way:
const mapStateToPropsMemoized = createSelector(
getMyLang,
getMyMediaSize,
getMyNavigation,
(lang, mediaSize, navigation) => ({
lang,
mediaSize,
navigation,
})
);
//using react.redux connect will return a pure component and passing that
// to React.memo should cause an error because connect does not return a
// functional component.
export default connect(
mapStateToPropsMemoized,
mapDispatchToProps
)(Sidebar);
UPDATE
Your getState should work.
I cannot reproduce component re rendering with your code. The object returned from mapState is a new object every time but it's direct properties never change because the selectors always return memoized result. See example below
const { useRef, useEffect } = React;
const {
Provider,
useDispatch,
connect,
useSelector,
} = ReactRedux;
const { createStore } = Redux;
const { createSelector } = Reselect;
const state = { someValue: 2, unrelatedCounter: 0 };
//returning a new state every action someValue
// never changes, only counter
const reducer = (state) => ({
...state,
unrelatedCounter: state.unrelatedCounter + 1,
});
const store = createStore(
reducer,
{ ...state },
window.__REDUX_DEVTOOLS_EXTENSION__ &&
window.__REDUX_DEVTOOLS_EXTENSION__()
);
//selectors
const selectSomeValue = (state) => state.someValue;
//selectors only return a new object if someValue changes
const selectA = createSelector(
[selectSomeValue],
() => ({ value: 'A' }) //returns new object if some value changes
);
const selectB = createSelector(
[selectSomeValue],
() => ({ vale: 'B' }) //returns new object if some value changes
);
const selectC = createSelector(
[selectSomeValue],
() => ({ vale: 'C' }) //returns new object if some value changes
);
const Counter = () => {
const counter = useSelector(
(state) => state.unrelatedCounter
);
return <h4>Counter: {counter}</h4>;
};
const AppComponent = (props) => {
const dispatch = useDispatch();
const r = useRef(0);
//because state.someValue never changes this component
// never gets re rendered
r.current++;
useEffect(
//dispatch an action every second, this will create a new
// state but state.someValue never changes
() => {
setInterval(() => dispatch({ type: 88 }), 1000);
},
[dispatch] //dispatch never changes but linting tools don't know that
);
return (
<div>
<h1>Rendered {r.current} times</h1>
<Counter />
<pre>{JSON.stringify(props, undefined, 2)}</pre>
</div>
);
};
const mapStateToProps = (state) => {
return {
A: selectA(state),
B: selectB(state),
C: selectC(state),
};
};
const App = connect(mapStateToProps)(AppComponent);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/reselect/4.0.0/reselect.min.js"></script>
<div id="root"></div>

Resources