I am in the beginning of learning Vue, and having a hard time understanding how to define props etc. using the composition API.
I have a component (ACE Editor), loaded like this:
<v-ace-editor
v-model:value="currentRecord.text"
/>
The v-ace-editor needs to have the model and value loaded like this: v-model:value.
import {computed, ref} from 'vue';
import collect from "collect.js"
const props = defineProps({
records: {
type: Object,
},
})
//Get all records.
let getRecords = () => {
return collect(props.records)
}
//Filter the records using "collect.js"
const getUnlabeledRecords = () => {
return getRecords()
.where('skipped', false)
.where('processed', false);
}
//Assign all unlabeled records on the first load.
let allRecords = ref(getUnlabeledRecords());
//In order to check if "text" is empty - and if not, set a default value, load the first record into a temp. variable.
let first = allRecords.value.first();
if(first){
first.text ??= "";
}
//Load the current record into a ref.
let current = ref(first);
//Finally, compute it.
let currentRecord = computed(() => current.value)
Looking at this, and coming from a backend background, it feels very bloated.
I have tried the following:
let allRecords = ref(getUnlabeledRecords());
let currentRecord = computed(() => allRecords.value.first())
But doing this leads to me not being able to interact with the currentRecord - nor change the allRecords. This means that if for example currentRecord.text is null from the backend, my ace-editor component fails because it expects a value.
Is there another way to load in these variables?
You actually don't have to called .value of a ref when using it in the template.
So you can actually remove the computed part (last line of your ) and change your template to.
<v-ace-editor
v-model="current.text"
/>
Now, assuming you managed v-model correctly in v-ace-editor (if this is your own component), you should have reactivity kept when modifiying current.text from v-ace-editor.
As a side note, computed properties are read-only. You cannot expect a child component to modify its value by passing it with v-model.
However, you should note that updating records prop from parent component will not update current. For this, maybe you want to add a watcher on records.
Also, personal suggestion, but if you only really care about currentRecord in your component and not all records, maybe you should do the filtering from parent component and only pass currentRecord as a prop. Other personal suggestion, you can declare all your variables in your script with const instead of let. const prevent reassignation, but since you work with refs, you never reassign it, but you change its value property.
Related
I'm using the library React Datasheet Grid, the main component has a property called OnActiveCellChange, to which i'm passing my custom function:
const [selectedCell, setSelectedCell] = useState([])
...
function handleCellSelection({ cell }) {
if(cell) {
const {row, col} = cell;
const newData = data[row][col];
if(!newData.unpickable) {
console.log(selectedCell); // prints empty array
setSelectedCell(selectedCell => [...selectedCell, newData.value]);
}
}
}
...
<DataSheetGrid
className={'overwriteDisabledStyles'}
value={data}
onChange={setData}
columns={columns}
onActiveCellChange={handleCellSelection}
headerRowHeight={false}
gutterColumn={false}
lockRows
/>
The problem is that I wish to make some checks with the state variable "selectedCell", while I managed to update the state from with the functional form, I can't access the the state variable. I managed to use the variable using "useRef".
I want to know if I can tackle the problem in any other way.
EDIT:
Trying to clarify my question:
What I want is to, inside the handleCellSelection function, make some checks before inserting the newData using the setSelectedCell. Like searching the selectedCell array for existing values:
const valueAlreadyExists = selectedCell.some((existingCell) => {
return existingCell === newData.value
});
Yet, when the handleCellSelection function is called, the value of selectedCell is always an empty array. It should be the values that are being inserted in the array. I know there are values there because when I console.log outside the handleCellSelection they appear correctly.
So, I managed to access them inside the handleCellSelection using useRef() but what I am searching for is another option to solve this.
I use a react module that manages the use of spreadsheets:
react-spreadsheet https://github.com/iddan/react-spreadsheet/
In my project, I need several spreadsheets, and therefore several tables that the user can add, modify, delete on the fly
At the beginning, I wanted to use a big useState variable where the data arrays would be concentrated in the form : stateData = [ [...arrays], [...arrays...] ]
But the module, which uses an OnChange function taking as value the setData, seems to bug when my stateData variable contains different arrays.
<Spreadsheet key={index} className='table' columnLabels={stringLabelCol} rowLabels={stringLabelRow} data={stateData[index]} onChange={setData} />
As I can't manage everything in one UseState with this module,
is it possible to create a series of UseState inside a loop ?
It seems to me that it is a bad practice but I am really blocked
Thank you for reading my text... :/
Seeing your code and your comment, I'm assuming that:
You have one useState named data with ALL your data in it
You have multiple spreadsheets
For each spreadsheet, you want to pass the sheetData and the onChange that updates it
The issue in your code is that you're setting onChange={setData}, meaning any change will override the WHOLE data object, not just the targeted index.
What you want to do is have your onChange function be a function that updates only the right part of your state. So you'd do it like so:
const [data, setData] = useState([])
const updateSheetData = (index, sheetData) => {
// We clone the current state
const tempData = [...data]
// We update the right index
tempData[index] = sheetData
// This is now the new state
setData(tempData)
}
//...
// the onChange function for our component will use our custom function
<Spreadsheet
key={index}
className='table'
columnLabels={stringLabelCol}
rowLabels={stringLabelRow}
data={stateData[index]}
onChange={(newData) => updateSheetData(index, newData)}
/>
Then you'll also have to correctly update the data state whenever you add a new sheet or remove a sheet, to make sure the index are updated. But the idea is the same
Should ES6 classes be used directly as React state?
I want to define an ES6 class that:
Has member variables that will be displayed on the frontend. (changes to them trigger re-renders)
Has methods that sync those member variables with my backend periodically as they change.
However, calling setState does not appear to diff class members, at least as far as I can tell.
Using the following class:
class Document{
constructor(){
this.title = "";
this.body = "";
}
syncWithDatabase = async () => {
// do some logic to update the database
}
}
And this component:
// import Document from "...";
export default function Sandbox() {
const [document, setDocument] = useState(new Document());
const [renderTrigger, setRenderTrigger] = useState(false);
return (
<div>
<div>{document.title}</div>
<div>{document.body}</div>
<button
onClick={() => {
document.title = 'Some Default Title';
document.body = 'lorem text';
document.syncWithDatabase(); // being able to take this type of action in this way is why I'm trying to use classes.
setDocument(document);
}}
>
Set Canned Data
</button>
<div>Render trigger is: {renderTrigger ? 'true' : 'false'}</div>
<button onClick={() => setRenderTrigger(true)}>Force Render</button>
</div>
);
}
Clicking the first button will set the title and body on the instance of Document held react state, but it will not update the UI.
Clicking the second button to force a re-render in a way that I am confident will work makes the updated members of document render out, even though they didn't when setDocument is called.
Creating a new object with new Document() and passing it setDocument WILL trigger a re-render. So I'm thinking that react isn't doing a deep compare or is seeing that the reference to the Document object has not changed, and therefore not re-rending.
So, is it possible to change an object's members, pass that object to a setState hook and have it update the UI, without creating an entirely new object? Or should I avoid doing what I'm trying to do here?
You can (but probably shouldn't, see below) use an object created by a constructor function (which is what document is in your code) as state. What you can't do is directly modify it as you are here (see the documentation):
document.title = 'Some Default Title'; // <=== INCORRECT
document.body = 'lorem text'; // <=== INCORRECT
document.syncWithDatabase();
setDocument(document); // <=== INCORRECT
Instead, you'd need to create a new document object
const newDoc = new Document();
newDoc.title = 'Some Default Title';
newDoc.body = 'lorem text';
newDoc.syncWithDatabase();
setDocument(newDoc);
That said, when using the useState hook, you're usually better off keeping your state variables discrete (having one for title and one for body), so that changing one doesn't require also changing the other (unless, of course, they always change together). The documentation discusses that here; here's one quote:
...we recommend to split state into multiple state variables based on which values tend to change together.
(their emphasis)
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();
...
}
I'm having trouble determining if my component hierarchy really needs getDerivedStateFromProps, if the cases where it is needed is really as rare as the documentation makes it sound. It might be a fundamental misunderstanding about React/Redux design.
class AttributeList extends React.Component {
constructor(props){
super(props)
this.state = {
attributes: props.attributes,
newAttributes: []
}
}
addNewAttribute = () => {
// add new empty attribute to newAttributes state
}
onKeyChange = () => {
// update appropriate attribute key
}
onValueChange = () => {
// update appropriate attribute value
}
saveAttributes = () => {
// save the, API call
}
render = () => {
this.state.attributes.map((pair) => {
<Attribute
//pass data + functions, functional component />
})
this.state.newAttributes.map((pair) => {
<Attribute
//pass data + functions, functional component />
})
}
static getDerivedStateFromProps(){
// ?? do comparisons here to choose to remove or keep certain newAttributes? or just ignore result of save and keep interface as-is, just show error message if saving failed.
}
}
I have a parent component AttributeList which renders a bunch of Attributes, which are essentially key-value pairs. AttributeList receives the list of attributes of a document as props. However, the attributes can be edited, so it initializes its state (this.state.attributes) with this.props.attributes. Normally keys are immutable, but if a user adds a new attribute to the list, he can edit both the key and value. At any point, a user can save all the attributes. When the new attributes are saved, I'd like to disabled editing the keys for them as well. Here is the dilemma.
Option one is to save the document and just hope it worked, and then clear the new attributes list and mark all the attributes as saved (disabling the key input). I think this would be the "fully uncontrolled" solution, where once the state is initialized the component deals with everything on it's own. However, what if the save fails? I don't want to show and incorrect state to the user.
So I want to do option two. After save, fetch the document, which will load the attribute list and re-render the component. However I need to get rid of my new attributes since they are now a part of the attributes prop. I would like to verify that the new attributes are actually a part of the attributes prop now. It seems like this would happen ingetDerivedStateFromProps where I would on each render cycle check if any new attribute keys already exist in the attributes prop, and remove them from the "new" list if they do, and return that state.
But is this really the right time to use getDerivedStateFromProps? It seems to me that for any page that a user is "editing" something where you make an API call to save it, if you want to render based on the saved data ("the truth"), then I need to use getDerivedStateFromProps. Or perhaps from a design perspective it is better to show a message akin to "data not successfully saved" and keep the state as is, to prevent any data loss. I'm honestly not sure.
I don't see how getDerivedStateFromProps comes into it as there's no reason you need to copy props into state is there? When an old attribute value is changed you save it to the redux store, when new attribute properties are changed you can update local state (or save them to a different slice of the store, or differentiate them some other way). Update rules can be enforced in the update handlers or during merge on save.
// dispatch redux action to update store
onOldValueChange = () => {}
// this.setState to update new value
onNewKeyChange = () => {}
onNewValueChange = () => {}
render = () => {
this.props.attributes.map((pair) => {
<Attribute
//pass data + onOldValueChange, functional component />
})
this.state.newAttributes.map((pair) => {
<NewAttribute
//pass data + functions, functional component />
})
}