In my create-react-app app, I created an object using the useState hook in App.js that I pass down as an argument to 5 different instances of the same component. This object contains 5 different arrays under simple number property names (so they're easy to iterate over with a for loop).
const [ listObj, setListObj ] = useState({ 0: [], 1: [], 2: [], 3: [], 4: [] });
Each component maintains and modifys one and only one of those arrays (corresponding to it's number, so component 0 only modifies component 0, 1 only modifies 1, etc.), and I use functions to iterate over listObj and the arrays inside so I can compare their contents.
These components each have a unique componentNumber (0-4) passed to them through App.js, and a useEffect hook that looks something like this in each one for every time they want to update their specific array:
useEffect(() => {
let newArray = functionToGenerateSortedArrays();
let newListObj = { ...listObj, [ componentNumber ]: newArray };
setListObj( newListObj );
}, [ (all my state objects that trigger useEffect) ])
This works great for when I'm only updating something pertaining to one component at a time. The problem is that there is a functionality in the app where all the components need to update at the same time. It appears that each one will grab an "old" copy of the listObj, use that to create a newListObj with the updated information, then call setListObj with the newListObj, thereby overwriting the changes that the other components tried to make. The only listObj array that updates properly is the last one in the row.
I thought myself terribly clever when I tried to implement this hacky solution:
useEffect(() => {
let newArray = functionToGenerateSortedArrays();
setTimeout(() => {
let newListObj = { ...listObj, [ componentNumber ]: newArray };
setListObj( newListObj );
}, componentNumber * 100 );
}, [ (all my state objects that trigger useEffect) ])
This doesn't work, though. It actually switches the array in listObj back to the value I want instantly, but it reverts to the wrong one after the timeout triggers. I have no idea why but would love to understand it if someone has any insights. I thought perhaps it could be related to how React "batches" state updates, but couldn't find any information on how to keep React from doing that so I could test my hypothesis.
Do I have any options for changing the array values in listObj in a "cascading" manner so they don't interfere with each other? Is there perhaps another way of saving and updating state that won't create this problem? Thanks very much for your time.
This is where to us the state update callback form of the useState hook's setter:
useEffect(() => {
let newArray = functionToGenerateSortedArrays();
setListObj(currentListObj => {
// ^^^^^^^^^^^^^^
let newListObj = { ...currentListObj, [ componentNumber ]: newArray };
return newListObj;
});
}, [ (all my state objects that trigger useEffect) ])
That way, even when all the update functions are batched together, they will receive the current value instead of using the outdated one that the effect function had closed over.
Related
I want to push an 2d array into the end of a state. But somehow, when i log the state every time it changes with a useEffect, i replaces every existing array with the new one.
The variable "position" is my 2d array, also a state. Might this cause any reference problems?
I have no idea and im starting to lose my mind about this...
const [positionList, setPositionList] = useState([]);
useEffect(() => {
const updatePosition = [...positionList, position]
setPositionList(updatePosition);
}, [playerTurn])
expected output (or what i want)
positionList = [position1, position2, position3, //and so on]
but what i get is this (lets say i try to push the array three times)
positionList = [position3, position3, position3]
EDIT:
So, after some good ideas from the community, i found out that i had to copy my array i want to push (the "position") before i push it into the state. So my working code looks now like this:
const [positionList, setPositionList] = useState([])
useEffect(() => {
let positionCopy = [];
for (let i = 0; i < position.length; i++) {
positionCopy[i] = position[i].slice();
}
setPositionList([...positionList, positionCopy]);
}
You didn't add positionList as a dependency of your useEffect callback, so the positionList captured by the callback doesn't necessarily reflect the latest value.
useEffect(() => {
// return early if the update already happened to prevent recursion
const updatePosition = [...positionList, position]
setPositionList(updatePosition);
}, [playerTurn, positionList])
TBH, using useEffect to update a state hook is difficult to maintain and prone to accidental recursive behaviour.
You might get better mileage by using a state reducer with the useReducer hook. For anything beyond trivial state management, the useReducer hook is worth getting to grips with.
try:
const [positionList, setPositionList] = useState([]);
useEffect(() => {
setPositionList(state=> [...state, position]);
}, [playerTurn])
I want to create react table component which values are derived from single array object. Is it possible to control the component from view side? My goal is that every user using this component in their web browsers share the same data via singleton view object.
Program modeling is like below.
Database - there are single database in server which contain extinct and independent values.
DataView - there are singleton View class which reflects Database's table and additional dependent data like (sum, average)
Table - I'll build react component which looks like table. And it will show View's data with supporting sorting, filtering, editing and deleting row(s) feature (and more). Also it dose not have actual data, only have reference of data from View(Via shallow copy -- This is my question, is it possible?)
My intentions are,
- When user changes value from table, it is queried to DB by View, and if succeed, View will refer updated data and change it's value to new value and notify to Table to redraw it's contents. -- I mean redraw, not updating value and redraw.
- When values in View are changed with DB interaction by user request, there are no need to update component's value cause the components actually dose not have values, only have references to values (Like C's pointer). So only View should do is just say to Component to redraw it's contents.
I heard that React's component prop should be immutable. (Otherwise, state is mutable) My goal is storing references to component's real value to it's props so that there are no additional operation for reflecting View's data into Table.
It is concept problems, and I wonder if it is possible. Since javascript dose not support pointer officially(Am I right?), I'm not sure if it is possible.
View class is like below,
const db_pool = require('instantiated-singleton-db-pool-interface')
class DataView {
constructor() {
this.sessions = ['user1', 'user2'] // Managing current user who see the table
this.data = [ // This is View's data
{id:1, name:'James', phone:'12345678', bank:2000, cash:300, total:2300,..},
{id:2, name:'Michael', phone:'56785678', bank:2500, cash:250, total:2300,..},
{id:3, name:'Tyson', phone:'23455432', bank:2000, cash:50, total:2300,..}
] // Note that 'total' is not in db, it is calculated --`dependent data`
}
notifySessionToUpdate(ids) {
// ids : list of data id need to be updated
this.sessions.forEach((session) => {
session.onNotifiedUpdateRow(ids) // Call each sessions's
})
}
requestUpdateRow(row, changed_value) {
// I didn't write async, exception related code in this function for simple to see.
update_result = db_pool.update('UPDATE myTable set bank=2500 where id=1')
if (update_result === 'fail') return; // Do Nothing
select_result = db_pool.select('SELECT * from myTable where id=1') // Retrieve updated single data which object scheme is identical with this.data's data
for (k in Object.keys(select_result)) {.ASSIGN_TO_row_IF_VALUE_ARE_DIFFERENT.} // I'm not sure if it is possible in shallow copy way either.
calc.reCalculateRow(row) // Return nothing just recalculate dependant value in this.data which is updated right above.
// Notify to session
this.notifySessionToUpdate([1]) // Each component will update table if user are singing id=1's data if not seeing, it will not. [1] means id:1 data.
return // Success
}
... // other View features
}
Regarding session part, I'm checking how to implement sessionizing(?) the each user and it's component who is communicating with server. So I cannot provide further codes about that. Sorry. I'm considering implementing another shallow copied UserView between React Component Table and DataView(And also I think it helps to do something with user contents infos like sorting preference and etc...)
Regarding DB code, it is class which nest it's pool and query interface.
My problem is that I'm not familiar with javascript. So I'm not sure shallow copy is actually implementable in all cases which I confront with.
I need to think about,
1. Dose javascript fully support shallowcopy in consistent way? I mean like pointer, guarantee check value is reference or not.
2. Dose react's component can be used like this way? Whether using props or state Can this be fullfilled?
Actually, I strongly it is not possible to do that. But I want to check your opinions. Seems it is so C language-like way of thinking.
Redraw mean re-render. You can expose setState() or dispatch() functions from Table component and call them on View level using refs:
function View() {
const ref = useRef();
const onDbResponse = data => ref.current.update(data);
return (
<Table ref={ ref } />
);
}
const Table = React.forwardRef((props, ref) => {
const [ data, setData ] = useState([]);
useImperativeHandler(ref, {
update: setData
});
...
});
Anyway i don't think it's a good practice to update like that. Why can't you just put your data in some global context and use there?
const Context = React.createContext({ value: null, query: () => {} });
const Provider = ({ children }) => {
const [ value, setValue ] = useState();
const query = useCallback(async (request) => {
setValue(await DB.request(request));
}, [ DB ]);
const context = { value, query };
return <Context.Provider value={ context }>{ children }</Context.Provider>;
}
const useDB = () => useContext(Context);
const View = () => {
const { request } = useDB();
request(...);
}
const Table = () => {
const { value } = useDB();
...
}
Let's assume we have some array of objects, and these objects never change. For example, that may be search results, received from google maps places api - every result is rather complex object with id, title, address, coordinates, photos and a bunch of other properties and methods.
We want to use vue/vuex to show search results on the map. If some new results are pushed to the store, we want to draw their markers on the map. If some result is deleted, we want to remove its marker. But internally every result never changes.
Is there any way to tell vue to track the array (push, splice, etc), but not to go deeper and do not track any of its element's properties?
For now I can imagine only some ugly data split - keep the array of ids in vue and have separate cache-by-id outside of the store. I'm looking for a more elegant solution (like knockout.js observableArray).
You can use Object.freeze() on those objects. This comes with a (really tiny!) performance hit, but it should be negligible if you don't add hundreds or thousands of objects at once.
edit: Alternatively, you could freeze the array (much better performance) which will make Vue skip "reactifying" its contents.
And when you need to add objects to that array, build a new one to replace the old one with:
state.searchResults = Object.freeze(state.searchResults.concat([item]))
That would be quite cheap even for bigger arrays.
At the second glance data split seems not so ugly solution for this task. All that we need is using getters instead of the raw vuex state. We suppose that incoming results is an array with any objects that have unique id field. Then the solution could look like:
const state = {
ids: []
}
let resultsCache = {};
const getters = {
results: function(state) {
return _.map(state.ids,id => resultsCache[id]);
}
}
const mutations = {
replaceResults: function(state,results) {
const ids = [];
const cache = {};
(results||[]).forEach((r) => {
if (!cache[r.id]) {
cache[r.id] = r;
ids.push(r.id);
}
});
state.ids = ids;
resultsCache = cache;
},
appendResults: function(state,results) {
(results||[]).forEach((r) => {
if (!resultsCache[r.id]) {
resultsCache[r.id] = r;
state.results.push(r.id);
}
});
}
}
export default {
getters,
mutations,
namespaced: true
}
I created a fork out of vue called vue-for-babylonians to restrict reactivity and even permit some object properties to be reactive. Check it out here.
With it, you can tell Vue to not make any objects which are stored in vue or vuex from being reactive. You can also tell Vue to make certain subset of object properties reactive. You’ll find performance improves substantially and you enjoy the convenience of storing and passing large objects as you would normally in vue/vuex.
You can use shallowRef to achieve this.
First import it:
import {shallowRef} from 'vue';
In your mutations you can have a mutation like this:
mutations: {
setMyObject(state, payload) {
state.myObject = shallowRef(payload.value);
},
}
This will track replacing the object, but not changes to the objects properties.
For completeness here is the documentation to shallowRef:
https://v3.vuejs.org/api/refs-api.html#shallowref
My code works, but I have a best practice question: I have an array of objects in the state, and a user interaction will change a value of one object at a time. As far as I know, I'm not supposed to change the state directly, i should always use setState instead. If I want to avoid that with any price, I will deep clone the array by iteration, and change the clone. Then set the state to the clone. In my opinion avoiding to change the state that I will change later anyway is just decreasing my performance.
Detailed version:
this.state.data is an array of objects. It represents a list of topics in a forum, and a Favorite button will toggle, calling clickCollect().
Since I have an array in the state, when I change the is_collected property of one item, I need to create a copy of the array to work with, and after changing to the new value, I can set it to the state.
var data = this.state.data.slice(0);
data[index].is_collected = !data[index].is_collected;
this.setState({data: data});
var data = this.state.data : This would copy the pointer to the array and push(), shift(), etc would alter the state directly. Both data and this.state.data will be affected.
var data = this.state.data.slice(0) : This makes a shallow clone, push and shift doesn't change the state but in my clone I still have pointers to the elements of the state's array. So if I change data[0].is_collected, this.state.data[0].is_collected gets changed as well. This happens before I call setState().
Normally I should do:
var data = [];
for (var i in this.state.data) {
data.push(this.state.data[i]);
}
Then I change the value at index, setting it to true when it's false or false when it's true:
data[index].is_collected = !data[index].is_collected;
And change state:
this.setState({data: data});
Consider my array is relatively big or enormously big, I guess this iteration will reduce the performance of my APP. I would pay that cost if I knew that it is the right way for any reason. However, in this function (clickCollect) I always set the new value to the state, I'm not waiting for a false API response that would say to stop making the change. In all cases, the new value will get into the state. Practically I call setState only for the UI to render again. So the questions are:
Do I have to create the deep clone in this case? (for var i in ...)
If not, does it make sense to make a shallow clone (.slice(0)) if my array contains objects? The changes are being made on the objects inside of the array, so the shallow clone still changes my state, just like a copy (data = this.state.data) would do.
My code is simplified and API calls are cut out for simplicity.
This is a beginner's question, so a totally different approach is also welcome. Or links to other Q & A.
import React from 'react';
var ForumList = React.createClass({
render: function() {
return <div className="section-inner">
{this.state.data.map(this.eachBox)}
</div>
},
eachBox: function(box, i) {
return <div key={i} className="box-door">
<div className={"favorite " + (box.is_collected ? "on" : "off")} onTouchStart={this.clickCollect.bind(null, i)}>
{box.id}
</div>
</div>
},
getInitialState: function() {
return {data: [
{
id: 47,
is_collected: false
},
{
id: 23,
is_collected: false
},
{
id: 5,
is_collected: true
}
]};
},
clickCollect: function(index) {
var data = this.state.data.slice(0);
data[index].is_collected = !data[index].is_collected;
this.setState({data: data});
}
});
module.exports = ForumList;
Personally I don't always follow the rule, if you really understand what you are trying to do then I don't think it's a problem.
var data = this.state.data.slice(0);
data[index].is_collected = !data[index].is_collected;
this.setState({data: data});
In this case, mutating state and calling the setState again like this is fine
this.state.data[index].is_collected = !this.state.data[index].is_collected;
this.setState({data: this.state.data});
The reason you should avoid mutating your state is that if you have a reference to this.state.data, and calling setState multiple times, you may lose your data:
const myData = this.state.data
myData[0] = 'foo'
this.setState({ data: myData })
// do something...
// ...
const someNewData = someFunc()
this.setState({ data: someNewData })
myData[1] = 'bar' // myData is still referencing to the old state
this.setState({ data: myData }) // you lose everything of `someNewData`
If you really concerned about this, just go for immutable.js
Muting the state directly breaks the primary principle of React's data flow (which is made to be unidirectional), making your app very fragile and basically ignoring the whole component lifecycle.
So, while nothing really stops you from mutating the component state without setState({}), you would have to avoid that at all costs if you want to really take advantage of React, otherwise you would be leapfrogging one of the library's core functionalities.
If you want follow react best practices, you should do shallow copy of all your array, when you change any property. Please look into "immutable" library implementation.
But, from my experience, and from my opinion, setState method should be called if you have "shouldCompomenentUpdate" implementations. If you think, that your shallow copy will be consume much more resources, then react virtual dom checks, you can do this:
this.state.data[0].property = !this.state.data[0].property;
this.forceUpdate();
If I understood your question right, you have an array of objects and when a property of a single object in array changes,
Create a deep clone of the array and pass to setState
Create a shallow clone and pass to setState
I just checked with the redux sample todo app and in case of a single property of an object changes you've to create a fresh copy of that single object not the entire array. I recommend you to read about redux and if possible use to manage the state of your app.
I am trying to clear an array, but I'm having trouble.
this.setState({warnErrorTypes:[]})
I'm not sure if I am dealing with a race condition, or what the specific issue is, but I can see that the value of my array is consistently wrong in the case that I need to reset its value to [].
How does one replace an array that contains [1,2] with [] then subsequently [3] where the following are true:
this.state.warnErrorTypes is an Array which starts out with []
Based on condition, 2 is pushed in Array
Based on condition, 1 is pushed in Array.
Based on condition, 3 is NOT pushed in Array
Pause. User interacts with UI
Array is blanked: this.setState({warnErrorTypes:[]})
Based on condition, 2 is NOT pushed in Array
Based on condition, 1 is NOT pushed in Array
Based on condition, 3 is pushed in Array.
The result of the logic above is always [2,1,3], when I expect it to be [3].
setState gets aggregated and scheduled, it does not run atomic and immediate, so you can't just issue multiple setState() calls and expect things to work, you either have to wait for the state to update before updating it again, or use an instance variable.
Option 1:
moo: function() {
this.setState({
myarr: []
}, function() { // called by React after the state is updated
this.setState({
myarr: [3]
});
});
}
This is fairly cumbersome and depending on what you're doing, mostly just bad code. The other option is to use a "real" instance variable that you send over as state at moments when you need to.
Option 2:
getInitialState: function() {
this.mylist = [];
return {
myarr: this.mylist
};
},
...
moo: function() {
this.mylist = [];
for(var i=0; i<10; i++) {
this.mylist.push(i);
}
this.setState({ myarr: this.mylist });
}
Remember that updating the state means you have changed an aspect of your component that warrants a rerender, so don't use setState if you don't intend the component to rerender, like between clearing the array and refilling it. Do that stuff separately, and only update the state once you're done.
Option 3:
You could also do this by taking out the state values, running your updates, and then rebinding, without ever building a persistant instance variable:
moo: function() {
var list = this.state.myarr;
while(list.length > 0) { list.splice(0,1); }
for(var i=0; i<10; i++) { list.push(i); }
this.setState({ myarr: list });
}
The net effect is the same: you only update your UI when your data is in some stable configuration, so if you think you're calling setState() more than once between renders, that's a problem: every setState() call may trigger a render "eventually", and consecutive setState() calls before that happens will "override" same-named-key value updates if you don't wait for them to bind first.
Option 3, as mentioned by Anders Ekdahl:
moo () {
this.setState(state => ({
myarr: []
}));
// ...
if (weShouldAddAThree) {
this.setState(state => ({
myarr: [ ...state.myarr, 3 ] // like push but without mutation
}));
}
}
This pattern is useful if you need to refer to the previous existing state when you perform your state update. I'm not sure if you really need the previous state in your example, but I will explain this pattern as if you did.
The merge operation we provide to setState is always applied asynchronously, at some point in the future, at React's discretion. When the setState() function returns, our operation has not been applied, it has only been queued.
Therefore we should never use this.state in our state updater, because that might be out of date: an old copy of the state. If we need to know the previous state, we should receive the state argument in the function we pass to setState, and use that.
You can as well use this to clear array:
this.state.your_array.length = 0;
setState({your_array});