How to set list of object as default values in material-UI multi select box in reactjs? - reactjs

I have this example using multi select box from material-ui v4.
The example contains two components the first with a default value list of object that is not working properly and a second component with a list of string that works fine.
The problem: (first component) when I open the Select component the default values is not selected and when I click on the default value it added again to the select box I have the same value multiple time.

Use the same object, eg change.
const [personName, setPersonName] = React.useState([
{ _id: '1', name: 'Oliver Hansen' },
{ _id: '2', name: 'Van Henry' },
]);
to:
const [personName, setPersonName] = React.useState(names.slice(0,2));
I don't remember this being necessary previously with materialUI, but it fixes the problem in your demo, so maybe I just haven't run into it before.

This is what i tried Array.reduce (Shallow copy):
const namesCopy = names.reduce((newArray, element) => {
props.defaultList.map((item) => {
if (item._id === element._id) {
newArray.push(element);
}
});
return newArray;
}, []);
const [personName, setPersonName] = React.useState(namesCopy);

Related

React Select with firestore array

I'm trying to display a react-select with options equal to Firestore data but I get only one option with all the array. How can I show the options individually? Thanks.
Well, this is my options from react-select:
const options = [{ value: read01, label: read01 }];
and this is where I retrieve the date:
const retrieveNetwork13 = async () => {
try {
//const querySnapshot = await getDocs(collection(db, "cities"));
const q = query(collection(db, "schedule"));
const qq = query(q, where("uid2", "==", uid3));
const querySnapshot = await getDocs(qq);
setRead01(querySnapshot.docs.map((doc) => doc.data().schedule2));
} catch (e) {
alert(e);
}
};
Let's begin by isolating the key parts of your code. Here, you are using a react setter function to set read01 to some transformation of your query snapshot (I'm assuming that elsewhere there's code like const [read01, setRead01] = useState([])):
setRead01(querySnapshot.docs.map((doc) => doc.data().schedule2));
querySnapshot.docs appears to be an array of objects, which you are querying for its data() and then extracting the property schedule2. I can't say what the shape of schedule2 is from the information you've provided, but let's assume that it's just a string. Given all that, you are setting read01 to an array of strings (the result of querySnapshot.docs.map).
Now, let's look at the options you provide to react-select:
const options = [{ value: read01, label: read01 }];
You have specified one and only one option, for which both the value and label will be read01, which we've established is an array of strings. However, value and label in a react-select options array are intended to be strings, not arrays of strings. See e.g. the first example on the react-select homepage. The relevant options array is:
export const colourOptions: readonly ColourOption[] = [
{ value: 'ocean', label: 'Ocean', color: '#00B8D9', isFixed: true },
{ value: 'blue', label: 'Blue', color: '#0052CC', isDisabled: true },
...
];
See how value and label are just plain strings?
I think what you probably intended to do is map read01 into an array of options. Imagine that this is the contents of read01:
const read01 = ["option1", "option2", "option3"];
Then, we could define options as:
const options = read01.map((val) => ({ value: val, label: val }));
Here's a working sandbox for you to play with.

how to make multipe selectable checkboxes using React,GraphQL,Apollo

I'm making multiple selectable checkboxes using React, GraphQL, Apollo.
I would like to make a query if it is selected, insert query and unselected, delete query(I want to generate a query only for selected/removed items)
Right now I'm making it using mutation but I have a problem where everything is deleted and then inserted.
I would like to generate a query only for selected/removed items. How can I fix it?
My code is as follows.
const [updateNoteMutation] = useUpdateNoteMutation();
const updateLogic = (key: string, value: string | number | Condition[]) => {
const noteVariables = {
variables: {
id: noteData.id,
noteAttributes: {
[key]: value,
},
},
};
updateNoteMutation(noteVariables).then(({ errors }) => {});
});
const handleCollection = (name: string, arr: Array) => {
//arr: List of selected checkboxes.
const noteArr = [];
diff.map((val) => {
noteArr.push({ name: val });
});
updateLogic(name, noteArr);
};
updateNoteMutation
mutation UpdateNote($id: ID!, $noteAttributes: FemappNoteInput!) {
femappUpdateNote(input: {id: $id, noteAttributes: $noteAttributes}) {
note {
id
checkboxList {
name
}
}
}
}
Please let me know if there is a source code that I can refer to
Thanks for reading my question.
You can try react-select for that. Really easy to implement and fully customizable.
Here is the repo;
https://github.com/JedWatson/react-select

How to use useEffect correctly with useContext as a dependency

I'm working on my first React project and I have the following problem.
How I want my code to work:
I add Items into an array accessible by context (context.items)
I want to run a useEffect function in a component, where the context.items are displayed, whenever the value changes
What I tried:
Listing the context (both context and context.items) as a dependency in the useEffect
this resulted in the component not updating when the values changed
Listing the context.items.length
this resulted in the component updating when the length of the array changed however, not when the values of individual items changed.
wraping the context in Object.values(context)
result was exactly what I wanted, except React is now Complaining that *The final argument passed to useEffect changed size between renders. The order and size of this array must remain constant. *
Do you know any way to fix this React warning or a different way of running useEffect on context value changing?
Well, didn't want to add code hoping it would be some simple error on my side, but even with some answers I still wasn't able to fix this, so here it is, reduced in hope of simplifying.
Context component:
const NewOrder = createContext({
orderItems: [{
itemId: "",
name: "",
amount: 0,
more:[""]
}],
addOrderItem: (newOItem: OrderItem) => {},
removeOrderItem: (oItemId: string) => {},
removeAllOrderItems: () => {},
});
export const NewOrderProvider: React.FC = (props) => {
// state
const [orderList, setOrderList] = useState<OrderItem[]>([]);
const context = {
orderItems: orderList,
addOrderItem: addOItemHandler,
removeOrderItem: removeOItemHandler,
removeAllOrderItems: removeAllOItemsHandler,
};
// handlers
function addOItemHandler(newOItem: OrderItem) {
setOrderList((prevOrderList: OrderItem[]) => {
prevOrderList.unshift(newOItem);
return prevOrderList;
});
}
function removeOItemHandler(oItemId: string) {
setOrderList((prevOrderList: OrderItem[]) => {
const itemToDeleteIndex = prevOrderList.findIndex((item: OrderItem) => item.itemId === oItemId);
console.log(itemToDeleteIndex);
prevOrderList.splice(itemToDeleteIndex, 1);
return prevOrderList;
});
}
function removeAllOItemsHandler() {
setOrderList([]);
}
return <NewOrder.Provider value={context}>{props.children}</NewOrder.Provider>;
};
export default NewOrder;
the component (a modal actually) displaying the data:
const OrderMenu: React.FC<{ isOpen: boolean; hideModal: Function }> = (
props
) => {
const NewOrderContext = useContext(NewOrder);
useEffect(() => {
if (NewOrderContext.orderItems.length > 0) {
const oItems: JSX.Element[] = [];
NewOrderContext.orderItems.forEach((item) => {
const fullItem = {
itemId:item.itemId,
name: item.name,
amount: item.amount,
more: item.more,
};
oItems.push(
<OItem item={fullItem} editItem={() => editItem(item.itemId)} key={item.itemId} />
);
});
setContent(<div>{oItems}</div>);
} else {
exit();
}
}, [NewOrderContext.orderItems.length, props.isOpen]);
some comments to the code:
it's actually done in Type Script, that involves some extra syntax
-content (and set Content)is a state which is then part of return value so some parts can be set dynamically
-exit is a function closing the modal, also why props.is Open is included
with this .length extension the modal displays changes when i remove an item from the list, however, not when I modify it not changeing the length of the orderItems,but only values of one of the objects inside of it.
as i mentioned before, i found some answers where they say i should set the dependency like this: ...Object.values(<contextVariable>) which technically works, but results in react complaining that *The final argument passed to useEffect changed size between renders. The order and size of this array must remain constant. *
the values displayed change to correct values when i close and reopen the modal, changing props.isOpen indicating that the problem lies in the context dependency
You can start by creating your app context as below, I will be using an example of a shopping cart
import * as React from "react"
const AppContext = React.createContext({
cart:[]
});
const AppContextProvider = (props) => {
const [cart,setCart] = React.useState([])
const addCartItem = (newItem)=>{
let updatedCart = [...cart];
updatedCart.push(newItem)
setCart(updatedCart)
}
return <AppContext.Provider value={{
cart
}}>{props.children}</AppContext.Provider>;
};
const useAppContext = () => React.useContext(AppContext);
export { AppContextProvider, useAppContext };
Then you consume the app context anywhere in the app as below, whenever the length of the cart changes you be notified in the shopping cart
import * as React from "react";
import { useAppContext } from "../../context/app,context";
const ShoppingCart: React.FC = () => {
const appContext = useAppContext();
React.useEffect(() => {
console.log(appContext.cart.length);
}, [appContext.cart]);
return <div>{appContext.cart.length}</div>;
};
export default ShoppingCart;
You can try passing the context variable to useEffect dependency array and inside useEffect body perform a check to see if the value is not null for example.

Get ID from 1st withTracker query and pass to a 2nd withTracker query? (Meteor / React)

Im using React with Meteor. Im passing data to my Event React component with withTracker, which gets and ID from the URL:
export default withTracker(props => {
let eventsSub = Meteor.subscribe('events');
return {
event: Events.find({ _id: props.match.params.event }).fetch(),
};
})(Event);
This is working but I now need to get data from another collection called Groups. The hard bit is that I need to get an ID from the event that I'm already returning.
The code below works when I hardcode 1. However 1 is actually dynamic and needs to come from a field return from the event query.
export default withTracker(props => {
let eventsSub = Meteor.subscribe('events');
let groupsSub = Meteor.subscribe('groups');
return {
event: Events.find({ _id: props.match.params.event }).fetch(),
group: Groups.find({ id: 1 }).fetch(),
};
})(Event);
#Yasser's answer looks like it should work although it will error when event is undefined (for example when the event subscription is still loading).
When you know you're looking for a single document you can use .findone() instead of .find().fetch(). Also when you're searching by _id you can just use that directly as the first parameter. You should also provide withTracker() with the loading state of any subscriptions:
export default withTracker(props => {
const eventsSub = Meteor.subscribe('events');
const groupsSub = Meteor.subscribe('groups');
const loading = !(eventsSub.ready() && groupsSub.ready());
const event = Events.findOne(props.match.params.event);
const group = event ? Groups.findOne(event.id) : undefined;
return {
loading,
event,
group,
};
})(Event);
There's another issue from a performance pov. Your two subscriptions don't have any parameters; they may be returning more documents than you really need causing those subscriptions to load slower. I would pass the event parameter to a publication that would then return an array that includes both the event and the group.
export default withTracker(props => {
const oneEventSub = Meteor.subscribe('oneEventAndMatchingGroup',props.match.params.event);
const loading = !oneEventSub.ready();
const event = Events.findOne(props.match.params.event);
const group = event ? Groups.findOne(event.id) : undefined;
return {
loading,
event,
group,
};
})(Event);
The oneEventAndMatchingGroup publication on the server:
Meteor.publish(`oneEventAndMatchingGroup`,function(id){
check(id,String);
const e = Events.findOne(id);
return [ Events.find(id), Groups.find(e.id) ];
});
Note that a publication has to return a cursor or array of cursors hence the use of .find() here.
It's not very clear which field of the event object should be supplied to Groups.find. But I'll try answering the question.
Try using something like this -
export default withTracker(props => {
let eventsSub = Meteor.subscribe('events');
let groupsSub = Meteor.subscribe('groups');
event = Events.find({ _id: props.match.params.event }).fetch();
return {
event,
group: Groups.find({ id: event['id'] }).fetch(),
};
})(Event);
Note this line -
group: Groups.find({ id: event['id'] }).fetch(),
can be modified to use whichever field you need.
group: Groups.find({ id: event['whichever field'] }).fetch(),

REACT.js initial radio select

I have been trying to modify existing code by adding a few of new functionalities.
I have this function that renders set of radiobuttons based on variable ACCREDITATION_TYPES:
createRadioCalibration(name) {
const { id, value, labels } = this.props;
const _t = this.props.intl.formatMessage;
const ACCREDITATION_TYPES = [
[CALIBRATION_ACCREDITED, _t(messages.calibrationAccredited)],
[CALIBRATION_NOT_ACCREDITED, _t(messages.calibrationNotAccredited)]
];
return <FormChoiceGroup
type = "radio"
values = {ACCREDITATION_TYPES.map(mapValueArray)}
key = {`${id}_${name}`}
name = {`${id}_${name}`}
value = {value[name]}
handleChange = {this.handleFieldChangeFn(name)}
/>;
}
The radios by default are all unchecked. When clicked this function is fired up:
handleFieldChangeFn(name) {
return (e) => {
const { handleFieldChange, id } = this.props;
handleFieldChange(id, name, e.target.value);
};
}
The form is rendered as follows:
render () {
const FIELDS = {
[CALIBRATION]: this.createRadioCalibration(CALIBRATION),
};
return (
<div className="">
<label>{_t(messages.calibration)}</label>
{ FIELDS[CALIBRATION] }
How can I select any option I want as an initial state? There are only two options now but what if there were five?
Also how can I manipulate the radio selection based on onChange event of other elements of the form, namely a drop down menu?
Answering my question I got two minuses for asking: in the constructor one needs to add the following this.props.handleFieldChange( 'id', 'radio_button_group_name', 'value_of_the_output_that_should_be_selected' );
That will select particular radio in a loop.

Resources