Display Redux-Form validation error for embedded component - redux

I have embedded a React component inside of another where I have applied validation on the parent:
const EmailAddressInput = (props) => {
const { emailList, onKeyUp } = props;
return (
<div>
<textarea
{...emailList}
/>
</div>
);
};
It's placed inside another component like:
let Emailer = (props) => {
const { fields: { passType, invitees },
return (
<legend>Select pass type:</legend>
{ renderPassTypes(eventsState.selectedEvent.AssociatedPassTypes) }
{passType.touched && passType.error && <span className="error">{passType.error}</span>}
<EmailAddressInput { ...invitees } onKeyUp={ () => handleEmailToBarKeyUp(invitees.emailList.value) } />
{invitees.touched && invitees.error && <span className="error">{invitees.error}</span> }
)
}
Now, given I want to ensure the EmailAddressInput's emailList is not empty, I added a custom validation rule:
const emailValidator = createValidator({
invitees: [requiredProperty('emailList')],
passType: required,
});
My validation utility looks like:
export function required(value) {
if (isEmpty(value)) {
return 'Required';
}
}
export function requiredProperty(fieldName) {
return function (value) {
return required(value[fieldName]);
};
}
export function createValidator(rules) {
return (data = {}) => {
const errors = {};
Object.keys(rules).forEach((key) => {
const rule = join([].concat(rules[key])); // concat enables both functions and arrays of functions
const error = rule(data[key], data);
if (error) {
errors[key] = error;
}
});
return errors;
};
}
Now when I submit my form with the EmailAddressInput textarea empty, createValidator returns {invitees: 'Required'}. The form submission is halted as expected (hooray!) but the error message is lost.
Errors are added as an errors property of the Redux-Form field object, but invitees isn't a field object, so I guess for that reason the collection of errors isn't being attached.
The field is actually the emailList textarea in EmailAddressInput, but that isn't getting the errors collection attached as the relevant key in the errors collection is different (invitees vs emailList)
Any idea how I can get that error displayed?

The trick was to project the right structure from my validation rule functions:
export function requiredProperty(fieldName) {
return function (value) {
const error = required(value[fieldName]);
if (error) {
return { [fieldName]: error };
}
};
}

Related

How can I attach word/pdf files to React Native gifted chat?

I would like to be able to send word doc/pdf files via messaging in my react native app using react native gifted chat. I have had a look at a few links which suggests using the renderActions() function in react-native-gifted-chat but it does not specify how I can implement this. Do you know how I can implement this function? Would I need to import a package like document picker or file picker in the function? If so, how can I use this? I'm fairly new to react native. Can someone please help here?
Here is what I have so far in my renderActions() method:
renderActions() {
return(
<Actions
{...props}
options={{
['Document']: async (props) => {
try {
const result = await DocumentPicker.pick({
type: [DocumentPicker.types.doc || DocumentPicker.types.docx || DocumentPicker.types.pdf],
});
console.log("resulting file: "+result);
console.log("string result? "+JSON.stringify(result));
} catch(e){
if(DocumentPicker.isCancel(e)){
console.log("User cancelled!")
} else {
throw e;
}
}
},
['Cancel']: (props) => {console.log("cancel")}
}}
icon={() => (
<Ionicons
name={'add'}
size={28}
color={'#0077ff'}
style={{left:0, bottom:0}}
/>
)}
onSend={args => console.log(args)}
/>
)
}
Which produces:
I have managed to get the file object. Does anyone know how I can append this doc file object to the messages in gifted chat once selected? Can someone please help? How can I display in the chat box and then send the file?
Thanks.
The link https://github.com/FaridSafi/react-native-gifted-chat/issues/2111 mentions to to add parameters to the message object. For example you have this message object:
const newMessage = {
_id: data.send_at,
text: data.messagetext,
createdAt: data.send_at,
(...),
file_type: data?.file_type,
file_id: data?.file_id,
}
Then render a custom view:
const renderCustomView = (props) => {
if (props?.currentMessage?.file_type) {
(...)
}
else {
(...)
}
}
Can someone please help on where I would need to create the messages object as well as what I would need to put inside the renderCustomView function? I am really not too sure on what needs to be done.
function renderActions(props) {
let selectFile = async () => {
//Opening Document Picker to select one file
try {
const res = await DocumentPicker.pick({
//Provide which type of file you want user to pick
type: [DocumentPicker.types.pdf],
//There can me more options as well
// DocumentPicker.types.allFiles
// DocumentPicker.types.images
// DocumentPicker.types.plainText
// DocumentPicker.types.audio
// DocumentPicker.types.pdf
});
//Printing the log realted to the file
console.log('res : ' + JSON.stringify(res));
props.onSend({pdf:res.uri,file_type:'pdf'});
//Setting the state to show single file attributes
singleFile = res;
// setSingleFile(res);
} catch (err) {
singleFile = null;
// setSingleFile(null);
//Handling any exception (If any)
if (DocumentPicker.isCancel(err)) {
//If user canceled the document selection
alert('Canceled from single doc picker');
} else {
//For Unknown Error
alert('Unknown Error: ' + JSON.stringify(err));
throw err;
}
}
};
const handlePicker = () => {
// console.log('edit');
ImagePicker.showImagePicker({}, (response) => {
// console.log('Response = ', response);
if (response.didCancel) {
console.log('User cancelled image picker');
} else if (response.error) {
console.log('ImagePicker Error: ', response.error);
} else if (response.customButton) {
console.log('User tapped custom button: ', response.customButton);
} else {
setAvatar({uri: response.uri});
console.log(response.uri);
props.onSend({image:response.uri});
// onSend([{"_id": "f3fda0e8-d860-46ef-ac72-0c02b8ea7ca9", "createdAt": new Date(), "image": response.uri, "user": {"_id": 1}}])
return response.uri
// here we can call a API to upload image on server
}
return avatar;
});
};
return (
<Actions
{...props}
options={{
['Send Image']: () => handlePicker(),
['Send Files']: () => selectFile(),
}}
icon={() => (
<Icon name='attachment' size={28} />
)}
// onSend={onSend}
/>
)}
in custom view :
export default class CustomView extends React.Component {
renderPdf() {
return (
<TouchableOpacity style=
{[styles.container,this.props.containerStyle]} >
<Image
{...this.props.imageProps}
style={[styles.image, this.props.imageStyle]}
source ={{
uri:""
}}
/>
</TouchableOpacity>
);
}
render() {
if (this.props.currentMessage.file_type == 'pdf') {
return this.renderPdf();
} else if (this.props.currentMessage.template &&
this.props.currentMessage.template != 'none') {
return this.renderHtml();
}
return null;
}
}

wait until first hook is complete before fetching data

I have this custom hook which fetches the query.me data from graphql. The console.log statement shows that this hook is running a number of times on page load, but only 1 of those console.logs() contains actual data.
import { useCustomQuery } from '../api-client';
export const useMe = () => {
const { data, isLoading, error } = useCustomQuery({
query: async (query) => {
return getFields(query.me, 'account_id', 'role', 'profile_id');
},
});
console.log(data ? data.account_id : 'empty');
return { isLoading, error, me: data };
};
I then have this other hook which is supposed to use the id's from the above hook to fetch more data from the server.
export const useActivityList = () => {
const { me, error } = useMe();
const criteria = { assignment: { uuid: { _eq: me.profile_id } } } as appointment_bool_exp;
const query = useQuery({
prepare({ prepass, query }) {
prepass(
query.appointment({ where: criteria }),
'scheduled_at',
'first_name',
'last_name',
);
},
suspense: true,
});
const activityList = query.appointment({ where: criteria });
return {
activityList,
isLoading: query.$state.isLoading,
};
};
The problem I am facing is that the second hook seems to call the first hook when me is still undefined, thus erroring out. How do I configure this, so that I only access the me when the values are populated?
I am bad with async stuff...
In the second hook do an early return if the required data is not available.
export const useActivityList = () => {
const { me, error } = useMe();
if (!me) {
return null;
// or another pattern that you may find useful is to set a flag to indicate that this query is idle e.g.
// idle = true;
}
const criteria = { assignment: { uuid: { _eq: me.profile_id } } } as appointment_bool_exp;
...

How to access Apollo useMutaion response

When I do a mutation with useMutation hook, I can console.log my data but I cannot save the data object in a localState. I don't know what am I missing. Any help is appreciated.
My code is as follows:
//Schema file
startTest(identifier: String): TestResponse
the mutation payload is as follows:
type TestResponse {
errorCode: Int
errorMessage: String
success: Boolean
transactionId: ID
version: Int
}
In the resolver I hardcoded the following data return:
startTest(_, { identifier }) {
console.log("hello from server");
return {
errorCode: 0,
errorMessage: null,
success: true,
transactionId: "d2984911-bbc4-4e6a-9103-96ca934f6ed3",
version: 0,
};
},
In component folder I've triggered the mutation with a button click.
const [startTestt, { loading, error, data }] = useMutation(
START_FIRE_DAMPER_TEST,
{
onCompleted: ({ startFireDampersTest }) =>
console.log(startFireDampersTest.success),
useState(startFireDampersTest.success)
},
{
onError: () => console.log("error!"), // never gets called
}
);
console.log(result); // undefined
if (loading) return <p>Loading...</p>;
if (error) return <p>Error!</p>;
if (data && data.startFireDampersTest)
return (
<div>
<p>Success!</p>
<pre>{JSON.stringify(data, null, 2)}</pre> // I can see string object but how to render the element of the object inside the component??
</div>
);
const triggerTest = () => {
startTest({ variables: { identifier: "0000000" } });
};
return (
<>
<Banner />
<button onClick={triggerTest}>Click to Delete Buck</button>
</>
);
I need to access returned data returned from mutation for later use. the test response/payload is not related to any other queries or types to update. It just contains status of the test and other key values that I need to save in a local state or maybe render it inside a component.
You already have a useState inside your onCompleted, you'd just need to write properly. Assuming the useState is a call to the a React's useState hook, then you're probably just need to move the initialization of your state to your component.
const [isSuccess, setIsSuccess] = useState(false);
const [startTestt, { loading, error, data }] = useMutation(
START_FIRE_DAMPER_TEST,
{
onCompleted: ({ startFireDampersTest }) =>
console.log(startFireDampersTest.success),
setIsSuccess(startFireDampersTest.success)
},
{
onError: () => console.log("error!"), // never gets called
}
);

Dispatching actions in Redux while using Apollo Client causes Apollo client to reset query

I have a Component that is a TypeAhead. When the user enters the component page Apollo pulls an initial query of 5 players that is used for the typeahead. Ideally i would like to skip this initial query but thats another thing entirely. So the query is filled with 5 players. Player1 to Player5, When i start typing in the typeahead searching for Player10, I select Player10 and it dispatches an action to make it the currently selected Player. However after I trigger an onBlur or leave the box, Apollo dispatches a Redux action of APOLLO_QUERY_RESULT_CLIENT which sets all the typeAhead back to Player1 to Player5 my initial query instead of having it set correctly to Player10. How do you prevent that APOLLO_QUERY_RESULT_CLIENT from dispatching as it dispatches anytime i dispatch an action that i created myself.
class TypeAhead extends Component {
constructor() {
super();
this.state = {
value: ''
};
}
renderInputComponent = (inputProps) => {
let {selectedSuggestion} = this.props;
return (
<div className="inputContainer">
<img className="type-ahead__image" alt="" src={getImageURL(selectedSuggestion.id)} />
<TextField floatingLabelText="Search Player" {...inputProps} />
</div>
)
}
onChange = (event, { newValue }) => {
this.setState({
value: newValue
});
};
shouldRenderSuggestions(value) {
return value.trim().length > MIN_SEARCH_LENGTH;
}
onSuggestionsFetchRequested = ({ value }) => {
// debugger;
if(/^[a-z .,-]+$/i.test(value)) {
this.props.data.refetch({name: value});
}
};
onSuggestionsClearRequested = () => {
// debugger;
// this.setState({
// suggestions: []
// });
// this.props.data.Players = [];
};
onBlur = () => {
if (this.state.value.toLowerCase() === this.props.data.Players[0].name.toLowerCase()) {
let suggestion = this.props.data.Players[0];
this.props.onSuggestionSelected(null, { suggestion });
}
}
render() {
console.log(this.props.data.Players)
let suggestions = this.props.data.Players || [];
let { onSuggestionSelected } = this.props;
let { value } = this.state;
let inputProps = {
value,
onChange: this.onChange,
onBlur: this.onBlur
};
return (
<div>
<Autosuggest
suggestions={suggestions}
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
getSuggestionValue={getSuggestionValue}
renderSuggestion={renderSuggestion}
shouldRenderSuggestions={this.shouldRenderSuggestions}
onSuggestionSelected={onSuggestionSelected}
renderInputComponent={this.renderInputComponent}
inputProps={inputProps}
/>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
selectedSuggestion: state.selectedSuggestion
}
}
const mapDispatchToProps = (dispatch) => {
return {
onSuggestionSelected(event, {suggestion}) {
dispatch(actions.selectSuggestion(suggestion));
// dispatching action causes apollo to requery and pull inital query causing issues.
},
onSuggestionUnselected() {
dispatch(actions.unselectSuggestion());
}
}
}
const TypeAheadWithData = graphql(TypeAheadQuery, {
options: ({ name }) => ({ variables: { name } })
})(TypeAhead);
const TypeAheadWithDataAndState = connect(mapStateToProps, mapDispatchToProps)(TypeAheadWithData);
export default TypeAheadWithDataAndState;
const TypeAheadWithData = graphql(TypeAheadQuery, {
options: ({ name }) => ({ variables: { name } })
})(TypeAhead);
Whenever the name prop changes, the query will be run again. It is very likely that you reset the name just before the query is run again.
If this is not the case, you can know why the graphql container is refetching by debugging the networkStatus. You also need to add options: { notifyOnNetworkStatusChange: true }

React props using Meteor Apollo

I am playing with the Meteor Apollo demo repo.
I am having difficulty passing variables down to children with React. I am getting an error
imports/ui/Container.jsx:10:6: Unexpected token (10:6)
The below code is the Container.jsx component:
import React from 'react';
import { Accounts } from 'meteor/std:accounts-ui';
class Container extends React.Component {
render() {
let userId = this.props.userId;
let currentUser = this.props.currentUser;
}
return (
<Accounts.ui.LoginForm />
{ userId ? (
<div>
<pre>{JSON.stringify(currentUser, null, 2)}</pre>
<button onClick={() => currentUser.refetch()}>Refetch!</button>
</div>
) : 'Please log in!' }
);
}
}
It is passed props via the Meteor Apollo data system (I have omitted some imports at the top):
const App = ({ userId, currentUser }) => {
return (
<div>
<Sidebar />
<Header />
<Container userId={userId} currentUser={currentUser} />
</div>
)
}
// This container brings in Apollo GraphQL data
const AppWithData = connect({
mapQueriesToProps({ ownProps }) {
if (ownProps.userId) {
return {
currentUser: {
query: `
query getUserData ($id: String!) {
user(id: $id) {
emails {
address
verified
}
randomString
}
}
`,
variables: {
id: ownProps.userId,
},
},
};
}
},
})(App);
// This container brings in Tracker-enabled Meteor data
const AppWithUserId = createContainer(() => {
return {
userId: Meteor.userId(),
};
}, AppWithData);
export default AppWithUserId;
I would really appreciate some pointers.
I believe the error is that you accidentally ended the render function before the return statement.
render() { // <- here it starts
let userId = this.props.userId;
let currentUser = this.props.currentUser;
} // <- here it ends
Another error is that your return statement doesn't return a single DOM element, but two of them: an Accounts.ui.LoginForm and a div. The return function should only return one element. Just put the entire thing into a single <div>.

Resources