I want to trigger a function whenever a new ImmutableJS object is created. I want to use an ImmutableJS object as my React application state and I want to reduce boilerplate code.
Here's part of what I have now:
function addTodo(text) {
let state = store.getState(); // Gets ImmutableJS object.
let todos = state.get('todos');
let newState = state.set('todos', todos.push('text'));
store.update(newState); // Triggers rerender.
}
Here's what I want it to be like:
function addTodo(text) {
let state = store.getState(); // Gets ImmutableJS object.
state.get('todos').push(text); // Store listens to change and replaces the current state with the newly created state. Store automatically triggers rerender.
}
Is this possible?
I tried wrapping the entire addTodo in withMutations, but withMutations allows only shallow mutations.
Agree with Lukas' comment. This is really what Redux is for. You mutate the state of your stores and it will force a re-render of your component tree(s) with the new values.
I'm not aware of any sort of observable functions in Immutable.js itself, there might be a third part library that has that functionality though.
Related
assuming I have a React app that has a internal state where I store an object like
const [car, setCar] = useState(new Car());
Assuming my class Car looks like the following:
class Car {
constructor(brand) {
this.carname = brand;
}
present() {
return "I have a " + this.carname;
}
}
When I run and debug the app, I can save a Car object into the state and also retrieve it and call present().
Now, when I make changes to the present() function, e.g.
present() {
return "I have a " + this.carname + " and it is shiny";
}
then due to Fast Refresh my App gets refreshed. But unfortunately, because the object is already stored in state, it will not receive the update of the function implementation.
Is there a way that I can change the code so that with a Fast Refresh the function implementation will be updated also for the object in React state?
I tried updating the method via prototype but it did also not work.
This is actually a bit tricky since what you are trying to achieve goes somewhat against react's design around immutable. Directly changing a classes method and assigning it to an existing instance can easily be done using Object.setPrototypeOf(car, Car.prototype).
This replaces the original car instance's prototype and changes the method implementation. As you've said this does not work because the mutating an object does not trigger a rerender.
useState compares objects using Object.is() which won't trigger a rerender in this case since both objects have the same reference.
For the sake of Object.is(), you need to pass in a new object with the new prototype and exact properties of the existing instance to setCar(). This can't be done using the basic spread operator {..car} or Object.assign({}, car) since these methods only copy an object's own enumerable properties.
You can instead use the following to create a new object with the exact same properties the new prototype setCar(Object.assign(Object.create(Car.prototype), car))
This can be placed inside useEffect which will run everytime the original Car class is changed
const [car, setCar] = useState(new Car());
useEffect(() => {
setCar(Object.assign(Object.create(Car.prototype), car))
}, [])
By design, Fast Refresh will preserve state whenever it can. useEffect however will always update during a Fast Refresh regardless of what is in the dependancy array. As long as you provide an empty dependency array, you can use setState inside of useEffect to change state between fast refreshes.
const [car, setCar] = useState(new Car());
useEffect(() => {
setCar(new Car());
},[])
Keep in mind though, useEffect will only run after the first render so you need to keep the default state new Car() in the useState hook argument to prevent any undefined errors.
I know mutating state can work against PureComponent (or similar)
Is there other reason not to mutate state?
I wonder if the 3rd way is ok to do?
// The Right Way:
// copy the existing items and add a new one
addItemImmutably = () => {
this.setState({
items: [...this.state.items, this.makeItem()]
});
};
// The Wrong Way:
// mutate items and set it back
addItemMutably = () => {
this.state.items.push(this.makeItem());
this.setState({ items: this.state.items });
};
// is this ok? (mutate but set state with new copy)
addItem3rdWay = () => {
this.state.items.push(this.makeItem());
this.setState({items: [...this.state.items]});
}
Here is an example: You have fired an async method that sends a request with your current state data. In meantime you have executed a function that mutates the state. This will cause the async function to send the mutated state despite the fact it intended to send the state before mutation.
You can think state as your database.
You don't directly mutate your database, you modify your database by API.
setState is the API.
Of course, you can directly mutate your database, but other components will have a hard time retrieving those data, because those are inconsistent right now, because somebody doesn't use the API that framework provides for you.
If you really like the way mutating state, you can use Vue, Vue is designed like that.
Here's a sandbox reproducing my issue:
https://codesandbox.io/s/ymmyr3o70x?expanddevtools=1&fontsize=14&hidenavigation=1
For reasons I can't understand, when I add product via the form, the product list is only updated once, then never again.
I'm using a custom hook (useObservable) combined with RxJS to manage state. If you check the console logs, the ReplaySubject does emit the expected values. But the useObservable hook is not triggering an update of the DOM.
What am I missing?
The issue is that your addProduct function mutates the old state instead of creating a new one. Yes, you have the observable emit the state again, but since its the same object as before, calling setValue has no effect and so react does not rerender.
The solution to this is to make the state immutable. For example:
import { ReplaySubject } from "rxjs";
let products = {};
export const products$ = new ReplaySubject(1);
export const addProduct = product => {
products = {...products, [product]: product};
products$.next(products);
};
I'm new to ReDux, and working on a project with multiple reducers.
In the function called/referenced by the subscribe, it is called anytime any of the data in the store is changed. For example, I have a to do list and an object which stores info on the animation, basically a list of properties. If I update a to do item I don't want to re-render the animation object. If I update an animation property, I don't want to re-render the to do list.
How can I tell which data object has been changed?
Here's the code I have that subscribes to data changes in ReDux:
$(function() {
var $todoPane = $('#todopane');
var $animFormPane = $('#animation_form');
// Render objects onload.
// renderTodos($todoPane, APP.store.getState().todos.allTodos);
// renderAniPropForm($animFormPane, APP.store.getState().animation);
APP.store.subscribe(function() {
// Did some part of the ui change? (sliders that alter the height or width of panes on the page)
// Did todos change?
renderTodos($todoPane, APP.store.getState().todos.allTodos);
// Did animation values change?
renderAniPropForm($animFormPane, APP.store.getState().animation);
// Did an object change?
// Did an action change?
});
});
What do I put where the comments like with // Did ?? values change?
The standard approach used for detecting changes in React and Redux apps is strict equality checks. If two variables are not === equal, then they are assumed to be different. This requires that you update your data immutably.
So, as a quick example:
let lastTodosState, lastAnimationState;
APP.store.subscribe(function() {
// Did some part of the ui change? (sliders that alter the height or width of panes on the page)
const newState = store.getState();
if(newState.todos.allTodos !== lastTodosState.allTodos) {
renderTodos($todoPane, newState.todos.allTodos);
}
if(newState.animation !== lastAnimationState) {
renderAniPropForm($animFormPane, newState.animation);
}
lastTodosState = newState.todos;
lastAnimationState = newState.animation;
// Did an object change?
// Did an action change?
});
If you are using React for your UI layer, the React-Redux connect function generates components that do this for you automatically.
in Redux reducing operation will update the single and only state tree available which will force an entire render of all the components under a <Provider> enter reselect https://github.com/reactjs/reselect top the rescue. by suing memoized selectors you can remember properties and prevent your component from re render as long as the watched state by the selectors does not change
If state is immutable wouldn't the whole state need to be replaced on any change? Otherwise, you're mutating the state. Are top-level keys held as separate immutable objects?
By definition wouldn't any change need to replace the whole thing? How is this handled by Redux? Wouldn't replacing the full state break PureComponents?
Thanks for any clarification.
Let's break it down step by step:
There is an initial redux store of { color: 'red' }
The user clicks a button
An action is generated, say CHANGE_COLOR
Redux calls your reducer with the store (from step 1) and the action. The reducer returns a new store, say { color: 'blue' }
Then, let's say you had a class Square extends PureComponent with mapStateToProps as (store) => { color: store.color }
What happens now is that when the store changes, redux-react runs the mapStateToProps every time. It then generates new props. These props get sent to react.
If you're with me so far the TL;DR of how redux and react work together is that whenever the redux store changes, every single mapStateToProps runs and this generates new props for your react components.
From this point on, it's standard React. When the props of a React component change, it runs componentWillReceiveProps followed by shouldComponentUpdate.
For a PureComponent, basically that means shouldComponentUpdate just does a shallow equality check on your new props, and goes from there.
Okay, with this base understanding out of the way, nested objects behave similarly.
Instead of a store of {color: 'red'} what if we had a store like this:
{
allMyData: {
color: 'red'
key1: 'someData',
key2: 'lotsMoreData',
bigData: {
nestedKey: 'EvenMoreNestedData',
}
}
Then, your reducer might look something like this:
const reducer = (store, action) => {
if (action == CHANGE_COLOR) {
let newStore = Object.assign({}, store);
newStore.allMyData = Object.assign({}, newStore.allMyData, { color: 'blue' });
}
}
Note that there are better tools to do deep immutable merges. One main point I want to show here is that although the newStore is a different object, both store and newStore BOTH point to the SAME bigData key and the other values that are unchanged. The immutability part allows for this kind of thing to work.
After this, the steps are basically the same. After every store change, mapStateToProps runs and generates a new props object. Redux-react then passes that new prop object to React and in this case, a PureComponent wouldn't render if the prop values have the same identity (===)
Note that redux-react has some perf optimizations where it will avoid calling into react with new props but for the purposes of this discussion I'm omitting that step.
Hope this helps clear up your questions.
TL;DR: Every redux action returns a new store. Every time a new store is generated, mapStateToProps generates new props for every connected react component. If the react component is a pureComponent, it won't re-render because the props haven't changed.
The crux for your question is that because your store is immutable, you can re-use the same inner objects across different stores, and even pass those into your react components via mapStateToProps.
Redux never says that the state is immutable. It says the reducers which constitute the store or state of the application should be immutable and should be pure functions.
Yes the top level keys are seperately considered to be immutable.
So on any change or in Redux terms any action is done we trigger a reducer on each action which causes the state change.