I have a custom hook to redirect users to edit page. On index page I can duplicate items and delete. I can redirect users after duplicate, but the problem is when I delete an item, this custom hook redirects users to edit page again. So I need to find a way to make it work conditionally.
Custom hook:
export default function useDuplicateItem(url: string) {
const { sendRequest: duplicate } = useHttpRequest();
const duplicateItem = useCallback(
(data) => {
duplicate([
{
url: `/api/server/${url}`,
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
data,
},
]);
},
[duplicate, url]
);
useRedirectEditPage(url); // This causes the problem
return duplicateItem;
}
index page:
const duplicateItem = useDuplicateItem('documents');
// custom hook rendered here, which is not correct. I want to run it when duplicate function runs.
const duplicate = useCallback(() => {
const data = {
name: copiedName,
sources: singleDocument?.sources,
document: singleDocument?.document,
tool: singleDocument?.tool,
access: singleDocument?.access,
};
duplicateItem(data);
}, [copiedName, duplicateItem, singleDocument]);
useRedirectEditPage:
export default function useRedirectEditPage(slug: string) {
const { saveResponses, setSaveResponses, setHeaderStates } =
useAdminContext();
const router = useRouter();
useEffect(() => {
const statusCodes: number[] = [];
let id;
saveResponses.forEach((value) => {
statusCodes.push(value?.status);
id = value?.id;
});
if (statusCodes.length && id) {
if (statusCodes.includes(404)) {
setHeaderStates((prev) => ({
...prev,
canBeSaved: false,
}));
} else {
router.push(`/admin/${slug}/edit/${id}`);
setSaveResponses(new Map());
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [saveResponses, router, setSaveResponses]);
}
saveResponses state is coming after I make any request to server, and I am able to get id to redirect users. I use new Map() to set data inside saveResponses.
From the react docs:
Don’t call Hooks inside loops, conditions, or nested functions.
Instead, always use Hooks at the top level of your React function,
before any early returns. By following this rule, you ensure that
Hooks are called in the same order each time a component renders.
That’s what allows React to correctly preserve the state of Hooks
between multiple useState and useEffect calls. (If you’re curious,
we’ll explain this in depth below.)
React relies on the order in which Hooks are called to know which setState corresponds to which state, calling them inside a condition will mess up the previous mechanism.
I would recommend to read the following: https://reactjs.org/docs/hooks-rules.html#explanation
Related
I have created a custom react hook to basically validate a user input field and dispatch a action to verify using some 3rd party api.
Now, now i have two fields which basically does the same thing to use that custom hook, verify and return result. I am using the same hook with 2 intances.
Now, everytime i fill and verify using the hook it works fine, but it also updates the other field, i have changed the name everything but same thing is happening.
Let me share some code.
export default function useVerifier(inputFieldValue, cT) {
const [data, setData] = useState([]);
const [isVerified, setIsVerified] = useState(false)
const [toolTip, setTooltip] = useState({ message: '', type: '' })
const {
details
} = useSelector((state) => state?.profile);
const dispatch = useDispatch()
const checkIFValid = () => {
for (var key in details) {
if (has(details[key][0], 'isValid')) {
setIsVerified(true)
setTooltip(validationMessgage['VALID'])
} else {
setIsVerified(false)
setTooltip(validationMessgage['NOT_VALID'])
}
}
}
useEffect(() => {
checkIFValid()
}, [details])
useEffect(() => {
let isVer = false
setTooltip(validationMessgage.NOT_VALID)
setIsVerified(isVer)
if (inputFieldValue != undefined && inputFieldValue.length === 10) {
const request = {
fieldNumber: [inputFieldValue.toUpperCase()],
};
dispatch(callVerify(request, cT));
}
checkIFValid()
setData(inputFieldValue)
// setIsVerified(isVer);
}, [inputFieldValue, cT]);
return [data, isVerified, toolTip];
}
So the details field which i am extracting from profile, basically consists of array of object, which have isValid key. If that key is true, i set the verifiy flag to be true.
The details field is working and storing data fine in redux store.
Now the hook i am using in one component like this.
const [proposerData, isProposerVerified, proposerToolTips] = usePanVerifier(watchNumber.Number, 'proposer')
const [panddData, isVerified, toolTips] = usePanVerifier(watchInsuredNumber.insuredNumber, 'insurer')
Now the watch here is from react hook form. So the problem is that even both hooks have different parameter, they are called simultaneously, so both the verify (isProposerVerified and isVerified) is getting verified at the same time, even when user have not entered anything in number input field.
I hope i have explained it correctly. Please help. Stuck big time in this.
I have created a function which adds a specific item to my diary. 9/10 times everything works, which means that there is nothing wrong with the code?
However rarely I add the item to my diary, but I don't see the update values, even thought I activated queryClient.invalidateQueries() method, the value is updated on my server, because when I manually refresh I see the updated diary again.
Does this mean that by the time I activate invalidatequeries method, the update has not reached my server and that is why I am seeing stale data? But what would I do in that case?
Here is the function:
const newAddItemFunction = () => {
const day = newDiary?.[currentDay];
if (day && selectedMealNumber && selectedItem) {
setSavingItem(true);
NewAddItemToDiary({
day,
selectedMealNumber,
selectedItem,
});
queryClient.invalidateQueries(["currentDiary"]).then(() => {
toast.success(`${selectedItem.product_name} has been added`);
});
router.push("/diary");
}
};
Here is my custom hook(useFirestoreQuery is just custom wrapped useQuery hook for firebase):
export const useGetCollectionDiary = () => {
const user = useAuthUser(["user"], auth);
const ref = collection(
firestore,
"currentDiary",
user.data?.uid ?? "_STUB_",
"days"
);
return useFirestoreQuery(
["currentDiary"],
ref,
{
subscribe: false,
},
{
select: (data) => {
let fullDaysArray = [] as Day[];
data.docs.map((docSnapshot) => {
const { id } = docSnapshot;
let data = docSnapshot.data() as Day;
data.documentId = id;
fullDaysArray.push(data);
});
fullDaysArray.sort((a, b) => a.order - b.order);
return fullDaysArray;
},
enabled: !!user.data?.uid,
}
);
};
NewAddItemToDiary function is just firebase call to set document:
//...json calculations
setDoc(
doc(
firestore,
"currentDiary",
auth.currentUser.uid,
"days",
day.documentId
),
newDiaryWithAddedItem
);
9/10 times everything works, which means that there is nothing wrong with the code?
It indicates to me that there is something wrong with the code that only manifests in edge cases like race conditions.
You haven't shared the code of what NewAddItemToDiary is doing, but I assume it's asynchronous code that fires off a mutation. If that is the case, it looks like you fire off the mutation, and then invalidate the query without waiting for the query to finish:
NewAddItemToDiary({
day,
selectedMealNumber,
selectedItem,
});
queryClient.invalidateQueries(["currentDiary"]).then(() => {
toast.success(`${selectedItem.product_name} has been added`);
});
Mutations in react-query have callbacks like onSuccess or onSettled where you should be doing the invalidation, or, if you use mutateAsync, you can await the mutation and then invalidate. This is how all the examples in the docs are doing it:
// When this mutation succeeds, invalidate any queries with the `todos` or `reminders` query key
const mutation = useMutation(addTodo, {
onSuccess: () => {
queryClient.invalidateQueries('todos')
queryClient.invalidateQueries('reminders')
},
})
I have this JavaScript data file(src/test/test.js):
module.exports = {
"title": "...",
"Number": "number1",
"Number2": ({ number1 }) => number1 / 2,
}
I want to pass this file verbatim(functions preserved) to a page, so that the page can use that data to build itself. I already have the page template and everything else sorted out, I just need to find a way to pass this into the page.
The first approach I tried is requireing this file in gatsby-node.js and then passing it as pageContext.
gatsby-node.js
const path = require('path');
exports.createPages = ({actions, graphql}) => {
const { createPage } = actions;
return graphql(`
query loadQuery {
allFile(filter: {sourceInstanceName: {eq: "test"}}) {
edges {
node {
relativePath
absolutePath
}
}
}
}
`).then(result => {
if (result.errors) {
throw result.errors;
}
for (const node of result.data.allFile.edges.map(e => e.node)) {
const data = require(node.absolutePath);
createPage({
path: node.relativePath,
component: path.resolve('./src/templates/test.js'),
context: data,
});
}
});
};
gatsby-config.js
module.exports = {
plugins: [
{
resolve: `gatsby-source-filesystem`,
options: {
name: `test`,
path: `${__dirname}/src/test/`,
},
},
],
}
src/templates/test.js
import React from 'react';
const index = ({ pageContext }) => (
<p>{pageContext.Number2()}</p>
);
export default index;
However, I get this warning when running the dev server:
warn Error persisting state: ({ number1 }) => number1 / 2 could not be cloned.
If I ignore it and try to use the function anyway, Gatsby crashes with this error:
WebpackError: TypeError: pageContext.Number2 is not a function
After searching for a while, I found this:
The pageContext was always serialized so it never worked to pass a function and hence this isn't a bug. We might have not failed before though.
- Gatsby#23675
which told me this approach wouldn't work.
How could I pass this data into a page? I've considered JSON instead, however, JSON can't contain functions.
I've also tried finding a way to register a JSX object directly, however I couldn't find a way.
Regarding the main topic, as you spotted, can't be done that way because the data is serialized.
How could I pass this data into a page? I've considered JSON instead,
however, JSON can't contain functions.
Well, this is partially true. You can always do something like:
{"function":{"arguments":"a,b,c","body":"return a*b+c;"}}
And then:
let func = new Function(function.arguments, function.body);
In this case, you are (de)serializing a JSON function, creating and casting a function based on JSON parameters. This approach may work in your scenario.
Regarding the JSX, I guess you can try something like:
for (const node of result.data.allFile.edges.map(e => e.node)) {
const data = require(node.absolutePath);
createPage({
path: node.relativePath,
component: path.resolve('./src/templates/test.js'),
context:{
someComponent: () => <h1>Hi!</h1>
},
});
}
And then:
import React from 'react';
const Index = ({ pageContext: { someComponent: SomeComponent} }) => (
return <div><SomeComponent /></div>
);
export default index;
Note: I don't know if it's a typo from the question but index should be capitalized as Index
In this case, you are aliasing the someComponent as SomeComponent, which is a valid React component.
I have the next hook in my react js application:
const {
data,
loading
} = fetchData(info)({
variables: {
id: myId,
},
fetchPolicy: 'no-cache',
});
//
const fetchData = (info) => {
if (a > 1) {
return useGetCars;
}
return useGetColors;
};
The issue appear in the first render when the myId is empty, but it is required. Due this fact i get an error from the server.
Question: How to create a condition for the hook above to be able to run it only when the myId is not empty?
Use the effect hook:
useEffect(() => {
if (myId) {
fetchData() // after this, you can set the required states.
}
}, [myId])
I have read several cases on stackoverflow regarding missing dependencies in useEffect:
example :
How to fix missing dependency warning when using useEffect React Hook?
Now the case I have is, I use useEffect for pagination:
Here's the source code:
react-router-dom configuration
{ path: "/note", name: "My Note", exact: true, Component: Note },
Note Component
const Note = (props) => {
const getQueryParams = () => {
return window.location.search.replace("?", "").split("&").reduce((r, e) => ((r[e.split("=")[0]] = decodeURIComponent(e.split("=")[1])), r),
{}
);
};
const MySwal = withReactContent(Swal);
const history = useHistory();
// Get Queries On URL
const { page: paramsPage, "per-page": paramsPerPage } = getQueryParams();
// Queries as state
const [queryPage, setQueryPage] = useState(
paramsPage === undefined ? 1 : paramsPage
);
const [queryPerPage, setQueryPerPage] = useState(
paramsPerPage === undefined ? 10 : paramsPerPage
);
// Hold Data Records as state
const [notes, setNotes] = useState({
loading: false,
data: [],
totalData: 0,
});
useEffect(() => {
console.log(queryPage, queryPerPage);
setNotes({
...notes,
loading: true,
});
// Fetching data from API
NoteDataService.getAll(queryPage, queryPerPage)
.then((response) => {
setNotes({
loading: false,
data: response.data,
totalData: parseInt(response.headers["x-pagination-total-count"]),
});
return true;
})
.catch((e) => {
MySwal.fire({
title: e.response.status + " | " + e.response.statusText,
text: e.response.data,
});
});
return false;
}, [queryPage, queryPerPage]);
const { loading, data, totalData } = notes;
...
So there are two problems here:
There is a warning React Hook use Effect has missing dependencies: 'MySwal' and 'notes'. Either include them or remove the dependency array. You can also do a functional update 'setNotes (n => ...)' if you only need 'notes' in the 'setNotes' call. If I add notes and MySwal as dependencies, it gives me a continuous loop.
When I access the "note" page, the Note component will be rendered.
Then, with pagination: / note? Page = 2 & per-page = 10, it went perfectly.
However, when returning to "/ note" the page does not experience a re-render.
Strangely, if a route like this / note? Page = 1 & per-page = 10, returns perfectly.
Does my useEffect not run after pagination?
First of all, move your API call inside of useEffect. After your data is fetched, then you can change the state.
useEffect(() => {
//Fetch the data here
//setState here
},[]) //if this array is empty, you make the api call just once, when the `component mounts`
Second Argument of useEffect is a dependancy array, if you don't pass it, your useEffect will trigger in every render and update, which is not good. If you parss an empty array, then it makes just one call, if you pass a value, then react renders only if the passed value is changed.