I'm trying to get plants that have a folderId with value based on the useRouter params. I don't have errors but no data is returned and I do have plants with folderId in "PlantsData" collection. Code:
const [user] = useAuthState(auth)
const {
query: { id },
} = useRouter();
const [plants, setPlants] = useState();
const [loading, setLoading] = useState(true);
const getPlants= async () => {
try {
const PlantsQuery = query(
collection(db, "PlantsData"),
where("folderId", "==", id),
orderBy("postedAt", "desc")
);
const plantsDocs = await getDocs(PlantsQuery);
const plants = plantsDocs.docs.map((doc) => ({
id: doc.id,
...doc.data(),
}));
setPlants((prev) => ({
...prev,
plants: plants,
}));
setLoading(false);
} catch (error) {
console.log("error", error.message);
}
};
useEffect(() => {
if (id && user) {
getPlants();
}
}, [id, user]);
folderId was in an object field folder and did fix it by: where(new FieldPath("folder", "folderId"), "==", id)
this is the image(click here!) where you can see our web page with certain fields, that one of the field with arrow is the one didn't populate object from the backend
this is written with react & typescript & our backend is java springboot & MySQL as database.
and here is the code that i'm suspected , having a issue:
const AddUsers: FC = (props) => {
const navigate = useNavigate();
// const { id } = useParams();
const dispatch = useDispatch();
const roleList = useGetRoleList()
const user = useCurrentUser();
const [rolesInput, setRolesInput] = useState<MultiValue<{ value: string; label: string; }>>([]);
const isFetching = useInviteUserLoading()
const permitted = useCheckPermission(ROLES.ADD_USER)
const { register, handleSubmit, formState: { errors }, clearErrors, reset } =
useForm<IUserForm>({ //'resetField'
mode: "onChange",
resolver: yupResolver(userFormSchema)
});
useEffect(() => {
window.scrollTo({ top: 0, behavior: 'smooth' });
}, [])
//fetching list from backend.. <--------
useUpdateEffect( () => {
dispatch( getRolesList({companyId: user.companyId}) );
},[user.companyId])
useUpdateEffect(() => {
clearErrors()
reset();
}, [isFetching])
const onSubmit: SubmitHandler<IUserForm> = (data)=>{
const roles = rolesInput.map(role => parseInt(role.value))
if(roles){
dispatch(inviteUser({
companyId: user.companyId,
roles: roles,
firstName: data.firstName,
lastName: data.lastName,
email: data.email
}))
}else{
alert("Please assign roles")
}
}
export const Modal = (props, { farm, id }) => {
const [currentUser, setCurrentUser] = useState();
console.log("NewId", props.id);
const initialFieldValues = {
title: "",
image: "",
product: "",
content: "",
phone: "",
email: "",
};
const [values, setValues] = useState(initialFieldValues);
function handleChange(e) {
const { name, value } = e.target;
setValues({
...values,
[name]: value,
});
}
const handleFormSubmit = (e) => {
e.preventDefault();
const obj = {
...values,
};
getFirebase()
.database()
.ref(`Users/${props.id}`)
// .ref(`Users/${newId}`)
.push(obj, (err) => {
if (err) console.log(err);
// console.log("ID", newId);
});
};
PARENT COMPONENT
const App = (props) => {
const [currentUser, setCurrentUser] = useState();
const [id, setId] = useState("");
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged(async (userAuth) => {
if (userAuth) {
const userRef = await createUserProfileDocument(userAuth);
userRef.on("value", (snapShot) => {
setCurrentUser({ key: snapShot.key, ...snapShot.val() });
});
// }
}
if (setCurrentUser(userAuth)) {
} else if (!userAuth && typeof window !== "undefined") {
return null;
}
const id = userAuth.uid;
setId(id);
});
<>
<Modal id={id} />
</>;
return unsubscribe;
}, []);
When i console.log it here .ref(Users/${props.id}) it's undefined.
This is how my console looks like:
12modal.jsx:12 'NewId' fRHyyzzEotPpIGUkkqJkKQnrTOF2
12modal.jsx:12 'NewId' undefined
it comes one time with the ID and 12 times undifiend.
Any advice would be greatly appreciated.
Firstly you can't change a props values, and if props.id is a state(i.e. const [id, setId] = React.useState(initial value)) then you can change id only by setId which is not received by child, so i think parent might be causing this issue.
I got data.js that contains name, listOfReview that have sub nested state(name, occupation and etc). i'm using useReducer to add another review on the listofreview. but the problem is after posting a review it will create a new object outside the listofreview. The output
data.js
export const data = [
{
id: 1607089645363,
name: 'john',
noOfReview: 1,
listOfReview: [
{
reviewId: 1607089645361,
name: 'john doe',
occupation: 'hero',
rating: 5,
review: 'lorem ipsum',
}
]
},
];
index.js
import { data } from '../../src/data';
import { reducer } from './reducer';
const defaultState = {
review: data
}
const ModalHandler = props => {
const [rating, setRating] = useState(null);
const [name, setName] = useState('');
const [occupation, setOccupation] = useState('');
const [reviews, setReviews] = useState('');
const [state, dispatch] = useReducer(reducer, defaultState);
const handelSubmit = (e) => {
e.preventDefault();
if (name && occupation && reviews) {
const newReview = { Reviewid: new Date().getTime().toString(), rating, name, occupation, reviews };
dispatch({ type: 'ADD_REVIEW_ITEM', payload: newReview });
}
}
}
reducer.js
export const reducer = (state, action) => {
switch (action.type) {
case "ADD_REVIEW_ITEM":
return state.map((data) => {
if (data) {
const newReview = [...data.listOfReview, action.payload];
return {
...data,
listOfReview: newReview
};
}
return data;
});
default:
return state;
}
};
OUTPUT
According to your data structure, you have an array of listOfReview in an array of data:-
These 2 fixes may help you:-
FIXES 1: You need to pass the selected id of data you want to add new review to
FIXES 2: You need to change the way you code your reducer so that it will update the state correctly and not mutating the current state
Fixes 1
index.js. Adjust your handleSubmit to receive arg of selected id of data you want to add *new review obj. Thus, send both 'selectedDataId' and newReview via dispatch payload
import { data } from '../../src/data';
import { reducer } from './reducer';
const ModalHandler = props => {
const [rating, setRating] = useState(null);
const [name, setName] = useState('');
const [occupation, setOccupation] = useState('');
const [reviews, setReviews] = useState('');
const [state, dispatch] = useReducer(reducer, defaultState);
const handelSubmit = (e, selectedDataId) => {
e.preventDefault();
if (name && occupation && reviews) {
const newReview = { Reviewid: new Date().getTime().toString(), rating, name, occupation, reviews };
dispatch({
type: 'ADD_REVIEW_ITEM',
payload: {
selectedDataId: selectedDataId,
newReview: newReview
}
});
}
}
}
Fixes 2
in reducer. Adjust so that it will not mutate your current state
const reducer = (state, action) => {
switch (action.type) {
case "ADD_REVIEW_ITEM":
return {
...state,
review: state.review.map((data) => {
if (data.id === action.payload.selectedDataId) {
return {
...data,
listOfReview: [...data.listOfReview, action.payload.newReview]
};
}
return data;
})
};
default:
return state;
}
};
This a working sandbox of your case.
I have several functional components which share the same logic. So I would like to refactor them using React hooks. All of them make some calls to the server on mount to check if the order has been paid. If yes, paid state is set to true , and a file is being downloaded. On submit I check if paid state is set to true, if yes, the same file is being downloaded, if not, a new order is created and a user is being redirected to a page with a payment form.
I have already extracted all functions (getOrder(), getPaymentState(), createOrder(), initPayment() and downloadFile()) which make API calls to the server. How can I further optimize this code, so that I could move checkOrder(), checkPayment(), downloadPDF() and newOrder() outside the component to use the same logic with other components as well?
Here is my component:
const Form = () => {
const [paid, setPaid] = useState(false);
const [submitting, setSubmitting] = useState(false);
const [loading, setLoading] = useState(false);
const [data, setData] = useState({});
const checkOrder = async () => {
let search = new URLSearchParams(window.location.search);
let success = search.get("Success");
if (success) {
try {
const data = await getOrder();
setData(data);
checkPayment(data);
} catch (err) {
alert(err.message)
}
}
};
const checkPayment = async values => {
try {
const paid = await getPaymentState();
setPaid(paid);
downloadPDF(values);
} catch (err) {
alert(err.message)
}
};
const downloadPDF = async values => {
setLoading(true);
let downloadData = {
email: values.email,
phone: values.phone
}
const response = await downloadFile(downloadData, sendURL);
setLoading(false);
window.location.assign(response.pdf);
}
const newOrder = async values => {
setSubmitting(true);
const order = await createOrder(values, description, sum);
const paymentUrl = await initPayment(order, description, sum, returnURL);
setSubmitting(false);
window.location.assign(paymentUrl);
}
const onSubmit = async values => {
if (paid) {
try {
downloadPDF(data);
} catch (err) {
console.log(err);
}
} else {
try {
newOrder(values)
} catch (err) {
alert(err.message)
}
}
};
useEffect(() => {
checkOrder();
}, []);
return (
)
}
EDIT 1: I also need to be able to pass some data to this hook: downloadData, sendURL, description, sum and returnURL, which will be different in each case. downloadData then needs to be populated with some data from the values.
I would appreciate if you could point me at the right direction. I'm just learning React and I would really like to find the correct way to do this.
EDIT 2: I've posted my own answer with the working code based on the previous answers. It's not final, because I still need to move downloadPDF() outside the component and pass downloadData to it, but when I do so, I get an error, that values are undefined. If anybody can help me with that, I will accept it as an answer.
I made a quick refactor of the code and put it in a custom hook, it looks like search param is the key for when the effect needs to run.
const useCheckPayment = (search) => {
const [paid, setPaid] = useState(false);
const [submitting, setSubmitting] = useState(false);
const [loading, setLoading] = useState(false);
const [data, setData] = useState({});
const checkOrder = useCallback(async () => {
let paramSearch = new URLSearchParams(search);
let success = paramSearch.get('Success');
if (success) {
try {
//why not just pass it, makes getOrder a little less impure
const data = await getOrder(paramSearch);
setData(data);
checkPayment(data);
} catch (err) {
alert(err.message);
}
}
}, [checkPayment, search]);
const checkPayment = useCallback(async (values) => {
try {
const paid = await getPaymentState();
setPaid(paid);
downloadPDF(values);
} catch (err) {
alert(err.message);
}
}, []);
const downloadPDF = async (values) => {
setLoading(true);
const response = await downloadFile();
setLoading(false);
window.location.assign(response.pdf);
};
const newOrder = async (values) => {
setSubmitting(true);
const order = await createOrder();
const paymentUrl = await initPayment(order);
setSubmitting(false);
window.location.assign(paymentUrl);
};
const onSubmit = useCallback(
async (values) => {
if (paid) {
try {
downloadPDF(data);
} catch (err) {
console.log(err);
}
} else {
try {
newOrder(values);
} catch (err) {
alert(err.message);
}
}
},
[data, paid]
);
useEffect(() => {
checkOrder();
}, [checkOrder]); //checkOrder will change when search changes and effect is called again
return { onSubmit, submitting, loading };
};
const Form = () => {
const { onSubmit, submitting, loading } = useCheckPayment(
window.location.search
);
return '';
};
You can extract out all the generic things from within the Form component into a custom Hook and return the required values from this hook
The values which are dependencies and will vary according to the component this is being called from can be passed as arguments to the hook. Also the hook can return a onSubmit function to which you can pass on the downloadData
const useOrderHook = ({returnURL, sendURL, }) => {
const [paid, setPaid] = useState(false);
const [submitting, setSubmitting] = useState(false);
const [loading, setLoading] = useState(false);
const [data, setData] = useState({});
const checkOrder = async () => {
let search = new URLSearchParams(window.location.search);
let success = search.get("Success");
if (success) {
try {
const data = await getOrder();
setData(data);
checkPayment(data);
} catch (err) {
alert(err.message)
}
}
};
const checkPayment = async values => {
try {
const paid = await getPaymentState();
setPaid(paid);
downloadPDF(values);
} catch (err) {
alert(err.message)
}
};
const downloadPDF = async values => {
setLoading(true);
let downloadData = {
email: values.email,
phone: values.phone
}
const response = await downloadFile(downloadData, sendURL);
setLoading(false);
window.location.assign(response.pdf);
}
const newOrder = async (values, description, sum) => {
setSubmitting(true);
const order = await createOrder(values, description, sum);
const paymentUrl = await initPayment(order, description, sum, returnURL);
setSubmitting(false);
window.location.assign(paymentUrl);
}
const onSubmit = async ({values, downloadData: data, description, sum}) => {
if (paid) {
try {
downloadPDF(data);
} catch (err) {
console.log(err);
}
} else {
try {
newOrder(values, description, sum)
} catch (err) {
alert(err.message)
}
}
};
useEffect(() => {
checkOrder();
}, []);
return {onSubmit, loading, submitting, paid, data };
}
Now you can use this hook in component like Form as follows
const Form = () => {
const {onSubmit, newOrder, loading, submitting, paid, data } = useOrderHook({returnUrl: 'someUrl', sendURL: 'Some send URL'})
const handleSubmit = (values) => {
// since this function is called, you can get the values from its closure.
const data = {email: values.email, phone: values.phone}
onSubmit({ data, values, description, sum})// pass in the required values for onSubmit here. you can do the same when you actually call newOrder from somewhere
}
// this is how you pass on handleSubmit to React-final-form
return <Form
onSubmit={handleSubmit }
render={({ handleSubmit }) => {
return <form onSubmit={handleSubmit}>...fields go here...</form>
}}
/>
}
Based on the answers above I came up with the following code.
The hook:
const useCheckPayment = ({initialValues, sendUrl, successUrl, description, sum, downloadPDF}) => {
const [paid, setPaid] = useState(false);
const [loading, setLoading] = useState(false);
const [submitting, setSubmitting] = useState(false);
const [data, setData] = useState(initialValues);
const checkOrder = useCallback(
async () => {
let search = new URLSearchParams(window.location.search);
let success = search.get('Success');
if (success) {
try {
const data = await getOrder(search);
setData(data);
checkPayment(search);
} catch (err) {
alert(err.message);
}
}
}, [checkPayment]
);
const checkPayment = useCallback(
async (search) => {
try {
const paid = await getPaymentState(search);
setPaid(paid);
document.getElementById('myForm').dispatchEvent(new Event('submit', { cancelable: true }))
} catch (err) {
alert(err.message);
}
}, []
);
const newOrder = useCallback(
async (values) => {
setSubmitting(true);
const order = await createOrder(values, description, sum);
const paymentUrl = await initPayment(order, description, sum, successUrl);
setSubmitting(false);
window.location.assign(paymentUrl);
}, [description, sum, successUrl]
);
const downloadPDF = async (values, downloadData) => {
setLoading(true);
const response = await downloadFile(downloadData, sendUrl);
setLoading(false);
window.location.assign(response.pdf);
};
const onSubmit = useCallback(
async ({ values, downloadData }) => {
if (paid) {
try {
downloadPDF(values, downloadData);
} catch (err) {
console.log(err);
}
} else {
try {
newOrder(values);
} catch (err) {
alert(err.message);
}
}
},
[paid, downloadPDF, newOrder]
);
useEffect(() => {
checkOrder();
}, [checkOrder]);
return { onSubmit, submitting };
};
The component:
const sendUrl = 'https://app.example.com/send'
const successUrl = 'https://example.com/success'
const description = 'Download PDF file'
const sum = '100'
const Form = () => {
const handleSubmit = (values) => {
const downloadData = {
email: values.email,
phone: values.phone
}
onSubmit({ downloadData, values })
}
const { onSubmit, submitting } = useCheckPayment(
{sendUrl, successUrl, description, sum}
);
return (
<Form
onSubmit={handleSubmit}
render={({ handleSubmit }) => (
<form onSubmit={handleSubmit}></form>
)}
/>
)
}