I have the following array I am working with
const data = [
{
name: "Person",
id: 0,
familyMembers: [
{
id: 00,
name: "personOne"
},
{
id: 01,
name: "personTwo"
}
]
},
]
I am dynamically mapping over this array with one map for the main objects and another for the nested array. I have a toggle and I am trying to implement functionality that when the parent is selected it automatically selects the nest kids of said parent as well.
const [isSelected, setIsSelected] = useState({});
const handleCheck = (name: string, event: any): void => {
setIsSelected({ ...isSelected, [name]: event.target.isSelected });
};
Well, it took me some time to apply it :)
First of all, there isn't any isSelected prop for the CheckBox component and it should be changed to checked as below:
checked={isSelected[id]}
Then, handleCheck function in dataList was not called properly and it should be changed as below:
dataList(
nestedItem.name,
nestedItem.id,
true,
isSelected,
(name: string, event: any | undefined) =>
handleCheck(event, name)
)
we need to also initialize values in useState otherwise we would receive this error:
component is changing an uncontrolled input to be controlled
Finally, in the handleCheck function I checked whether data[name] exist and then mapped over data[name].familyMembers and stored checked values in the isSelected object as below:
const handleCheck = (name: string, event: any): void => {
var nested = {};
if (data[name]) {
data[name].familyMembers.map(
(el, id) => (nested = { ...nested, [el.id]: event.target.checked })
);
}
setIsSelected({ ...isSelected, [name]: event.target.checked, ...nested });
};
You can also check these corrections online here in sandbox
Related
I want to make a Dropdown component with two versions, one that can return multiple selected values and one that returns a single selected value. All of that is determined by a single prop which is called variant. this variant has union type single | multiple. So this is what it looks like:
type MyOption = { label: any; value: any };
type MyDropdownProps =
| {
variant?: "single";
options: [];
onChange: (values: MyOption) => void;
}
| {
variant?: "multi";
options: [];
onChange: (values: MyOption[]) => void;
};
const MyDropdown = (props: MyDropdownProps) => {
return <pre>{JSON.stringify(props)}</pre>;
};
const Render = () => {
return <MyDropdown options={[]} variant="single" onChange={(evt) => {}} />;
};
you see this is work correctly if we specify the variant prop:
my question is what if we don't want to specify the variant prop? what if we already specify it from defaultProps?
const MyDropdown = (props: MyDropdownProps) => {
return <pre>{JSON.stringify(props)}</pre>;
};
MyDropdown.defaultProps = {
variant: "single"
};
You see the onChange is not returning the correct data, it returns with any type. How do we fix this? I've spent weeks trying to find the answer but still have not found it yet :(
Define your props and component as generic and use the variant type to determine the parameter type for onChange.
Since your variant can only be one of two types, I would opt for a simpler Boolean type determined by the presence of a multiple prop (just like <select>).
interface MyDropDownProps<Multiple extends Boolean> {
multiple?: Multiple;
options: MyOption[];
onChange: (values: Multiple extends true ? MyOption[] : MyOption) => void;
}
You can then define your generic component
const MyDropdown = <Multiple extends Boolean>(
props: MyDropDownProps<Multiple>
) => {
return <pre>{JSON.stringify(props)}</pre>;
};
Consider this example:
import React from 'react'
type MyOption = { label: any; value: any };
type MyDropdownProps =
{
options: [];
onChange: (values: MyOption[] | MyOption) => void;
};
const MyDropdown = (props: MyDropdownProps) => {
return <pre>{JSON.stringify(props)}</pre>;
};
const Render = () => {
return <MyDropdown options={[]} onChange={(evt /* MyOption[] | MyOption */) => { }} />;
};
Playground
If you don't want to specify variant property, then, in fact you don't need a union of props. You need a union of onChange arguments
I have question about changing properties inside the object array. I will post my code below for better understanding. First, I have object array that contains two array and first array contain attribute called "first" and second array contains "last". When I click on the function it will pass the specific name such as first or last as string. After that I have to loop through the object array and change the value that matches the properties name (tempFirst,tempLast). I just declared inside the onHandle function but on my real project, I am passing through the parameters.
example would be: if (first === first) change the value of that property.
import logo from './logo.svg';
import './App.css';
import react ,{ Component } from 'react';
class App extends Component {
constructor(props) {
super(props);
this.state = {
objectTemp: []
};
}
componentDidMount(){
let objectArray = [
{
name: "hello",
first: 77
},
{
name: "world",
last: 66
},
]
this.setState({
objectTemp: objectArray
})
}
onHandle = () => {
let tempFirst = "first";
let tempLast = "last";
let storeState = [...this.state.objectTemp];
//console.log(storeState);
// here i want to check if either tempfirst or templast is equal to the property on object array
for(let i = 0; i < storeState.length; i++){
//if(storeState[i] === tempLast){
console.log(...storeState[i]);
//}
}
}
render() {
console.log(this.state);
return <button onClick={this.onHandle}>VIEW</button>;
}
}
export default App;
I think the step for your modification is:
onHandle = (firstName: string, value: any) => {
/// Modify your data:
const newData = this.state.objectTemp.map( item => {
/// Update Data if matched condition
if(item.firstName === firstName){
item.value = value
}
return item
})
/// Update state
this.setState({objectTemp: newData})
}
You can map the previous state to new state, and when mapping each element object, check each key against a "searchKey" and if you find a match update the value.
Here's an example onHandle function that maps the objectTemp state and creates an array of object entries (i.e. array of key-value pairs) of the objects. It reduces the entries array back into an object, and while doing so checks the object keys for the match.
onHandle = (searchKey, newValue) => {
this.setState(prevState => ({
// map previous state to next state
// [obj1, obj2, ...objN]
objectTemp: prevState.objectTemp.map(obj => {
// Create array of entries and reduce back into object
// [[key1, value1], [key2, value2], ...[keyN, valueN]]
return Object.entries(obj).reduce((obj, [key]) => {
// If key is a match to search key, create new object and update value,
// otherwise return existing object
return key === searchKey
? {
...obj,
[key]: newValue
}
: obj;
}, obj);
})
}))
}
Demo
I have the following components:
const ParentComponent: React.FC = () => {
// Somewhere in the code, I set this to some value
const [newType, setNewType] = useState<any>(undefined);
// Somewhere in the code, I set this to true
const [enableAddEdge, setEnableAddEdge] = useState(false);
const addEdge = (data: any) => {
console.log("newType", newType); // This is undefined
}
return (
...
<VisNetwork
newType={newType}
onAddEdge={addEdge}
enableAddEdge={enableAddEdge}
/>
...
)
}
export default ParentComponent;
interface Props {
newType: any;
onAddEdge: (data: any) => void;
enableAddEdge: boolean;
}
const VisNetwork: React.FC<Props> = (props: Props) => {
const options: any = {
// Some vis-network specific option configuration
manipulation: {
addEdge: (data: any, callback: any) => props.onAddEdge(data);
}
}
...
// Some code to create the network and pass in the options
const network = new vis.Network(container, networkData, options);
useEffect(() => {
if (props.enableAddEdge) {
// This confirms that indeed newType has value
console.log("newType from addEdge", props.newType);
// Call reference to network (I name it currentNetwork)
// This will enable the adding of edge in the network.
// When the user is done adding the edge,
// the `addEdge` method in the `options.manipulation` will be called.
currentNetwork.addEdgeMode();
}
}, [props.enableAddEdge])
useEffect(() => {
if (props.newType) {
// This is just to confirm that indeed I am setting the newType value
console.log("newType from visNetwork", props.newType); // This has value
}
}, [props.newType]);
}
export default VisNetwork;
When the addEdge method is called, the newType state becomes undefined. I know about the bind but I don't know if it's possible to use it and how to use it in a functional component. Please advise on how to obtain the newType value.
Also, from VisNetwork, I want to access networkData state from inside options.manipulation.addEdge. I know it's not possible but any workaround? I also need to access the networkData at this point.
You need to useRef in this scenario. It appears const network = new vis.Network(container, networkData, options); uses the options from the first render only. Or something similar is going on.
It's likely to do with there being a closure around newType in the addEdge function. So it has stale values: https://reactjs.org/docs/hooks-faq.html#why-am-i-seeing-stale-props-or-state-inside-my-function
In order to combat this, you need to useRef to store the latest value of newType. The reference is mutable, so it can always store the current value of newType without re-rendering.
// Somewhere in the code, I set this to some value
const [newType, setNewType] = useState<any>(undefined);
const newTypeRef = useRef(newType);
useEffect(() => {
// Ensure the ref is always at the latest value
newTypeRef.current = newType;
}, [newType])
const addEdge = (data: any) => {
console.log("newType", newTypeRef.current); // This is undefined
}
I'm using react useState, where the state is an object with some nested properties. When I call setState on it, I'm not seeing a re-render or the state being updated. I assume react is seeing that the new state equals the old state and so no updates occur. So, I've tried cloning the state first, but still am not seeing any updates.
How can I get this function to cause the state to update?
export type TermEditorStateRecord = {
term: SolrTermType;
state: SolrTermEditorRecordState;
classifications: { [key: string]: string };
};
export type TermEditorStateRecordMap = {
[phrase: string]: TermEditorStateRecord;
};
const [records, setRecords] = useState({});
const setRecordClassification = (label, key, value) => {
const cloned = new Object(records) as TermEditorStateRecordMap;
cloned[label].classifications[key] = value;
setRecords(cloned);
};
I apologize for the TypeScript types, but I've included them here so that you can see the expected shape of the state.
Is it not updating because the changes are deeply nested? Is there a way to get this to work, or do I need to somehow pull the state that changes out into its own state?
new Object does not make a deep copy, so for setRecords it's the same instance and it won't trigger the re-render,
const obj = {
a: {
b: "b"
}
};
const copy = new Object(obj);
copy["c"] = "c";
console.log(obj);
You'll need to manually updated the nested property :
const setRecordClassification = (label, key, value) => {
setRecords(record => ({
...record,
[label]: {
...record[label],
classifications: {
...record[label].classifications,
[key]: value
}
}
}));
};
or to create a copy, use :
const cloned = JSON.parse(JSON.stringify(record));
cloned[label].classifications[key] = value;
setRecords(cloned);
I have a component that I am giving an array with objects as props to it like this:
describe('component', () => {
it('should return the correct number of items passed in the array', () => {
const comp = shallowMount(component, {propsData: {
buttons: [
{name:'button1'},
{name:'button2'}
]
}});
expect(component.length).toHaveLength(buttons).length
});
});
How can I test that the provided array has the correct length, for example if there are two objects in the array, the component should return two, if there is one, one, if there are none then it should return 0, how can I achieve that? I tried
expect(component.length).toHaveLength(buttons).length
But that does not work
I guess you want to check if the correct number of childs of some type was rendered (in Vue).
// import component that you want to count, e.g. Button
const buttons = [
{ name: 'button1' },
{ name: 'button2' }
]
const comp = shallowMount(component, { propsData: { buttons } })
expect(comp.findAll(Button).length).toBe(buttons.length)
https://lmiller1990.github.io/vue-testing-handbook/finding-elements-and-components.html#findall
const buttons = [
{name:'button1'},
{name:'button2'}
]
const comp = shallowMount(component, {propsData: { buttons }});
expect(comp.findAll(Button)).toHaveLength(buttons.length)