I'm new to Redux work, trying to learn by doing. Here I have AntD input, when user writes something then it saves it to the object keys billingName: and billingContactPerson, but I have also two buttons sender and receiver, when user clicks sender button then it takes data from redux and put it to input, but my question is how to save that data to the same billingName and billingContactPerson. I have tried to save it in useEffect billingName = PickUpName, but it did not save it.
My code:
let billingName: any;
let billingContactPerson: any;
const userData = useSelector(selectUserData);
const dispatch = useDispatch();
const DeliveryName = userData.deliveryName;
const PickUpName = userData.pickUpName;
const DeliveryContactPerson = userData.deliveryContactPerson;
const PickUpContactPerson = userData.pickUpContactPerson;
const [name, setName] = useState(billingName);
const [contactPerson, setContactPerson] = useState(
billingContactPerson
);
const [payer, setPayer] = useState("");
useEffect(() => {
const names = () => {
if (payer === "receiver") {
billingName = DeliveryName;
dispatch(changeUserData({ ...userData, billingName }));
}
if (payer === "sender") {
billingName = PickUpName;
dispatch(changeUserData({ ...userData, billingName }));
} else {
return billingName;
}
};
setName(names);
const contactPersons = () => {
if (payer === "receiver") {
billingContactPerson = DeliveryContactPerson;
dispatch(changeUserData({ ...userData, billingContactPerson }));
}
if (payer === "sender") {
billingContactPerson = PickUpContactPerson;
dispatch(changeUserData({ ...userData, billingContactPerson }));
} else {
return billingContactPerson;
}
};
setContactPerson(contactPersons);
}, [payer]);
const senderPays = (e: any) => {
e.preventDefault();
setPayer("sender");
};
const receiverPays = (e: any) => {
e.preventDefault();
setPayer("receiver");
};
<div>
<Button onClick={senderPays}>sender</Button>
<Button onClick={receiverPays}>receiver</Button>
<Form.Item
label={t("o.billingName")}
name="billingName"
initialValue={userData["billingName"] || name || ""}
>
<Input
onChange={(e: any) =>
dispatch(
changeUserData({ ...userData, billingName: e.target.value })
)
}
type="string"
/>
</Form.Item>
<Form.Item
label={t("orders.ContactPerson")}
name="billingContactPerson"
initialValue={
userData["billingContactPerson"] ||
contactPerson ||
""
}
>
<Input
onChange={(e: any) =>
dispatch(
changeUserData({
...userData,
billingContactPerson: e.target.value,
})
)
}
type="string"
/>
</Form.Item>
</div>
If you're new I recommend you to start with reduxjs/toolkit. It is the new recommended way of writting redux logic.
Let's Learn modern redux
About the question you asked. You can try triggering an function after the redux logic or after sending the data to the input field and give type to billingName and billingContactPerson. So, that you can more catch errors.
Related
I have search component with validate js.
Problem: when my input in foucs first time, validate and request dont work, but when i lose focus my input, and click it again, and try again, search working without validation
interface IProps {
onSearchChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
}
const Search: React.FC<IProps> = ({ onSearchChange }) => {
const inputRef = useRef<HTMLInputElement>(null);
const [inputIsTouched, setInputIsTouched] = useState(false);
const currentValue = inputRef.current?.value && inputRef.current.value;
const validateErrors = validate({ currentValue }, constraints);
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
if (validateErrors?.currentValue) {
return;
}
currentValue && onSearchChange && onSearchChange(event);
setInputIsTouched(true);
};
const debouncedOnChange = debounce(handleChange, 1000);
return (
<div className={classes['Root']}>
<Input
type="text"
autoComplete="off"
placeholder="..."
onChange={debouncedOnChange}
ref={inputRef}
onBlur={() => setInputIsTouched(true)}
isError={inputIsTouched && !!validateErrors?.currentValue}
/>
<div className={classes['ErrorContainer']}>
{inputIsTouched && validateErrors?.currentValue && (
<Text color="error" size="s">
{validateErrors.currentValue}
</Text>
)}
</div>
</div>
);
};
That's expected because on first render, currentValue is undefined (as inputRef.current is null) and there's nothing calling handleChange to trigger the search.
You need to make sure the handleChange logic also runs on the initial render, so it should look something like this:
const Search: React.FC<IProps> = ({ onSearchChange }) => {
// Use a single object for all input state props:
const [{
isTouched,
validateErrors,
}, setInputState] = useState({
isTouched: false,
validateErrors: null,
});
const inputRef = useRef<HTMLInputElement>(null);
// Debounce only search callback:
const debouncedSearchChange = debounce(onSearchChange, 1000);
const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
// Get the current value:
const currentValue = e.currentTarget.value;
// Validate it:
const validateErrors = validate({ currentValue }, constraints);
if (validateErrors?.currentValue) {
// And handle error:
setInputState(prevState => ({ ...prevState, validateErrors }));
return;
}
// Or success:
setInputState(prevState => ({ ...prevState, validateErrors: null }));
// And trigger the debounced search if needed:
if (currentValue && debouncedSearchChange ) debouncedSearchChange(event);
}, [constraints, debouncedSearchChange]);
// Trigger validation and search on first render:
useEffect(() => {
const inputElement = inputRef.current;
// TypeScript will complain about this line, so you might want to
// re-structure the logic above to accommodate this:
if (inputElement) handleChange({ currentTarget: inputElement });
}, []);
return (
<div className={classes['Root']}>
<Input
type="text"
autoComplete="off"
placeholder="..."
onChange={handleChange}
ref={inputRef}
onBlur={() => setInputState(prevState => ({ ...prevState, isTouched: true }))}
isError={inputIsTouched && !!validateErrors?.currentValue}
/>
<div className={classes['ErrorContainer']}>
{inputIsTouched && validateErrors?.currentValue && (
<Text color="error" size="s">
{validateErrors.currentValue}
</Text>
)}
</div>
</div>
);
};
Bit of a noob to redux but this appears to be quite a difficult question! I hope someone may be able to help me :)
I have build a page where you can input a search for different types of jobs. From this, it will make a get request to my DB and get all the info on this job. As this page is multi-levelled, I want to use redux to dispatch and pass the state throughout. This will help me pass my data on the job, e.g Data Analyst, through to each component so it can use the data and populate fields.
However, this was how my input field was originally setup:
export function SearchBarComp(props) {
const [isExpanded, setExpanded] = useState(false);
const [parentRef, isClickedOutside ] = useClickOutside();
const inputRef = useRef();
const [searchQuery, setSearchQuery] = useState("");
const [isLoading, setLoading] = useState(false);
const [jobPostings, setjobPostings] = useState([]);
const [noRoles, setNoRoles] = useState(false)
const isEmpty = !jobPostings || jobPostings.length === 0;
const changeHandler = (e) => {
//prevents defaulting, autocomplete
e.preventDefault();
if(e.target.value.trim() === '') setNoRoles(false);
setSearchQuery(e.target.value);
}
const expandedContainer = () => {
setExpanded(true);
}
//LINK THE BACKEND!
const prepareSearchQuery = (query) => {
//const url = `http://localhost:5000/api/role/title?title=${query}`;
const url = `http://localhost:5000/api/role/titlerole?title=${query}`;
//replaces bad query in the url
return encodeURI(url);
}
const searchRolePosition = async () => {
if(!searchQuery || searchQuery.trim() === "")
return;
setLoading(true);
setNoRoles(false);
const URL = prepareSearchQuery(searchQuery);
const response = await axios.get(URL).catch((err) => {
console.log(err);
});
if(response) {
console.log("Response", response.data);
if(response.data && response.data === 0)
setNoRoles(true);
setjobPostings(response.data);
}
setLoading(false);
}
useDebounce(searchQuery, 500, searchRolePosition)
const collapseContainer = () => {
setExpanded(false);
setSearchQuery("");
setLoading(false);
setNoRoles(false);
if (inputRef.current) inputRef.current.value = "";
};
// console.log("Value", searchQuery)
useEffect(()=> {
if(isClickedOutside)
collapseContainer();
}, [isClickedOutside])
return (
<SearchBarContainer animate = {isExpanded ? "expanded" : "collapsed"}
variants={containerVariants} transition={containerTransition} ref={parentRef}>
<SearchInputContainer>
<SearchIconSpan>
<SearchIcon/>
</SearchIconSpan>
<SearchInput placeholder = "Search for Roles"
onFocus={expandedContainer}
ref={inputRef}
value={searchQuery}
onChange={changeHandler}
/>
<AnimatePresence>
{isExpanded && (<CloseIconSpan key="close-icon"
inital={{opacity:0, rotate: 0}}
animate={{opacity:1, rotate: 180}}
exit={{opacity:0, rotate: 0}}
transition={{duration: 0.2}}
onClick={collapseContainer}>
<CloseIcon/>
</CloseIconSpan>
)}
</AnimatePresence>
</SearchInputContainer>
{isExpanded && <LineSeperator/>}
{isExpanded && <SearchContent>
{!isLoading && isEmpty && !noRoles && (
<Typography color="gray" display="flex" flex="0.2" alignSelf="center" justifySelf="center">
Start typing to search
</Typography>
)}
{!isLoading && !isEmpty && <>
{jobPostings.map((searchRolePosition) => (
<JobSection
title={searchRolePosition.title}
//will need to do something like ----
//people = {searchRolePosition.title && searchRolePosition.title.average}
// future implementations
/>
))}
</>}
</SearchContent>}
</SearchBarContainer>
)
}
As you can see, the main thing is the 'query' this creates a backend request to my titlerole, such as getting the data on Data Analyst. This all works in my frontend right now, but I can't pass that data down to the next component etc
So i'm looking to use redux.
I've created the following slice:
import { createSlice } from "#reduxjs/toolkit";
const jobSearchSlice = createSlice({
name: "jobsearch",
initialState: {
currentRole: null,
isFetching: false,
error: false,
},
reducers: {
jobsearchStart: (state) => {
state.isFetching = true;
},
jobsearchSuccess: (state, action) => {
state.isFetching = false;
state.currentRole = action.payload;
},
jobsearchFailure: (state) => {
state.isFetching = false;
state.error = true;
},
},
});
export const { jobsearchStart, jobsearchSuccess, jobsearchFailure } = jobSearchSlice.actions;
export default jobSearchSlice.reducer;
With this, I'm also using the following apiCalls.js file:
import { jobsearchStart, jobsearchSuccess, jobsearchFailure } from "./jobsearchSlice";
import { publicRequest } from "../requestMethods";
export const roleSearchQuery = async (dispatch, jobsearch) => {
dispatch(jobsearchStart());
try{
const res = await publicRequest.get("`http://localhost:5000/api/role/titlerole?title=${query}`", jobsearch);
dispatch(jobsearchSuccess(res.data))
}catch(err){
dispatch(jobsearchFailure());
}
};
My question is as a Redux noob, how do I implement this query functionality into a redux API request? What's the way to do this properly as I begin to tranisition this to an app which uses standard state management!
My code
UserEdit.jsx
import useForm from "../../utils/useForm";
import LoadingBtn from "../../utils/loadingButton";
import { getUser } from "../../store/users/userActions";
const UserEdit = () => {
//declare form data and scmena
const formInput = {
name: "",
};
const schema = {
name: Joi.string().required().min(3).max(191).label("Name"),
};
//dispatch on first mount
const dispatch = useDispatch();
const params = useParams();
useEffect(() => {
setErrors({});
dispatch(getUser(params.id));
}, []);
const {
handleChange,
handleSubmit,
formData,
setFormData,
errors,
setErrors,
} = useForm(formInput, doSubmit, schema);
function doSubmit() {
console.log("handle Submit", formData);
}
const isSubmitting = useSelector((state) => state.users.isSubmitting);
const user = useSelector((state) => state.users.user);
useEffect(() => {
console.log("useEffect user >>", user);
setFormData({
...formData,
name: user.name,
});
}, [user]);
return (
<input
className={`form-control ${
errors["name"]
? "is-invalid"
: ""
}`}
type="text"
id="name"
name="name"
required=""
placeholder="Enter your name"
onChange={handleChange}
value={formData.name}
/>
);
};
export default UserEdit;
UseForm.jsx
const useForm = (formInput, callback, schema = {}) => {
const [formData, setFormData] = useState(formInput);
const [errors, setErrors] = useState({});
const [isSubmitting, setIsSubmitting] = useState(false);
const handleChange = (event) => {
const { name, value } = event.target;
setFormData({
...formData,
[name]: value,
});
};
const handleSubmit = (e) => {
e.preventDefault();
//handle error
setErrors(validate(formData));
setIsSubmitting(true);
};
const validate = (formData) => {
const { error } = Joi.validate(formData, schema, {
abortEarly: false,
});
if (!error) return {};
const validataionErrors = {};
for (let item of error.details) {
validataionErrors[item.path[0]] = item.message;
}
return validataionErrors;
};
useEffect(() => {
//check if there are any errors
if (Object.keys(errors).length === 0 && isSubmitting) {
callback();
setIsSubmitting(true);
}
}, [errors]);
return {
handleChange,
handleSubmit,
formData,
setFormData,
errors,
setErrors,
};
};
export default useForm;
I googled about the error and it mentioned that state need to be initialized at first with the field. However, I have already defined initial state as
const formInput = {
name: "",
};
I could not find how could I fix this, I am open to restructure the useForm hooks if that is the one which causing trouble.
currently, if I uncomment the following line on userEdit.jsx, the warning will be gone, but also the edit form becomes empty as well
setFormData({
...formData,
name: user.name,
});
In all the examples that I saw online, only the event was being passed into the handleChange and value was being used automatically. However, I get an error that value is not found. How can I use value in the handleChange? I am trying to validate the form using Formik here.
export default function MyPage() {
const [isSubmitted, setIsSubmitted] = useState(false);
const [isRemoved, setIsRemoved] = useState(false);
const [removeUser] = useMutation<Response>(USER);
let submitForm = (email: string) => {
User({
variables: {
email: email,
},
})
.then(({ data }: ExecutionResult<Response>) => {
if (data !== null && data !== undefined) {
setIsRemoved(true);
}
}) };
const formik = useFormik({
initialValues:{ email: '' },
onSubmit:(values, actions) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
actions.setSubmitting(false);
}, 1000);
},
validationSchema:schema
})
const handleChange = (e: ChangeEvent<HTMLInputElement>)=>{
if (name!== null) && (value!==null){
const {name,value} = event.target;
formik.setFieldValue(name,value);
}
}
return (
<div>
<Form
onSubmit={e => {
e.preventDefault();
submitForm(formik.values.email);
}}>
<div>
<TextField
variant="outlined"
margin="normal"
id="email"
name="email"
helperText={formik.touched.email ? formik.errors.email : ''}
error={formik.touched.email && Boolean(formik.errors.email)}
label="Email"
value={formik.values.email}
//onChange={change.bind(null, 'email')}
onChange={handleChange}
/>
<CustomButton
disabled={!isValid || !formik.values.email}
text={'Remove User'}
/>
</div>
</Form>
</div>
);
}
On this line:
const {name,value} = event.target;
I also get this error even though I am already checking this with an if statement:
Property 'name' does not exist on type 'EventTarget | null'.ts(2339)
You are trying to access the values that are not assigned yet. First, declare them then try to access them. Also, you are using event.target but you need to use e.target instead as you have declared event like:
const handleChange = (e: ChangeEvent<HTMLInputElement>)
So, the code inside the handleChange() function needs to be updated like:
const { name,value } = e.target;
if (name && value){
formik.setFieldValue(name, value);
}
In your handleChange, you use (e) as a parameter but then you reference event.target. Rename your parameter to (event).
handleChange= (event) => {
const {name,value} = event.target;
...
}
I am unable to save description as part of the component's state. I can only save the title. How do I save title and description to the database?
const BlogCreate = ({ history }) => {
const [blogCreate, setBlogCreate] = useState({
title: "",
description: ""
});
const [editorState, setEditorState] = useState(
EditorState.createEmpty(),
);
const handleChange = ({ currentTarget }) => {
const { name, value } = currentTarget;
setBlogCreate({...blogCreate, [name]: value});
};
const onEditorStateChange = editorState => {
setEditorState(editorState);
};
const handleSubmit = async event => {
event.preventDefault();
const data = draftToHtml(convertToRaw(editorState.getCurrentContent()));
try {
await blogAPI.create(blogCreate, data);
} catch (error) {
console.log(error)
}
}
console.log(data);
}
return(
<Field type="text" name="title" error={errors.title} value={blogCreate.title}
onChange={handleChange}
/>
<Editor name="description" editorState={editorState} onEditorStateChange={editorState => onEditorStateChange(editorState)}
/>
<button type="submit">Save</button>
);
}
export default BlogCreate;
Based on the full code you've provided me, I realised that you aren't properly updating the blogCreate state whenever there is a change at the Editor component.
The onEditorStateChange() should be updating the blogCreate state, and in addition, changeValue() needs to return the result value.
const changeValue = editorState => {
const value = ....
return value;
};
const onEditorStateChange = editorState => {
const description = changeValue(editorState);
setBlogCreate({
...blogCreate,
description,
});
setEditorState(editorState);
};
This way, description will be properly updated on your state, and it will be sent to your server side when you make the blogAPI.create() request.