The general behavior that I want is on an event, update a property in the Redux state tree, and then access the updated property, right after the state tree has been updated.
But below, what happens is the synchronousReducer function makes it's call, and then the relevantProperty is undefined. Everything is re-rendered, and only then is relevantProperty updated.
How should I accomplish this goal the Redux way? No anti-patterns please.
const { relevantProperty } = props;
...
<button onClick={() =>
synchronousReducer(); // This updates relevantProperty in the Redux state tree.
console.log(relevantProperty); // undefined
loadEvent(relevantProperty);
} />
Related
Similar to:
How to use `setState` callback on react hooks but I am trying to do this with functional component specifically and I am not trying to replicate the exact same scenario.
React hooks: accessing up-to-date state from within a callback but I am not passing the state from the callback
So say I have a notify() method that I want to be fired AFTER the states have changed, not during state change. Also after I click.
something like
const handleClick = useCallback(()=>{
const [state,setState] = useState("bar");
...
setState("foo", ()=> {
... at this point `state` should be "foo" ...;
notify("yo we're set. but I am not passing the current state to you");
})
});
...
a component that is child of the context
const { state } = useSomeContext();
subscribe(()=>{
console.log("I want ", state, " to be the updated value, but I get the existing value);
});
useStateRef lets the callback know but the ones listening when they query the value that is in the state may not get the updated value.
useStateCallback does not solve the problem for me either because it's the callback that has the value.
The workaround I sort of ended up with (still testing it) is to utilize a useRef to the value somewhat like useStateRef and do stateRef.current instead of just state
When you change the state via the setter, it will cause the current component to re-render. If the component you want to 'notify' is within the current component and has a dependency on the state value that changed e.g.:
return (
<>
<SomeOtherComponent something={state}/>
</>
)
... then that component will re-render as well.
If the component you want to notify isn't in the render path, you can make the state a dependency of a useEffect so your useEffect will run whenever the state change (and do your notification there). You can have multiple useEffects with with dependencies on different state. Example:
useEffect(()=> {
console.log('hello, state is',state)
}, [state])
And you can look into context and other mechanisms to communicate state. You should read this article. I think it's very inline with what you'd like to do.
Certainly don't do anything in your useState setter other than the minimal needed to return the new state.
So to do a very simplified use case, I have three components:
const ParentComponent = (props) => {
return(
<ChildComponentA ...variousPropsHere />
<ChildComponentB ...variousPropsHere />
}
ChildComponentA is an overlay that shows when a user clicks something, we'll call the value "value".
ChildComponentB doesn't need "value" at all. And in fact I want to prevent ChildComponentB from re-rendering based on the data change, because rendering ChildComponentB is extremely expensive, and it has no need of updating.
But ChildComponentA does need the value and needs to re-render based on "value" updating.
I have considered useContext, refs, useReducer, etc, but none of them are working quite how I want - useContext seems like it will update all components that subscribe to it - plus the state would still be managed in the parent, necessitating a re-render there too. Refs won't re-render ChildComponentA. useReducer also seems like it will cause parent re-render (and thus child re-renders). Memoization helps with the speed of ChildComponentB re-rendering, but it's still pretty slow.
At this point I'm leaning towards just using my global redux store, storing the value there, and clearing it out on component dismount. That would allow me to update the value from ChildComponentB, without subscribing ChildComponentB (or ParentComponent) to the value itself, preventing re-render in these very expensive components.
Is there a more elegant way to do this than the way I am describing?
I just really cannot have ChildComponentB re-render, but must have ChildComponentA re-render based on a value change.
Instead of keeping value in Context or State of parent component, keep there callback from ChildComponentA and call it from ChildComponentB.
This way only sibling component will be rerendered.
Example:
const ChildComponentA = () => {
const context = useContext(SomeContextToStoreCallback)
useEffect(() => {
context.callback = (value) => {
// Do something expensive
}
return () => {
context.callback = undefined;
}
}, [])
// rest of the code....
}
const ChildComponentB = () => {
const context = useContext(SomeContextToStoreCallback)
const handleSomeEvent = (value) => {
context.callback?.(value)
}
// rest of code.....
}
This is only a pseudo code, but you've got an idea.
Something similar is used in large libraries like mui-x
I have a class component which renders a child component which cannot be modified (comes from a library).
This component has an internal state in which there is a data I want to retrieve and do something with it as soon at it is changed.
I tried using a callback on ref like this:
onRefChange = el => {
console.log('el state', el.state);
// prints the initial state of the child component, with the value still to be changed (it changes a little after initialization)
}
<Carousel
ref={(el) => {
this.onRefChange(el);
return this.Carousel = el;
}}
//other stuff
/>
Of course I can access the state by reading this.Carousel.state later but I'm trying to do an operation immediately after this data changes, with a callback.
Also apart from changing the behaviour of the child component, the parent component should remain a class componente, so no hooks like useEffect() can be used.
Is it a dead-end or is there a way to achieve this?
This might be a really stupid question but I am writing my first project in React and am struggling to understand the purpose of setState hooks.
As far as I understand, the setState hook is used to set current values used in a component that is scoped to that component only, and does not persist if a page is reloaded for example, the value is simply held in memory until it is destroyed.
My question is, what is the difference between using setState() to store values and just simply declaring a let variable and updating it the regular way? Both methods just seem to be holding a non-persisting value scoped to that component. What is the difference?
changes in the state automatically cause your app to re-render (in most cases), so typically you store data in a state that is being displayed and possibly changed throughout the app (a menu whose options can change based on previous selections, for example).
TL;DR, even though the answer's not very long:
setState or useState is the key for React to subscribe to your component's state and update your components when necessary. Using let variables for storing app state means React will never get to know about state change and won't rerender and update your components.
A short overview of React
The core principle of React is that your components, and consequentially your UI, are a function of your app's state. When your app's state changes, components "react" to this state change and get updated. Here's a simple example:
const CounterButton = () => {
// Create a state variable for counting number of clicks
const [count, setCount] = React.useState(0);
// Decide what the component looks like, as a function of this state
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
};
ReactDOM.render(<CounterButton />, document.querySelector('#root'));
<div id="root"></div>
<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>
This is a component that just creates a button that shows how many times it has been clicked. Now, the component needs to store information about how many times it has been clicked - this means that the clicks count is a part of this component's "state". That's what we get from React.useState(0) - a state variable whose initial value is 0, and a function that allows us to change the value. Whenever you call setCount with some value, React gets to know that the CounterButton component's state has changed and thus the CounterButton component needs a rerender.
So in other words, React allows you to neatly and concisely define what internal state a component requires and what the component looks like as a function of this internal state (and external props). React does rest of the work - it manages the app's state, and whenever a piece of state changes anywhere in the app, React updates the components that depend on that. In other words, components "react" to data change.
To your question
If you use a simple let variable instead of React.useState in the above example, React will no longer get to know if the count variable has changed. useState is the key for React to "subscribe" to your component's state.
const CounterButton = () => {
// React will never get to know about this variable
let count = 0;
return (
<button onClick={() => count++}>
Count: {count}
</button>
);
};
In fact, for a functional component, let variables won't work in any case because while rendering a functional component, React internally runs the function. That would mean your let variable would be reset to its default value. The above reason is more relevant to class components. Using let variables to store component state is like hiding things from React - and that's not good because then there's no one else to rerender your component when component state changes :)
This part of the React docs is a bit relevant - it does not go into any details, though.
React re-renders the new / updated state on an event occurance storing value into a variable wont trigger re-render and data is passed on form parent component to child component through props and a change in state can be reflected among all the parts.
For example we need to print 100 elements on a page if an element is modified or updated in any way this triggers re-render but using var if the variable is modified or updated this won't cause re-render where in data wont be updated.
I'm new to React and am trying to update the state of a parent component from the child everytime an onChange action happens. The onchange action comes from an input box that when letters are typed it updates the state of searchInputVal with the value of what has been typed. I have a parent <App/> component with the following properties and states here:
updateSampleFilteredState(filteredSamples) {
this.setState({
samples: filteredSamples
});
},
getInitialState () {
return {
samples:allSamples,
searchInputVal:""
}}
I pass the properties and states down to a child component here:
updateNewSampleState(filteredSamples){
return (
this.props.updateSampleFilteredState(filteredSamples)
)
}
render() {
const filteredSamples = this.props.samples.filter(sample => {
return sample.sampleFamily.toLowerCase().indexOf(this.props.searchInputVal.toLowerCase()) !== -1;
});
this.updateNewSampleState(filteredSamples);
return <div className="samples-container-inner-styling">
{
filteredSamples.map((sample) => {
return (...
Before I added the line this.updateNewSampleState(filteredSamples); the child component would render out the filtering just fine but obviously not update the state of sample with the new filtered state. When I the line this.updateNewSampleState(filteredSamples); to execute the function in the component to set the new state I get a list of re-occuring errors that eventually make my app crash. The errors say something about an anti pattern. I'm not sure how else to update the state?
You should't be updating the state from the render function, and you are facing the reason why that's a bad way to do things. Every time you call the setState the component re-renders, so if you call it inside the render function it will be called again and so on... You should ask yourself why are you calling that function there. I guess you could just do it in the onChange function you are using for the input.
As already mentioned by #César, setting the state in the renderer doesn't make sense, since setting the state triggers a rerender of the component, so you basically get something like an infinite render loop.
Given that you are computing filteredSamples only from the props, you could compute that state in the constructor:
The constructor is the right place to initialize state.
However, note the following when deriving state from props in the constructor:
It's okay to initialize state based on props if you know what you're doing. [...]
Beware of this pattern, as it effectively "forks" the props and can lead to bugs. Instead of syncing props to state, you often want to lift the state up.
If you "fork" props by using them for state, you might also want to implement componentWillReceiveProps(nextProps) to keep the state up-to-date with them. But lifting state up is often easier and less bug-prone.