React: Set Value in a TextField - reactjs

Back again with another react question!
UserInfoStep.js (Child)
function UserInfoStep({ PUID, FirstName, handleChangeParent }) {
const { dispatch } = useContext(ContactFormContext);
return (
//some controls
<FormControl>
<Grid container spacing={3}>
<Grid item xs={12}>
<TextField required label="PUID"
style={{ width: '100%' }}
name="PUID"
onChange={e =>
dispatch({ type: "PUID", payload: e.target.value })
}
value={PUID} variant="outlined" />
</Grid>
</Grid>
<Grid item xs={12} md={6}>
<TextField
required
style={{ width: '100%' }}
label="First Name"
name="FirstName"
onChange={e =>
dispatch({ type: "FIRST_NAME_CHANGE", payload: e.target.value })
}
value={FirstName}
variant="outlined"
/>
</Grid>
</FormControl>
);
}
export default UserInfoStep;
ContactForm.js (Parent)
const initialState = {
PUID: "",
FirstName: "",
total: 0
//other params removed
};
function formReducer(state, action) {
switch (action.type) {
case "PUID":
fetch('https://api.npms.io/v2/search?q=react')
.then(result => result.json())
.then(data => {
console.log(data.total);
});
return { ...state, PUID: action.payload };
case "FIRST_NAME_CHANGE":
return { ...state, FirstName: action.payload };
default:
throw new Error();
}
}
function ContactForm() {
const { PUID, FirstName, total } = state;
const steps = [
<UserInfoStep handleChangeParent={handleChange} {...{ PUID, FirstName }} />,
];
return (
<ContactFormContext.Provider value={{ dispatch }}>
//some code removed
</ContactFormContext.Provider>
);
}
export default ContactForm;
I want to set the value returned from the API call (data.total), in the FirstName input box when PUID onChange triggers. The API call can also be moved to the Child (UserInfoStep) if needed.
Edit
I have now moved my API call to UserInfoStep.js
const onPUIDChanged = (event) => {
fetch('https://api.npms.io/v2/search?q=react')
.then(result => result.json())
.then(data => {
console.log(data.total);
});
dispatch({ type: "PUID", payload: event.target.value });
};
<Grid item xs={12}>
<TextField required label="PUID"
style={{width:'100%'}}
name="PUID"
onChange={onPUIDChanged}
value={PUID} variant="outlined"/>
</Grid>

Don't use API call in your reducer, the reducer is just a function that gets an action and returns the state.
The philosophy of using action - reducer - store in redux architecture is to separate the logic.
There are two options for you:
using a third library like redux-saga to control the async API calling and handle the side effects with the power of generators.
call the API in your component/page and then dispatch the proper action for success/failure cases.

Related

React REDUX is updating all state after the update action

I've been figuring out this bug since yesterday.
All of the states are working before the update action. I have console log all the states before the update action.
Then after creating a model, the update action is executed.
This is the result when I console log.
I wondered why dataGrid returns an error since I point to all the id in the DataGrid component.
Uncaught Error: MUI: The data grid component requires all rows to have a unique `id` property.
This is my code:
Models Reducer:
import * as actionTypes from 'constants/actionTypes';
export default (models = [], action) => {
switch (action.type) {
case actionTypes.FETCH_MODELS:
return action.payload.result;
case actionTypes.CREATE:
return [...models, action.payload.result];
case actionTypes.UPDATE:
return models.map((model) => (model.model_id === action.payload.result.model_id ? action.payload.result : model));
case actionTypes.DELETE:
return models.filter((model) => model.model_id !== action.payload);
default:
return models;
}
};
In my model component:
import * as actionTypes from 'constants/actionTypes';
export default (models = [], action) => {
switch (action.type) {
case actionTypes.FETCH_MODELS:
return action.payload.result;
case actionTypes.CREATE:
return [...models, action.payload.result];
case actionTypes.UPDATE:
return models.map((model) => (model.model_id === action.payload.result.model_id ? action.payload.result : model));
case actionTypes.DELETE:
return models.filter((model) => model.model_id !== action.payload);
default:
return models;
}
};
My ModelForm:
<Formik
enableReinitialize={true}
initialValues={modelData}
validationSchema={Yup.object().shape({
model_code: Yup.string(4).min(4, 'Minimum value is 4.').max(50, 'Maximum value is 4.').required('Model code is required'),
model_description: Yup.string().max(200, 'Maximum value is 200.'),
model_status: Yup.string().min(5).max(10, 'Maximum value is 10.')
})}
onSubmit={async (values, { setErrors, setStatus, setSubmitting }) => {
try {
if (scriptedRef.current) {
if (currentId === 0) {
// , name: user?.result?.name
dispatch(createModel({ ...values }, setFormVisible));
} else {
dispatch(updateModel(currentId, { ...values }, setFormVisible));
}
setStatus({ success: true });
setSubmitting(false);
}
} catch (err) {
console.error(err);
if (scriptedRef.current) {
setStatus({ success: false });
setErrors({ submit: err.message });
setSubmitting(false);
}
}
}}
>
{({ errors, handleBlur, handleChange, handleSubmit, isSubmitting, touched, resetForm, values }) => (
<form noValidate onSubmit={handleSubmit}>
<Grid container spacing={1}>
<Grid item lg={4} md={4} sm={12}>
<JTextField
label="Model"
name="model_code"
value={values.model_code}
onBlur={handleBlur}
onChange={handleChange}
touched={touched}
errors={errors}
/>
</Grid>
</Grid>
<Grid container spacing={1} sx={{ mt: 1 }}>
<Grid item lg={4} md={4} sm={12}>
<JTextField
label="Description"
name="model_description"
value={values.model_description}
onBlur={handleBlur}
onChange={handleChange}
touched={touched}
type="multiline"
rows={4}
errors={errors}
/>
</Grid>
</Grid>
{currentId ? (
<Grid container spacing={1} sx={{ mt: 1 }}>
<Grid item lg={4} md={4} sm={12}>
<JSelect
labelId="model_status"
id="model_status"
name="model_status"
value={values.model_status}
label="Status"
onBlur={handleBlur}
onChange={handleChange}
errors={errors}
>
<MenuItem value="ACTIVE">ACTIVE</MenuItem>
<MenuItem value="INACTIVE">INACTIVE</MenuItem>
</JSelect>
</Grid>
</Grid>
) : (
''
)}
<Box sx={{ mt: 2 }}>
<ButtonGroup variant="contained" aria-label="outlined button group">
<Button size="small" disabled={isSubmitting} type="submit">
Save
</Button>
<Button size="small" onClick={resetForm}>
Cancel
</Button>
{currentId ? (
<Button size="small" color="secondary" onClick={handleDelete}>
Delete
</Button>
) : (
''
)}
</ButtonGroup>
</Box>
</form>
)}
</Formik>
Why products, parts or other states are updating too? Since I only update the model create action?
Please check this out: https://www.awesomescreenshot.com/video/11412230?key=a0212021c59aa1097fa9d38917399fe3
I Hope someone could help me figure out this bug. This is only the problem else my CRUD template is good.
Update:
Found out that actions in redux should always be unique or else multiple reducers with the same action name will be triggered.
I have updated my action types to:
// AUTHENTICATION ACTIONS
export const AUTH = 'AUTH';
export const LOGOUT = 'LOGOUT';
// MODEL ACTIONS
export const FETCH_MODELS = 'FETCH_MODELS';
export const CREATE_MODEL = 'CREATE_MODEL';
export const UPDATE_MODEL = 'UPDATE_MODEL';
export const DELETE_MODEL = 'DELETE_MODEL';
// PRODUCTS ACTIONS
export const FETCH_PRODUCTS = 'FETCH_PRODUCTS';
export const CREATE_PRODUCT = 'CREATE_PRODUCT';
export const UPDATE_PRODUCT = 'UPDATE_PRODUCT';
export const DELETE_PRODUCT = 'DELETE_PRODUCT';
// ASSEMBLY ACTIONS
export const FETCH_ASSEMBLY = 'FETCH_ASSEMBLY';
export const CREATE_ASSEMBLY = 'CREATE_ASSEMBLY';
export const UPDATE_ASSEMBLY = 'UPDATE_ASSEMBLY';
export const DELETE_ASSEMBLY = 'DELETE_ASSEMBLY';
// PARTS ACTIONS
export const FETCH_PARTS = 'FETCH_PARTS';
export const CREATE_PART = 'CREATE_PART';
export const UPDATE_PART = 'UPDATE_PART';
export const DELETE_PART = 'DELETE_PART';
Reducers to:
import * as actionTypes from 'constants/actionTypes';
export default (models = [], action) => {
switch (action.type) {
case actionTypes.FETCH_MODELS:
return action.payload.result;
case actionTypes.CREATE_MODEL:
return [...models, action.payload.result];
case actionTypes.UPDATE_MODEL:
console.log(models);
return models.map((model) => (model.model_id === action.payload.result.model_id ? action.payload.result : model));
case actionTypes.DELETE_MODEL:
return models.filter((model) => model.model_id !== action.payload);
default:
return models;
}
};
and Actions to:
import * as actionTypes from 'constants/actionTypes';
import * as api from 'api/index.js';
import Swal from 'sweetalert2';
export const getModels = () => async (dispatch) => {
try {
const { data } = await api.fetchModels();
dispatch({ type: actionTypes.FETCH_MODELS, payload: data });
} catch (error) {
console.log(error);
Swal.fire('Error!', 'Something went wrong', 'error');
}
};
export const createModel = (model, setFormVisible) => async (dispatch) => {
try {
const { data } = await api.createModel(model);
dispatch({ type: actionTypes.CREATE_MODEL, payload: data });
setFormVisible(false);
Swal.fire('Success!', 'Model has been added successfully', 'success');
} catch (error) {
console.log(error);
Swal.fire('Error!', 'Something went wrong', 'error');
}
};
export const updateModel = (id, model, setFormVisible) => async (dispatch) => {
try {
const { data } = await api.updateModel(id, model);
dispatch({ type: actionTypes.UPDATE_MODEL, payload: data });
setFormVisible(false);
Swal.fire('Success!', 'Model updated successfully', 'success');
} catch (error) {
console.log(error);
Swal.fire('Error!', 'Something went wrong', 'error');
}
};
export const deleteModel = (id) => async (dispatch) => {
try {
await await api.deleteModel(id);
dispatch({ type: actionTypes.DELETE_MODEL, payload: id });
Swal.fire('Success!', 'Model deleted successfully', 'success');
} catch (error) {
console.log(error);
Swal.fire('Error!', 'Something went wrong', 'error');
}
};

Child components not updating global state

I am developing a form with controlled components, Material-UI and, react hooks. All of the form data is saved in a global state via a useState setter function.
Additionally, some fields need to be toggled on and off depending on the user's response which implies that its local and global state has to be reset when toggled off.
That said when two or more components are toggled off at the same time one of them fails to update the global form state.
Here is my code:
App.js
imports...
function App () {
const [formState, setFormState] = useState({
fullName: '',
email: '',
ageLevel: '',
numTitle: '',
titleTypes: '',
materialType: '',
subjInterest: ''
})
const handleTxtFldChange = (e, name) => {
setFormState({ ...formState, [name]: e.target.value })
}
return (
<>
<div className='App'>
<form noValidate autoComplete='off'>
<TextField
required
value={formState.fullName}
onChange={e => handleTxtFldChange(e, 'fullName')}
label='fullName:'
/>
<AgeLevelSelect
formState={formState}
setFormState={setFormState}
/>
<NumOfTitles
formState={formState}
setFormState={setFormState}
ageLevel={formState.ageLevel}
/>
<MaterialType
formState={formState}
setFormState={setFormState}
ageLevel={formState.ageLevel}
/>
<SubjOfInterest
formState={formState}
setFormState={setFormState}
ageLevel={formState.ageLevel}
materialType={formState.materialType}
/>
<Button
onClick={() => { submitForm() }}
>
Submit
</Button>
</form>
</div>
</>
)
}
export default App
When Adult is selected from AgeLevelSelect, numTitle and materialType will be toggled on.
The data is saved in its local and global sate.
Component: AgeLevelSelect.js
imports...
const AgeLevelSelect = ({ formState, setFormState }) => {
const [age, setAge] = useState('')
const handleChange = (event) => {
setAge(event.target.value)
setFormState({ ...formState, ageLevel: event.target.value })
}
return (
<FormControl>
<InputLabel>Age level?</InputLabel>
<Select
value={age}
onChange={handleChange}
>
<MenuItem value='School-Age'>School-Age</MenuItem>
<MenuItem value='Teens'>Teens</MenuItem>
<MenuItem value='Adults'>Adults</MenuItem>
</Select>
</FormControl>
)
}
export default AgeLevelSelect
Here we select two from the select options. The data is saved in its local and global sate.
Component: NumOfTitles.js
imports...
const NumTitles = ({ formState, setFormState, ageLevel }) => {
const [titles, setTitles] = useState('')
const [isVisible, setIsVisible] = useState('')
const handleChange = (event) => {
setTitles(event.target.value)
setFormState({ ...formState, numTitle: event.target.value })
}
useEffect(() => {
if (ageLevel === 'Adults') {
setIsVisible(true)
} else {
setValue('')
setIsVisible(false)
setFormState(prevState => {
return { ...formState, materialType: '' }
})
}
}, [ageLevel])
useEffect(() => {
if (ageLevel !== 'Adults') {
setFormState(prevState => {
return { ...formState, materialType: '' }
})
setValue('')
setIsVisible(false)
}
}, [value])
return (
isVisible &&
<FormControl>
<InputLabel id='demo-simple-select-label'>Number of titles:</InputLabel>
<Select
value={titles}
onChange={handleChange}
>
<MenuItem value='One'>One</MenuItem>
<MenuItem value='Two'>Two</MenuItem>
</Select>
</FormControl>
)
}
export default NumTitles
If you made it this far THANK YOU. We are almost done.
Here we select Non-fiction. Data gets save in local and global state.
Additionally, the subject of interest question is toggled on.
Component: MaterialType.js
imports...
const TypeOfMaterial = ({ formState, setFormState, ageLevel }) => {
const [value, setValue] = useState('')
const [isVisible, setIsVisible] = useState('')
const handleChange = (event) => {
setValue(event.target.value)
setFormState({ ...formState, materialType: event.target.value })
}
useEffect(() => {
if (ageLevel === 'Adults') {
setIsVisible(true)
} else {
setValue('')
setIsVisible(false)
setFormState(prevState => {
return { ...formState, materialType: '' }
})
}
}, [ageLevel])
useEffect(() => {
if (!isVisible) {
setFormState(prevState => {
return { ...formState, materialType: '' }
})
setValue('')
setIsVisible(false)
}
}, [isVisible])
return (
isVisible &&
<FormControl component='fieldset'>
<FormLabel component='legend'>Select type of material:</FormLabel>
<RadioGroup name='MaterialTypes' value={value} onChange={handleChange}>
<FormControlLabel
value='Mystery'
control={<Radio />}
label='Mystery'
/>
<FormControlLabel
value='NonFiction'
control={<Radio />}
label='Non-fiction'
/>
</RadioGroup>
</FormControl>
)
}
export default TypeOfMaterial
Finally, we write World War II, in the text field. The data is saved in its local and global sate.
Component: SubjOfInterest.js
imports...
import React, { useState, useEffect } from 'react'
import TextField from '#material-ui/core/TextField'
const SubjOfInterest = ({ formState, setFormState, ageLevel, materialType }) => {
const [textField, setTextField] = useState('')
const [isVisible, setIsVisible] = useState('')
const handleTxtFldChange = (e) => {
setTextField(e.target.value)
setFormState({ ...formState, subjInterest: e.target.value })
}
useEffect(() => {
if (formState.materialType === 'NonFiction') {
setIsVisible(true)
} else {
setIsVisible(false)
}
}, [materialType])
useEffect(() => {
if (formState.materialType !== 'NonFiction') {
setTextField('')
}
}, [ageLevel])
return (
isVisible &&
<TextField
value={textField}
onChange={e => handleTxtFldChange(e)}
label='Indicate subjects of interest:'
/>
)
}
export default SubjOfInterest
At this point the global state looks as follow:
{
fullName:"Jhon Doe",
ageLevel:"Adults",
numTitle:"Two",
materialType:"NonFiction",
subjInterest:"World War"
}
Then if a user changes the selected option (Adults) from the AgeLeveleSelect to a different option (teens for example) the a part of global state (numTitle, materialType, subjInterest) is expected to be cleared, instead I get this:
{
fullName:"Jhon Doe",
ageLevel:"Teens",
numTitle:"Two",
materialType:"",
subjInterest:"World War"
}
Any ideas?
I have tried many things without results.
If anyone can help will be greatly appreciated!!!
You are only clearing the materialType field:
On the NumOfTitles.js file you are setting the field "materialType" instead of the "numOfTitles" field.
On the SubjOfInterest.js you are not clearing any fields.
My suggestion is that you check the "Adults" condition on the parent component. This way you will not update the same state three times (if this occurs at the same time, this can cause some problems).
You can try doing this way:
App.js
function App () {
const [formState, setFormState] = useState({
fullName: '',
email: '',
ageLevel: '',
numTitle: '',
titleTypes: '',
materialType: '',
subjInterest: ''
})
useEffect(() => {
if(formState.ageLevel !== 'Adults') {
setFormState({
...formState,
numTitle: '',
materialType: '',
subjInterest: '',
})
}
}, [formState.ageLevel]);
// ...

Unexpected React countdown component re-render, when input changes

I have created a custom countdown component using react-countdown package.
It work fines in general. But when I type inside a text input in my page, it will render again somehow and resets to its initial time. I have checked the onChange event of the input but it has nothing to do with the countdown. I'm really confused why this happens.
My idea in creating the countdown was that, if I change the key prop of countdown component, I will have a fresh countdown. Because as I know if we change the key prop in react components they will re-render.
Countdown component:
const AgapeCountdown = ({ duration, children, restartKey, ...props }) => {
const classes = useStyles();
const defaultRenderer = ({ hours, minutes, seconds, completed }) => {
if (completed) {
return children;
}
return (
<span className={classes.root}>
{minutes}:{seconds}
</span>
);
};
return (
<Countdown
renderer={defaultRenderer}
date={Date.now() + duration}
key={restartKey}
{...props}
/>
);
};
Usage:
<AgapeCountdown duration={10000} restartKey={countdownKey}>
<AgapeButton onClick={handleResendOtpClick} className={classes.textButton}>
ارسال مجدد کد
</AgapeButton>
</AgapeCountdown>;
input element in the same page:
<AgapeTextField
placeholder="مثال: ۱۲۳۴۵"
variant="outlined"
fullWidth
onChange={handleOtpChange}
value={otp}
helperText={otpHelperText}
error={otpHelperText}
/>
input change handler:
const handleOtpChange = (event) => {
if (otpRegex.test(event.target.value)) {
setOtpHelperText(null);
setDisableOtpAction(false);
setOtp(event.target.value).then(() => {
nextButtonClicked();
});
} else {
setOtp(event.target.value);
setOtpHelperText(helperInvalidOtp);
setDisableOtpAction(true);
}
};
where countdownKey get updated:
const handleResendOtpClick = () => {
setCountdownKey(countdownKey + 1);
console.log('hello from resendotpclick');
registerApiService({
mobile: phoneNumberPure,
})
.then((response) => {
if (response.status === 200) {
// TODO show user that otp code resent.
}
})
.catch((error) => {
// TODO show user that otp code resend failed.
});
};
full code for deeper inspection:
const LoginStep2 = ({ dialogHandler, ...props }) => {
const classes = useStyles(props);
const setIsLoginOpen = dialogHandler;
const dispatch = useDispatch();
const phoneNumberPure = useSelector(selectPhone);
const ELogin = useSelector(selectELogin);
const [otp, setOtp] = useStateWithPromise(null);
const [otpHelperText, setOtpHelperText] = React.useState(null);
const [disableOtpAction, setDisableOtpAction] = React.useState(true);
const [phoneNumber, setPhoneNumber] = React.useState('');
const [countdownKey, setCountdownKey] = React.useState(1);
React.useEffect(() => {
if (phoneNumberPure) {
setPhoneNumber(phoneNumberPure.split('-')[1]);
}
}, [phoneNumberPure]);
const handlePrevIconClicked = () => {
if (ELogin) {
dispatch(next());
return;
}
dispatch(prev());
};
const nextButtonClicked = () => {
setDisableOtpAction(true);
const convertedOtp = convertPersianDigitsToEnglish(otp);
loginApiService({ mobile: phoneNumberPure, otp: convertedOtp })
.then((response) => {
if (response.status === 200) {
if (response.data.access_token) {
const jsonUser = {
phone: phoneNumberPure,
token: response.data.access_token,
social: null,
email: null,
};
localStorage.setItem('user', JSON.stringify(jsonUser));
if (ELogin) {
setIsLoginOpen(false);
return;
}
dispatch(next());
}
} else if (response.status === 404) {
setOtpHelperText(helperWrongOtp);
}
})
.catch((error) => {
setOtpHelperText(helperWrongOtp);
})
.finally(() => {
setTimeout(() => {
setDisableOtpAction(false);
}, 1000);
});
};
const handleResendOtpClick = () => {
setCountdownKey(countdownKey + 1);
console.log('hello from resendotpclick');
registerApiService({
mobile: phoneNumberPure,
})
.then((response) => {
if (response.status === 200) {
// TODO show user that otp code resent.
}
})
.catch((error) => {
// TODO show user that otp code resend failed.
});
};
const handleOtpChange = (event) => {
if (otpRegex.test(event.target.value)) {
setOtpHelperText(null);
setDisableOtpAction(false);
setOtp(event.target.value).then(() => {
nextButtonClicked();
});
} else {
setOtp(event.target.value);
setOtpHelperText(helperInvalidOtp);
setDisableOtpAction(true);
}
};
return (
<Grid container>
<Grid item xs={12}>
<IconButton onClick={handlePrevIconClicked}>
<BsArrowRight className={classes.arrowIcon} />
</IconButton>
</Grid>
<Grid
item
container
xs={12}
justify="center"
className={classes.logoContainer}
>
<img src={AgapeLogo} alt="لوگوی آگاپه" />
</Grid>
<Grid
item
container
xs={12}
justify="center"
className={classes.loginTitle}
>
<Typography variant="h4">کد تایید را وارد نمایید</Typography>
</Grid>
<Grid item xs={12}>
<Typography variant="body1" className={classes.noMargin}>
کد تایید به شماره
<span className={classes.phoneNumberContainer}>{phoneNumber}</span>
ارسال گردید
</Typography>
</Grid>
<Grid
item
container
xs={12}
justify="space-between"
className={classes.loginInputs}
>
<Grid item xs={12}>
<AgapeTextField
placeholder="مثال: ۱۲۳۴۵"
variant="outlined"
fullWidth
onChange={handleOtpChange}
value={otp}
helperText={otpHelperText}
error={otpHelperText}
/>
</Grid>
</Grid>
<Grid item xs={12}>
<AgapeButton
color="primary"
disabled={disableOtpAction}
onClick={nextButtonClicked}
fullWidth
>
تایید
</AgapeButton>
</Grid>
<Grid
item
container
xs={12}
justify="space-between"
className={classes.textButtonsContainer}
>
<Grid item xs={4}>
<AgapeCountdown duration={10000} restartKey={countdownKey}>
<AgapeButton
onClick={handleResendOtpClick}
className={classes.textButton}
>
ارسال مجدد کد
</AgapeButton>
</AgapeCountdown>
</Grid>
<Grid item xs={4} className={classes.callButton}>
<AgapeButton className={classes.textButton}>
دریافت از طریق تماس
</AgapeButton>
</Grid>
</Grid>
</Grid>
);
};
I found the problem. this is the part actually creates the problem:
<Countdown
renderer={defaultRenderer}
date={Date.now() + duration}
key={restartKey}
{...props}
/>
the Date.now() will update. And it makes the countdown to restart. for solving this problem I used a ref which stop the component to re-render if it changes:
const AgapeCountdown = ({ duration, children, restartKey, ...props }) => {
const classes = useStyles();
const startDate = React.useRef(Date.now());
const defaultRenderer = ({ hours, minutes, seconds, completed }) => {
return (
<span className={classes.root}>
{minutes}:{seconds}
</span>
);
};
return (
<Countdown
renderer={defaultRenderer}
date={startDate.current + duration}
key={restartKey}
{...props}
/>
);
};

React-Admin: How to send input values that have been auto filled from an API call?

I have an input 'A' that fetches address data from an API and auto fills inputs 'B' 'C' and 'D' based on that data, but after the inputs have been filled and I try to send that form to my backend, none of those auto filled inputs are sent, just the input 'A' is sent. Furthermore, if i manually edit any of the inputs (remove a char, add a space, change the value) the ones that I edited get sent to my backend.
I'm using a reducer to store the state. The inputs that I'm using are all just normal react-admin TextInput components.
Here's the code:
const AutoFill = () => {
const [searching, setSearching] = useState(false);
const [error, setError] = useState(false);
const [stateData, setStateData] = useReducer(
(state, newState) => ({ ...state, ...newState }),
{
cep: ' - ',
address: '',
number: '',
neighborhood: '',
city: '',
state: '',
}
);
const FormControl = (event) => {
const { name, value } = event.target;
setStateData({ [name]: value });
};
const SearchControl = (event) => {
const { name, value } = event.target;
setStateData({ [name]: value });
if (value && !value.includes('_')) {
setSearching(true);
setStateData({ state: '...' });
setStateData({ city: '...' });
setStateData({ neighborhood: '...' });
setStateData({ address: '...' });
cep(value.replace('-', '')).then(
(result) => {
setSearching(false);
setError(false);
setStateData({ state: result.state });
setStateData({ city: result.city });
setStateData({ neighborhood: result.neighborhood });
setStateData({ address: result.street });
},
() => {
setSearching(false);
setError(true);
setStateData({ state: '' });
setStateData({ city: '' });
setStateData({ neighborhood: '' });
setStateData({ address: '' });
}
);
}
};
return (
<>
<TextInput
source="cep"
error={error}
value={stateData.cep}
onChange={SearchControl}
/>
<TextInput
source="address"
disabled={searching}
value={stateData.address}
onChange={FormControl}
/>
<TextInput
source="number"
disabled={searching}
value={stateData.number}
onChange={FormControl}
/>
<TextInput
source="neighborhood"
disabled={searching}
value={stateData.neighborhood}
onChange={FormControl}
/>
<TextInput
source="state"
disabled={searching}
value={stateData.state}
onChange={FormControl}
/>
<TextInput
source="city"
disabled={searching}
value={stateData.city}
onChange={FormControl}
/>
</>
);
};
export const Create = (props) => {
return (
<Create {...props}>
<SimpleForm>
<NumberInput label="Value" source="price" />
<AutoFill />
<RichTextInput label="Description" source="description" />
</SimpleForm>
</Create>
);
};
You're going to need to use React Final Form's FormState and Form solutions. Will use snippets of my code for example.
1) Grab the form values
const formState = useFormState();
const form = useForm();
const {
asset_system_parent_id: majorSystem,
classification,
} = formState.values;
2) Setup useEffect that will observe changes to a form field:
useEffect(() => {
const setFluidEnd = async () => {
DO SOMETHING!!!!!
};
if ('Fluid End Maintenance' === classification) {
setFluidEnd();
}
}, [classification, form, notify]);
3) Use form.change (+ form.batch if you need to update multiple inputs)
useEffect(() => {
const setFluidEnd = async () => {
await requestGetList('asset-systems', 'id', 'ASC', 500, {
description: 'Fluid End',
relationship: 'parent',
})
.then(res => {
form.change('asset_system_parent_id', res.data[0].id);
})
.catch(error => {
notify(`System Assets not found`, 'warning');
});
};
if ('Fluid End Maintenance' === classification) {
setFluidEnd();
}
}, [classification, form, notify]);
You can read more about the api here: https://final-form.org/docs/final-form/types/FormApi
Please use this code.
-index.js file
import axios from "axios";
export const setInputValue = (data) => {
return axios.get(`https://www.example.com/profile`)
.then((response) => {
return response.data;
});
};
-component.js
return setInputValue(value).then(() => {
this.setState(() => ({
loading: false
}));
});
...
render(){
return (
...
<input type="text" onClick={e => this.onClick(e)} value={this.state.value}/>
..
)}
...
react-admin.php
...
public function setInputValue(value)
{
try {
$user->set(value);
return response()->json(["result" => "successfully!"]);
} catch (\Exception $e) {
return getErrorResponse($e);
}
}

Interaction with Apollo GraphQL Store not Working

I'm Trying to Learn GraphQL by Developing a Simple To-do List App Using React for the FrontEnd with Material-UI. I Need to Now Update the Information on the Web App in Real-time After the Query Gets Executed. I've Written the Code to Update the Store, But for Some Reason it Doesn't Work. This is the Code for App.js.
const TodosQuery = gql`{
todos {
id
text
complete
}
}`;
const UpdateMutation = gql`mutation($id: ID!, $complete: Boolean!) {
updateTodo(id: $id, complete: $complete)
}`;
const RemoveMutation = gql`mutation($id: ID!) {
removeTodo(id: $id)
}`;
const CreateMutation = gql`mutation($text: String!) {
createTodo(text: $text) {
id
text
complete
}
}`;
class App extends Component {
updateTodo = async todo => {
await this.props.updateTodo({
variables: {
id: todo.id,
complete: !todo.complete,
},
update: (store) => {
const data = store.readQuery({ query: TodosQuery });
data.todos = data.todos.map(existingTodo => existingTodo.id === todo.id ? {
...todo,
complete: !todo.complete,
} : existingTodo);
store.writeQuery({ query: TodosQuery, data })
}
});
};
removeTodo = async todo => {
await this.props.removeTodo({
variables: {
id: todo.id,
},
update: (store) => {
const data = store.readQuery({ query: TodosQuery });
data.todos = data.todos.filter(existingTodo => existingTodo.id !== todo.id);
store.writeQuery({ query: TodosQuery, data })
}
});
};
createTodo = async (text) => {
await this.props.createTodo({
variables: {
text,
},
update: (store, { data: { createTodo } }) => {
const data = store.readQuery({ query: TodosQuery });
data.todos.unshift(createTodo);
store.writeQuery({ query: TodosQuery, data })
},
});
}
render() {
const { data: { loading, error, todos } } = this.props;
if(loading) return <p>Loading...</p>;
if(error) return <p>Error...</p>;
return(
<div style={{ display: 'flex' }}>
<div style={{ margin: 'auto', width: 400 }}>
<Paper elevation={3}>
<Form submit={this.createTodo} />
<List>
{todos.map(todo =>
<ListItem key={todo.id} role={undefined} dense button onClick={() => this.updateTodo(todo)}>
<ListItemIcon>
<Checkbox checked={todo.complete} tabIndex={-1} disableRipple />
</ListItemIcon>
<ListItemText primary={todo.text} />
<ListItemSecondaryAction>
<IconButton onClick={() => this.removeTodo(todo)}>
<CloseIcon />
</IconButton>
</ListItemSecondaryAction>
</ListItem>
)}
</List>
</Paper>
</div>
</div>
);
}
}
export default compose(
graphql(CreateMutation, { name: 'createTodo' }),
graphql(UpdateMutation, { name: 'updateTodo' }),
graphql(RemoveMutation, { name: 'removeTodo' }),
graphql(TodosQuery)
)(App);
Also, i Want to Create Some List Items but that Doesn't Work Either. I'm Trying to get the Text Entered in the Input Field in Real-time Using a Handler Function handleOnKeyDown() in onKeyDown of the Input Field. I Pass in a event e as a Parameter to handleOnKeyDown(e) and when i console.log(e) it, instead of logging the Text Entered, it Returns a Weird Object that i Do Not Need. This is the Code that Handles Form Actions:
export default class Form extends React.Component{
state = {
text: '',
}
handleChange = (e) => {
const newText = e.target.value;
this.setState({
text: newText,
});
};
handleKeyDown = (e) => {
console.log(e);
if(e.key === 'enter') {
this.props.submit(this.state.text);
this.setState({ text: '' });
}
};
render() {
const { text } = this.state;
return (<TextField onChange={this.handleChange} onKeyDown={this.handleKeyDown} label="To-Do" margin='normal' value={text} fullWidth />);
}
}
This above Code File Gets Included in my App.js.
I Cannot Figure out the Issues. Please Help.
I was stuck with a similar problem. What resolved it for me was replacing the update with refetchQueries as:
updateTodo = async todo => {
await this.props.updateTodo({
variables: {
id: todo.id,
complete: !todo.complete
},
refetchQueries: [{
query: TodosQuery,
variables: {
id: todo.id,
complete: !todo.complete
}
}]
});
};
For your second problem, try capitalizing the 'e' in 'enter' as 'Enter'.
Hope this helps!

Resources