React.memo issue with Redux - reactjs

I have two components.
function Parent(props){
const handleClick = () => {
console.log(props.stateA);
};
return <div><Child text={stateB} handleClick={handleClick} /></div>
}
const mapStateToProps = (state) => {
return {
stateA: state.stateA // stateA will be changed somewhere else
stateB: state.stateB
}
};
export default connect(mapStateToProps)(Parent);
function Child(props) {
return <div onClick={props.handleClick}>{props.text}</div>
}
export default React.memo(Child,(prev, next) => {
return prev.text === next.text
});
My problem is when stateA is changed somewhere, clicking on Child will log the previous stateA. I can't access the latest stateA.
You can see, I don't want to Child re-render when stateA changes,it should re-render only when stateB changed. But I want to access the latest stateA in Parent when clicking on Child.
Is there any method to solve this problem?

If the Parent component is a functional component then you can use like this
const [valueA, setValueA] = useState('')
useEffect(() => {
setValueA(props.stateA)
},[props.stateA])
console.log(valueA) // latest Value of stateA
return <div><Child text={stateB} handleClick={handleClick} /></div>
I hope it'll work for you.

You should be able to access props.stateA no problem
const handleClick = () => {
console.log(props.stateA);
};
because you accessing parent's props in handleClick. So if props.stateA is stale then the logical conclusion is the parent doesn't receive the latest props. Can we see how you update props/state?

The problem you are experiencing has nothing to do with Redux.
The Parent component passes 2 props to the child: the text which is changed when needed and handleClick which is changed each render of the Parent component - a new function is created each time.
But the React.memo is checking only the text prop, so the child receives a stale handleClick quite often.
The correct solution is to wrap the handleClick with useCallback and check all props in React.memo (react does this by default).
function Parent(props){
const handleClick = useCallback(() => {
console.log(props.stateA);
}, []);
return <div><Child text={stateB} handleClick={handleClick} /></div>
}
const mapStateToProps = (state) => {
return {
stateA: state.stateA // stateA will be changed somewhere else
stateB: state.stateB
}
};
export default connect(mapStateToProps)(Parent);
function Child(props) {
return <div onClick={props.handleClick}>{props.text}</div>
}
export default React.memo(Child);

You can keep a ref to stateA so it is what is logged when you call handleClick. useRef ensures that the last value is used.
function Parent(props){
const stateARef = useRef(props.stateA);
useEffect(() => {
stateARef.current = props.stateA;
}, [props.stateA])
const handleClick = () => {
console.log(stateARef.current);
};
return <div><Child text={stateB} handleClick={handleClick} /></div>
}

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.

React and React Hooks: Using an onClick function in a child to fire a function of a parent wrapping component

I have a wrapper component that conditionally renders it's children based on it's own state (isDeleted). Basically I have a 'delete-able item' component where if a button is clicked to delete, the item itself will be removed from the DOM (by returning an empty ReactNode i.e. <></>). The problem is, I can't figure out how to have the button click event, which appears as a child of the wrapper, to be passed INTO the wrapped component itself:
export default function App() {
return (
<DeleteableItemComponent>
{/* lots of other markup here */}
<button onClick={triggerFunctionInsideDeletableItemComponent}>
</DeleteableItemComponent>
)
}
and the most basic version of my delete-able item component:
export default function DeleteableItemComponent() {
const [isDeleted, setIsDeleted] = useState(false);
const functionIWantToFire = () => {
// call API here to delete the item serverside; when successful, 'delete' on frontend
setIsDeleted(true)
}
if (isDeleted) {
return <></>
}
return <>{children}</>
}
So put very simply, I just want to call the functionIWantToFire from the button onClick callback.
How can this be done properly via hooks? I've thought of using the context API but I've never seen it used to trigger function firing, only for setting values, and in this case I want to fire the event itself, not communicate specific values to the wrapper component. I also can't do it correctly through just passing a boolean prop, because then I can only set it once i.e. from false to true.
You could use React.cloneElement API to pass props to your child while iterating through it using React.children.map.
React.Children.map(children, (child) => {
return React.cloneElement(child, { /* .. props here */ });
});
A simple example would be.
You could check the example here
function App() {
return (
<Delete>
<Child1 />
<Child2 />
</Delete>
);
}
function Delete({ children }) {
const [clicked, setClicked] = React.useState(0);
const inc = () => setClicked(clicked + 1);
const dec = () => setClicked(clicked - 1);
return React.Children.map(children, (child) => {
return React.cloneElement(child, { inc, clicked, dec });
});
}
function Child1(props) {
return (
<div>
<p>{props.clicked}</p>
<button onClick={props.inc}>Inc</button>
</div>
)
}
function Child2(props) {
return (
<div>
<button onClick={props.dec}>Dec</button>
</div>
)
}

React avoid all child rerendering

So I have a master component which has several children, I simplified it with the example below
const Master = ({ c1props, c2props }) => {
const [count, setCount] = useState(0)
return <div>
<div>{count}</div>
<Child {...c1props}/>
<Child {...c2props}/>
</div>
}
So here my problem is that when I update only the state "count", the components are re-rendering, which is a problem because they are pretty heavy.
I was thinking about using useMemo() inside Child as a way to avoid those uneeded re-rendering but I don't if it's the best idea.
Any idea how to address this ?
thanks
const MemoizedChild1 = React.memo(Child1, isEqual);
const MemoizedChild2 = React.memo(Child2, isEqual);
Then you use it like this:
const Master = ({ c1props, c2props }) => {
const [count, setCount] = useState(0)
return <div>
<div>{count}</div>
<MemoizedChild1 {...c1props}/>
<MemoizedChild2 {...c2props}/>
</div>
}
where isEqual is a lodash function that deeply tests the equality of props.
Generally you can use memo like this:
function MyComponent(props) {
/* render using props */
}
function areEqual(prevProps, nextProps) {
/*
return true if passing nextProps to render would return
the same result as passing prevProps to render,
otherwise return false
*/
}
export default React.memo(MyComponent, areEqual);
Read more on the docs.
useMemo is a hook that memoizes a value, and memo can give you control about whenever to render your component or not.
if you want to avoid the usage of memo you may consider to split your Master, and create another Counter component which contains the count state:
const Master = ({ c1props, c2props }) => (
<div>
<Counter />
<Child {...c1props}/>
<Child {...c2props}/>
</div>
)

React rerendering children in functional component despite using memo and not having any prop changed

I have an Icon component that draw an icon and which is blinking because the parent is making it rerender for nothing. I don't understand why this is happening and how to prevent this.
Here is a snack that shows the issue.
We emulate the parent changes with a setInterval.
We emulate the icon rerendering by logging 'rerender' in the console.
Here is the code:
import * as React from 'react';
import { Text, View, StyleSheet } from 'react-native';
// or any pure javascript modules available in npm
let interval = null
const Child = ({name}) => {
//Why would this child still rerender, and how to prevent it?
console.log('rerender')
return <Text>{name}</Text>
}
const ChildContainer = ({name}) => {
const Memo = React.memo(Child, () => true)
return <Memo name={name}/>
}
export default function App() {
const [state, setState] = React.useState(0)
const name = 'constant'
// Change the state every second
React.useEffect(() => {
interval = setInterval(() => setState(s => s+1), 1000)
return () => clearInterval(interval)
}, [])
return (
<View>
<ChildContainer name={name} />
</View>
);
}
If you could explain me why this is happening and what is the proper way to fix it, that would be awesome!
If you move const Memo = React.memo(Child, () => true) outside the ChildContainer your code will work as expected.
While ChildContainer is not a memoized component, it will be re-rendered and create a memoized Child component on every parent re-render.
By moving the memoization outside of the ChildContainer, you safely memoize your component Child once, and no matter how many times ChildContainer will be called, Child will only run one time.
Here is a working demo. I also added a log on the App to track every re-render, and one log to the ChildComponent so you can see that this function is called on every re-render without actually touching Child anymore.
You could also wrap Child with React.memo directly:
import * as React from "react";
import { Text, View, StyleSheet } from "react-native";
// or any pure javascript modules available in npm
let interval = null;
const Child = React.memo(({ name }) => {
//Why would this child still rerender, and how to prevent it?
console.log("memoized component rerender");
return <Text>{name}</Text>;
}, () => true);
const ChildContainer = ({ name }) => {
console.log("ChildContainer component rerender");
return <Child name={name} />;
};
export default function App() {
const [state, setState] = React.useState(0);
const name = "constant";
// Change the state every second
React.useEffect(() => {
interval = setInterval(() => setState(s => s + 1), 1000);
return () => clearInterval(interval);
}, []);
console.log("App rerender");
return (
<View>
<ChildContainer name={name} />
</View>
);
}

Is it possible to use the same <Context.Provider> on multiple components?

I know that I can wrap HOC with my <Context.Provider> and consume it in all child components.
I would like to consume context in two separate components but they are nested somewhere deeply and closest parent of them is somewhere in the app root. I don't want to provide context to (almost) all components, so I was wondering is it possible to wrap only those two components?
I tried to do it but only first component gets context.
The App structure looks like this:
<App>
<A1>
<A2>
<MyContext.Provider>
<Consumer1/>
</MyContext.Provider>
</A2>
</A1>
<B1>
<B2>
<MyContext.Provider>
<Consumer2/>
</MyContext.Provider>
</B2>
</B1>
</App>
EDIT: I was wrong thinking that wrapping root component will make re-render all child components on context change. Only consumers will rerender so it's perfectly fine to wrap root component.
If you want to have a single value which is shared between multiple parts of the application, then in some form you will need to move that value up to the common ancestor component of the ones that need to consume the value. As you mentioned in the comments, your issue is one of performance and trying not to rerender everything. Having two providers doesn't really help with this, because there will still need to be some component making sure both providers are providing the same value. So that component will end up needing to be a common ancestor of the two providers.
Instead, you can use shouldComponentUpdate (for class components) or React.memo (for functional components) to stop the rerendering process from working its way down the component tree. Deep descendants which are using Context.Consumer will still rerender, and so you can skip over the middle parts of your tree. Here's an example (note the use of React.memo on the intermediate component):
const Context = React.createContext(undefined);
const useCountRenders = (name) => {
const count = React.useRef(0);
React.useEffect(() => {
count.current++;
console.log(name, count.current);
});
}
const App = () => {
const [val, setVal] = React.useState(1);
useCountRenders('App');
React.useEffect(() => {
setTimeout(() => {
console.log('updating app');
setVal(val => val + 1)
}, 1000);
}, [])
return (
<Context.Provider value={val}>
<IntermediateComponent />
</Context.Provider>
);
}
const IntermediateComponent = React.memo((props) => {
useCountRenders('intermediate');
return (
<div>
<Consumer name="first consumer"/>
<UnrelatedComponent/>
<Consumer name="second consumer"/>
</div>
);
})
const Consumer = (props) => {
useCountRenders(props.name);
return (
<Context.Consumer>
{val => {
console.log('running consumer child', props.name);
return <div>consuming {val}</div>
}}
</Context.Consumer>
)
}
const UnrelatedComponent = (props) => {
useCountRenders('unrelated');
return props.children || null;
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.development.js"></script>
<div id="root"></div>
When you run the above code, check the logs to see which components rerender. On the first pass, everything renders, but then after a second when the app state changes, only app rerenders. IntermediateComponent, UnrelatedComponent, and even Consumer don't rerender. The function inside the Context.Consumer does rerun, and any thing returned by that function (in this case just a div) will rerender.
As requested by the OP this solution uses mostly hooks but useReducer cannot achieve state sharing under separate providers (as far as I've tried).
It does not require one Provider to be at the root of the app and different reducer can be used for each Provider.
It uses a static state manager but that's an implementation detail, to share the state between several component under different context proviver, one will need something like a shared reference to the state, a way to change it and a way to notify of these changes.
When using the snippet the first button shows the shared state and increment foo when clicked, the second button shows the same shared state and increments bar when clicked:
// The context
const MyContext = React.createContext();
// the shared static state
class ProviderState {
static items = [];
static register(item) {
ProviderState.items.push(item);
}
static unregister(item) {
const idx = ProviderState.items.indexOf(item);
if (idx !== -1) {
ProviderState.items.splice(idx, 1);
}
}
static notify(newState) {
ProviderState.state = newState;
ProviderState.items.forEach(item => item.setState(newState));
}
static state = { foo: 0, bar: 0 };
}
// the state provider which registers to (listens to) the shared state
const Provider = ({ reducer, children }) => {
const [state, setState] = React.useState(ProviderState.state);
React.useEffect(
() => {
const entry = { reducer, setState };
ProviderState.register(entry);
return () => {
ProviderState.unregister(entry);
};
},
[]
);
return (
<MyContext.Provider
value={{
state,
dispatch: action => {
const newState = reducer(ProviderState.state, action);
if (newState !== ProviderState.state) {
ProviderState.notify(newState);
}
}
}}
>
{children}
</MyContext.Provider>
);
}
// several consumers
const Consumer1 = () => {
const { state, dispatch } = React.useContext(MyContext);
// console.log('render1');
return <button onClick={() => dispatch({ type: 'inc_foo' })}>foo {state.foo} bar {state.bar}!</button>;
};
const Consumer2 = () => {
const { state, dispatch } = React.useContext(MyContext);
// console.log('render2');
return <button onClick={() => dispatch({ type: 'inc_bar' })}>bar {state.bar} foo {state.foo}!</button>;
};
const reducer = (state, action) => {
console.log('reducing action:', action);
switch(action.type) {
case 'inc_foo':
return {
...state,
foo: state.foo + 1,
};
case 'inc_bar':
return {
...state,
bar: state.bar + 1,
};
default:
return state;
}
}
// here the providers are used on the same level but any depth would work
class App extends React.Component {
render() {
console.log('render app');
return (
<div>
<Provider reducer={reducer}>
<Consumer1 />
</Provider>
<Provider reducer={reducer}>
<Consumer2 />
</Provider>
<h2>I&apos;m not rerendering my children</h2>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
In the end we have re created something a little like redux with a static state shared by several Provider/Consumer ensembles.
It will work the same no matter where and how deep the providers and consumers are without relying on long nested update chains.
Note that as the OP requested, here there is no need for a provider at the root of the app and therefore the context is not provided to every component, just the subset you choose.

Resources