How to reset a FluentUI Dropdown selection on a change somewhere else? - office-ui-fabric

I'm using a FluentUI dropdown in a reactjs office addin and would like for it to be reset when the user types in a completely different textbox. Can I get a reference to the dropdown and call some reset function?
It's a plain single-select dropdown like:
return <Dropdown
placeholder={"Pick a thing"}
label={"Things"}
options={ thingOptions }
onChange={ handleSelectThing }
>
I saw https://github.com/microsoft/fluentui/issues/5917 but it doesn't quite seem to be the same thing.

I'm not thinking reactively enough. The solution was quite simple when it came down to it:
this.state = {
// other state,
selectedThing: undefined
}
const selectedThingKey =
thingOptions.reduce(
(acc, elt, index) => {
return acc || (this.state.selectedThing === index && elt.key)
},
false);
return <Dropdown
options={ thingOptions }
onChange={ (ev,op,i) => {
this.setState({selectedThing: i});
return handleSelectThing(ev, op, i);
}
}
selectedKey={ selectedThingKey }
notifyOnReselect={true}
/>

Related

Passing state Array to child components in React

I'm trying to pass two array variables to a child component in react. I know it's something simple and I'm being stupid but I jest need a hand.
I make two API calls in two components that are in the same file. They should be adding the iterable data into the variable using the set methods. By the time the call to the component comes, the variables are undefined.
What am I doing wrong?
const ShowResults = props => {
const { pk } = props;
const [attributionsData, setAttributionsData] = useState([])
const [interactionsData, setInteractionsData] = useState([])
function ListAttributions({ pk }) {
const { loading, error, data } = useQuery(GET_ATTRIBUTIONS, {
variables: { pk },
});
if ( loading ) return <h2>LOADING... </h2>;
if ( error ) return `Error! ${error}`;
if ( data !== undefined){
setAttributionsData(data.listAttributions.items);
return <div>
{attributionsData.map(({ sk, nominal, organisation, attribution, file_name, datetime_added, exhibit }, index) => (
<AttributionsCard
key={index}
Sk={sk}
Nominal={nominal}
Organisation={organisation}
Attribution={attribution}
FileName={file_name}
FoundInsidePhone={file_name.match(/[0-9]+/g)}
DateTimeAdded={datetime_added}
Exhibit={exhibit}
Pk ={pk}
/>
))}
</div>
}
}
function ListInteractions({ pk }) { //Aug 2022 - This is a working prototype of the query behavious i'm looking for.
const { loading, error, data } = useQuery(GET_INTERACTIONS, {
variables: { pk },
});
if ( loading ) return <h2>LOADING... </h2>;
if ( error ) return `Error! ${error}`;
if ( data !== undefined){
console.log("Interactions Data - " + data );
setInteractionsData(data.listInteractions.items)
return <div>
{interactionsData.map(({ direction, interaction, partner, duration, datetime, exhibit, organisation, file_name, datetime_added }, index) => (
<InteractionsCard
key={index}
Interaction={interaction}
Direction={direction}
Partner={partner}
Duration={duration}
DateTime={datetime}
Exhibit={exhibit}
Organisation={organisation}
FileName={file_name}
DateTimeAdded={datetime_added}
Pk={pk}
/>
))}
</div>
}
}
return (
<div style={{display: "inline-grid", inlineSize: "max-content", width: "100%"}}>
{/* <h2>🚀 Quest has identified the following results against {pk}</h2> */}
<center>
<ListAttributions pk={pk}/>
<ListInteractions pk={pk}/>
</center>
<br/>
<br />
{/* <ShowInteractionsDataGrid pk={pk}/> */}
{attributionsData !== [] &&
// Sep 14th 20:42 - No idea whats going on with this... The ListAttributions components above work fine
<TabbedResults pk={pk} attributionsData={attributionsData} interactionsData={interactionsData} />
}
<br />
<br />
</div>
);
}
ShowResults.propTypes = {
pk: PropTypes.string
};
export default ShowResults
a) You cant setSomeState and then use it immediately. It will not be immediately available within that same function. So the first time you try to map you will have the initialiser of the useState. In this case an empty array.
b) You should do the queries in the parent component, and pass them in as props so there is no need to set parent state
c) Its not good to be setting the state of a parent scope like this. You should move the children to different files (or at least outside of the component) and communicate with props and callbacks. You will save yourself a lot of trouble.
d) This will always be true so TabbedResults will always render:
attributionsData !== []
This is because [] makes a new array. You should use:
attributionsData && attributionsData.length
e) Never set state straight form the render function. Use a useEffect hook to set it based on the change of a property, such as your data suddenly being populated

react-select Creatable: override default behavior upon pressing Enter key

I'm using react-select Creatable for my project, and I need to override its default behavior. Currently whenever the user types something in and presses Enter key, it gets added as a new option.
Is there a way to change it to "whenever the user types something in and presses Space bar, it gets added as a new option"?
If you just do
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
e.stopPropagation();
}
}}
...in your component, that'll stop the Enter key from doing anything.
To get the space bar to create a new entry, I think you might want to make a KeyDown handler:
function handleKeyDown(e) {
if (e.key === "Enter") {
e.preventDefault();
e.stopPropagation();
}
if (e.key === "Space") {
handleChange();
}
// in component:
onKeyDown={handleKeyDown}
But I have not tried it yet.
Edit: Solution to keep Enter key from submitting a form the CreatableSelect is embedded within, while still allowing Enter to do react-select's default "create a new entry" behavior:
export function TagEditor({data, handleSave}) {
const [newValues, setNewValues] = useState(...our data mgr);
const [lastKey, setLastKey] = useState('');
const handleChange = (newValue) => {
setNewValues(newValue);
};
function handleKeyDown(e) {
/* React Select hack for pages where tags are inside a form element:
Default CreatableSelect functionality is that Enter key creates
the new tag. So we let that happen, but if they hit Enter again,
we kill it so the form doesn't submit. Yes, it's hack-y.
*/
switch (e.key) {
case 'Enter':
if (lastKey === 'Enter') {
e.preventDefault();
e.stopPropagation();
}
setLastKey('Enter');
break;
default:
setLastKey('');
}
}
return <CreatableSelect
aria-label="Add Tags"
autoFocus={true}
blurInputOnSelect={false}
defaultValue={...our data manager function}
inputId="tagmanager"
isClearable={false}
isMulti
name="my_field_name"
onBlur={(e) => {
e.preventDefault();
e.stopPropagation();
handleSave(newValues);
}}
onChange={handleChange}
onKeyDown={handleKeyDown}
openMenuOnFocus={true}
options={data}
placeholder="Select or type new..."
searchable
styles={our styles...}
/>;
}
Note this hack is needed because we're using stateless functional components, and react-select is still a state-full class component.

Auto Calculate Input Fields

Please could someone help answer this:
I have 2 NumberInput controls, one input and the other is disabled. I need to input number in the first input field, the disabled field to show this number/100. The two NumberInput will have source fields that will save to the current record in the simpleform.
How do I do this in react-admin
Thanks
Easiest way is to use the method described in the docs under section Linking two inputs
In essence: You can create your own input component where you can access the form values via the hook useFormState. Then just assign the desired value transformed the way you want e.g. divided by 100.
Edit
Found one more even cleaner way - using the final-form-calculate to create a decorator and pass it to the <FormWithRedirect /> component like so:
import createDecorator from 'final-form-calculate'
const calculator = createDecorator(
// Calculations:
{
field: 'number1', // when the value of foo changes...
updates: {
number2: (fooValue, allValues) => allValues["number1"] * 2
}
})
...
<FormWithRedirect
...
decorators={[calculator]}
/>
Check out this code sandbox
Using FormDataConsumer
<FormDataConsumer>
{({ formData }) => (
<NumberInput defaultValue={formData.my_first_input / 100} source="second_input"/>
)}
</FormDataConsumer>
Using the useFormState hook
import { useFormState } from 'react-final-form';
...
const { values: { my_first_input }} = useFormState({ subscription: { values: true } });
...
<NumberInput defaultValue={my_first_input / 100} source="second_input"/>
Source: https://marmelab.com/react-admin/Inputs.html#linking-two-inputs
Dynamic
You need to use the useForm hook of react-final-form to make your input dynamic:
import { useForm, useFormState } from 'react-final-form';
...
const {change} = useForm();
const { values: { my_first_input }} = useFormState({ subscription: { values: true } });
useEffect(() => {
change('my_second_input', my_first_input / 100);
}, [change, my_first_input]);
...
<NumberInput defaultValue={my_first_input / 100} source="second_input"/>
I got a shorter solution to this question:
All I did was to do the calculation within FormDataConsumer. Now, I am able to get the calculated value and it updates the correct record in the array.
Thanks
<FormDataConsumer>
{({
formData, // The whole form data
scopedFormData, // The data for this item of the ArrayInput
getSource, // A function to get the valid source inside an ArrayInput
...rest
}) => {
if (typeof scopedFormData !== 'undefined') {
scopedFormData.total = scopedFormData.quantity * scopedFormData.unitprice;
return (
<NumberInput disabled defaultValue={scopedFormData.total} label="Total" source={getSource('total')} />
)
} else {
return(
<NumberInput disabled label="Total" source={getSource('total')} />
)
}
}}

How to use the $filter variable on graphql query under the Connect component?

I have a simple query auto-generated from aws AppSync, and I'm trying to use the Connect Component, with a FlatList and use a TextInput to filter and auto-update the list. But I confess I didn't found out a way to do that... any hints?
Tried to find more information about this without success...
Auto-Generated query:
export const listFood = `query ListFood(
$filter: ModelFoodFilterInput
$limit: Int
$nextToken: String
) {
listFood(filter: $filter, limit: $limit, nextToken: $nextToken) {
items {
id
name
description
...
My current code, which I don't quite know where to place my filter value:
<Connect query={graphqlOperation(queries.listFood)}>
{
( { data: { listFood }, loading, error } ) => {
if(error) return (<Text>Error</Text>);
if(loading || !listFood) return (<ActivityIndicator />);
return (
<FlatList
data={listFood.items}
renderItem={({item}) => {
return (
<View style={styles.hcontainer}>
<Image source={{uri:this.state.logoURL}}
style={styles.iconImage}
/>
<View style={styles.vcontainer}>
<Text style={styles.textH3}>{item.name}</Text>
<Text style={styles.textP}>{item.description}</Text>
</View>
</View>
);
}}
keyExtractor={(item, index) => item.id}
/>
);
}
}
</Connect>
What I aim is mainly to filter by item.name, refreshing the list while typing from a TextInput, probably going somewhere on the $filter variable...
Ok, I think I've figured out the usage with the AWS AppSync Out-of-the-box queries...
query MyFoodList{
listFood(
filter: {
name: {
contains:"a"
}
}
) {
items {
id
name
}
}
}
And it is finally working properly with this disposition on my react-native code:
<Connect query={ this.state.filter!=="" ?
graphqlOperation(queries.listFood, {
filter: {
name: {
contains: this.state.filter
}
}
})
:
graphqlOperation(queries.listFood)
}>
I still didn't manage to make the sort key work yet... will try a little more and open another topic for it if I didn't get anything...
This is filter in use in React / Javascript:
const [findPage, setFindPage] = useState('') // setup
async function findpoints() {
// find user & page if exists read record
try {
const todoData = await API.graphql(graphqlOperation(listActivitys, {filter : {owner: {eq: props.user}, page: {eq: action}}}))
const pageFound = todoData.data.listActivitys.items // get the data
console.log('pageFound 1', pageFound)
setFindPage(pageFound) // set to State
} catch (err) {
console.log(err)
}
}
The async / wait approach means the code will try to operate, and move on to other areas of your code putting data into findPage through setFindPage when and if it finds data

React native navigator passing parent component to child

I don't know how to pass a reference to the TripList instance below to the AddTrip component. I need to do something like that to signal to TripList to refresh the data after adding a new trip.
In my render() method, inside <Navigator> I have:
if (route.index === 1) {
return <TripList
title={route.title}
onForward={ () => {
navigator.push({
title: 'Add New Trip',
index: 2,
});
}}
onBack={() => {
if (route.index > 0) {
navigator.pop();
}
}}
/>
} else {
return <AddTrip
styles={tripStyles}
title={route.title}
onBack={() => { navigator.pop(); }}
/>
}
However, when I call onBack() in AddTrip, after adding a trip, I want to call refresh() on TripList so the new trip is displayed. How best can I structure things to do that? I'm guessing I need to pass TripList somehow to AddTrip and then I can call refresh() there easily right before calling onBack().
This is not how React works. You don't pass instances of a component around, rather you pass the data to your component via props. And your component AddTrip should receive another props which is a function to call when adding a trip.
Let me illustrate this with a code example, this is not how your code should be in the end, but it'll illustrate how to contain the data outside of your components.
// Placed at the top of the file, not in a class or function.
let allTrips = [];
// Your navigator code.
if (route.index === 1) {
return <TripList
trips={allTrips}
title={route.title}
onForward={ () => {
navigator.push({
title: 'Add New Trip',
index: 2,
});
}}
onBack={() => {
if (route.index > 0) {
navigator.pop();
}
}} />
} else {
return <AddTrip
styles={tripStyles}
title={route.title}
onAdd={(tripData) => {
allTrips = [...allTrips, tripData];
}}
onBack={() => { navigator.pop(); }} />
}
As you can see, the logic about adding and finding the trips comes from the parent component, which is the navigator in this case. You will also note that we are reconstructing the content of allTrips, this is important as React is based on the concept of immutability.
You must have heard of Redux which is a system allowing all your components to discuss with a global store from which you fetch and save all your application state. It's a bit more complex that's why I did not use it as an example it.
I'll almost forget the most important! You will not need to signal to to your component that it needs refreshing, the magic of React should take care of it by itself!