How to setup default value of fetched data - javascript

I am fetching data from my "backend" CMS - everything works fine, but when I want to setup default value I am getting error of undefined data.
My content is divided into some categories e.g.
const [category1, setCategory1] = useState([]);
const [category2, setCategory2] = useState([]);
Then I am fetching data from backend
useEffect(() => {
const fetchData = async () => {
const result = await client.query(
Prismic.Predicates.at('document.type', 'post'),
{ pageSize: 100 }
);
if (result) {
const category1Arr = [];
const category2Arr = [];
result.results.forEach((post) => {
switch (post.data.category[0].text) {
case 'Category1':
category1Arr.push(post);
break;
case 'Category2':
category2Arr.push(post);
break;
default:
console.warn('Missing blog post category.');
}
});
setCategory1(category1Arr);
setCategory2(category2Arr);
return setDocData(result);
} else {
console.warn(
'Not found'
);
}
};
fetchData();
}, []);
Code above works without any issues, BUT chosen category should have one post opened by default.
I am having menu when you can pick category and therefore I am using activeComponent function.
const [activeComponent, setActiveComponent] = useState('category1');
const modifyActiveComponent = React.useCallback(
(newActiveComponent) => {
setActiveComponent(newActiveComponent);
},
[setActiveComponent]
);
So category1 is active on default, therefore the category should also have default post.
This is what I tried:
const [postTitle, setPostTitle] = useState('');
const [postText, setPostText] = useState([]);
{activeComponent === 'category1' &&
category1.length > 0 && category1.map((post) => {
return ( <button onClick={()=> {setPostTitle(post.data.title[0].text); setPostText(post.data.body)}}
And data are shown typical just as a {postTitle} & {postText}
I tried to put default value in each category like this
useEffect(() => {
if (activeComponent === 'category1') {
setPostTitle(category1[2].data.title[0].text);
setPostText(category1[2].data.body);
}
if (activeComponent === 'category2') {
// same here just with category2 }
}, [activeComponent, category1, category2]);
But the code above gives me an error or undefined data even though it should be correct.
How can I achieve to make a default value with this logic above? Everything works like charm, just the default data does not work :(
This is array of objects:

In your last piece of code you have a typo, here:
useEffect(() => {
if (activeComponent === 'category1') {
setPostTitle(category1[2].data.title[0].text);
setPostText(category[2].data.body);
}
if (activeComponent === 'category2') {
// same here just with category2 }
}, [activeComponent, category1, category2]);
it should be:
useEffect(() => {
if (activeComponent === 'category1') {
setPostTitle(category1[2].data.title[0].text);
setPostText(category1[2].data.body);
}
if (activeComponent === 'category2') {
// same here just with category2 }
}, [activeComponent, category1, category2]);
in the first if statement, in second setPostText, you have category instead of category1.

Related

Problem when I try to run two react-query in a row

I have two different endpoints, one that is called with getProjectMapping and one with getStaffing. The getProjectMapping query must be run first in order to set the project variable, which will then be used to make the getStaffing request. But I get the following error:
Uncaught TypeError: project is null
I get that error in the getStaffing request, although before activating it I check that the project is not null. Does anyone know what is wrong?
const Staffing = () => {
const { tokenApi } = useContext(LoginContext);
const [project, setProject] = useState(null);
const {
data: projectMapping,
isLoading: projectMappingIsLoading,
isFetching,
} = useQuery("ProjectMapping", () => getProjectMapping(tokenApi), {
onSuccess: () => {
if (projectMapping != null && projectMapping.length !== 0) {
setProject(projectMapping[0]);
}
},
});
const { data, isLoading } = useQuery(
[project.value, "Staffing"],
() => getStaffing(project.value, tokenApi),
{
enabled: !isFetching && project != null,
dependencies: [project],
}
);
}
This isn't how you structure dependent queries.. Instead of setting state you should derive it. If you have dependent queries it might also make sense to wrap them in a custom hook
e.g.
const useProjectStaffing = (tokenApi) => {
const {
data: [project] = [],
isLoading: projectMappingIsLoading,
} = useQuery("ProjectMapping", () => getProjectMapping(tokenApi), {
},
});
const projectValue = project && project.value
return useQuery(
[projectValue, "Staffing"],
() => getStaffing(projectValue, tokenApi),
{ enabled: !!projectValue }
);
}
const Staffing = () => {
const { tokenApi } = useContext(LoginContext);
const {isLoading, data: staffing} = useProjectStaffing(tokenApi);
// ... do stuff with the staffing data when it comes back.

React Native Firestore: How do I listen for database changes at the same time as using the .where() query?

I have made a FlatList that gets populated from a firestore database. I can currently do all the CRUD operations, but when I edit an entry, it doesn't change in the FlatList. It does change in the firestore database.
I suspect it's because I'm not using .onSnapshot(). My problem is that I need to filter the data using .where() and I haven't been able to find out how to combine the two operations.
My code looks like this:
export const Coach = () => {
const navigation = useNavigation();
const [user, setUser] = useState();
const [userName, setUserName] = useState('');
const [workoutIds, setWorkoutIds] = useState([]);
const [workouts, setWorkouts] = useState([]);
const userRef = firestore().collection('Users');
const workoutRef = firestore().collection('Workouts');
// Setting the user state
auth().onAuthStateChanged(userInstance => {
if (userInstance) {
setUser(userInstance);
}
});
// Getting coach id's from firestore - Started out at individual workout id's
useEffect(() => {
if (user) {
const subscriber = userRef.doc(user.uid).onSnapshot(userSnap => {
if (userSnap) {
setUserName(userSnap.data().Name);
setWorkoutIds(userSnap.data().Workouts);
}
});
return () => subscriber();
}
}, [user]);
// using the list of coach id's to get workouts
useEffect(() => {
if (workoutIds.length != 0) {
let workoutList = [];
workoutRef
.where(firestore.FieldPath.documentId(), 'in', workoutIds)
.get()
.then(query => {
query.forEach(snap => {
workoutList.push({...snap.data(), key: snap.id});
});
setWorkouts(workoutList);
});
}
}, [workoutIds]);
The problem should lie in the last useEffect block.
So how do I get it to listen for changes and update the FlatList, while still using the .where()?
----------------------------------------- Edit -----------------------------------------
I have tried to add an onSnapshot to my query:
Before:
// using the list of coach id's to get workouts
useEffect(() => {
if (workoutIds.length != 0) {
let workoutList = [];
workoutRef
.where(firestore.FieldPath.documentId(), 'in', workoutIds)
.get()
.then(query => {
query.forEach(snap => {
workoutList.push({...snap.data(), key: snap.id});
});
setWorkouts(workoutList);
});
}
}, [workoutIds]);
After:
// using the list of coach id's to get workouts
useEffect(() => {
if (workoutIds.length != 0) {
let workoutList = [];
workoutRef
.where(firestore.FieldPath.documentId(), 'in', workoutIds)
.onSnapshot(query => {
query.forEach(snap => {
workoutList.push({...snap.data(), key: snap.id});
});
setWorkouts(workoutList);
});
}
}, [workoutIds]);
It still doesn't update the view straight away and now I get an error about encountering two of the same keys.
To solve the issue I had to add .onSnapshot() to my query for it to listen to changes in the database. On top of that I accidentally put the temporary list that I added objects to, outside the onSnapshot(), so it just kept adding on. After moving the temporary list into the onSnapshot(), it now updates.
Before:
useEffect(() => {
if (workoutIds.length != 0) {
let workoutList = [];
workoutRef
.where(firestore.FieldPath.documentId(), 'in', workoutIds)
.get()
.then(query => {
query.forEach(snap => {
workoutList.push({...snap.data(), key: snap.id});
});
setWorkouts(workoutList);
});
}
}, [workoutIds]);
After:
useEffect(() => {
if (workoutIds.length != 0) {
workoutRef
.where(firestore.FieldPath.documentId(), 'in', workoutIds)
.onSnapshot(query => {
let workoutList = [];
query.forEach(snap => {
workoutList.push({...snap.data(), key: snap.id});
});
setWorkouts(workoutList);
});
}
}, [workoutIds]);

How do I refetch data after specific event triggered (click a button) using SWR, React Hooks for Data Fetching [duplicate]

This component is for counting views at page level in Next.js app deployed on AWS Lambda
function ViewsCounter({ slug }: { slug: string }) {
const { data } = useSWR(`/api/views/${slug}`, fetcher);
const views = new Number(data?.total);
useEffect(() => {
const registerView = () =>
fetch(`/api/views/${slug}`, { method: "POST" })
.catch(console.log);
registerView();
}, [slug]);
return (
<>
{views}
</>
);
}
This one is for displaying views on homepage
function ViewsDisplay({ slug }: { slug: string }) {
const { data } = useSWR(`/api/views/${slug}`, fetcher);
const views = new Number(data?.total);
return (
<>
{views}
</>
);
}
While it works as expected on localhost, looks like it displays only the first fetched value and doesn't revalidate it for some reason.
When visiting the page, Counter is triggered correctly and the value is changed in DB.
Probably it has something to do with mutating, any hints are appreciated.
useSWR won't automatically refetch data by default.
You can either enable automatic refetch using the refreshInterval option.
const { data } = useSWR(`/api/views/${slug}`, fetcher, { refreshInterval: 1000 });
Or explicitly update the data yourself using a mutation after the POST request to the API.
function ViewsCounter({ slug }: { slug: string }) {
const { data, mutate } = useSWR(`/api/views/${slug}`, fetcher);
const views = new Number(data?.total);
useEffect(() => {
const registerView = () =>
fetch(`/api/views/${slug}`, { method: "POST" })
.then(() => {
mutate();
})
.catch(console.log);
registerView();
}, [slug]);
return (<>{views}</>);
}

How to check input for "Enter" key press

I'm working on a slightly complicated component that basically allows a user to type into an input, and then trigger a search (external API) for that product, the current issue however is that using the "Enter" key press, causes different behaviour and I want to sync up the behaviour of the "Find" button and "Enter". But before that I'm having some trouble on establishing where that check should happen, here's my React component:
export type CcceHook = {
allowForClassification: boolean,
classifyInProgress: boolean,
dataProfileId: string,
embedID: string,
handleCancelClassify: () => void,
handleClassify: (event?: SyntheticEvent<any>) => void,
handleCloseModal: () => void,
handleShowModal: () => void,
isDebugMode: boolean,
resultCode: string | null,
shouldShowModal: boolean,
};
// returns Ccce input fields based on the object form model - used in context provider
const getCcceValues = (object?: FormObjectModel | null) => {
const ccceInput: $Shape<CcceInput> = {};
//WHERE I THINK THE CHECK SHOULD GO (`ccceInput` is an object, with the `ccce.product` containing the users typed entry)
if (!object) {
return {};
}
// ccce input values
const ccceValues = object.attributeCollection.questions.reduce(
(acc, attribute) => {
const fieldEntry = ccceBeInformedFieldMap.get(attribute.key);
if (fieldEntry) {
acc[fieldEntry] = attribute.value;
}
return acc;
},
ccceInput
);
//check for null or empty string and if so hide "find goods button"
const productValueWithoutSpaces =
ccceValues.product && ccceValues.product.replace(/\s+/g, "");
const canClassify =
Object.values(ccceValues).every(Boolean) &&
Boolean(productValueWithoutSpaces);
return { canClassify, ccceValues };
};
export const useCcceEmbed = (
ccceResultAttribute: AttributeType,
onChange: Function
): CcceHook => {
const { object, form } = useFormObjectContext();
const [resultCode, setResultCode] = useState<string | null>(null);
const { canClassify, ccceValues } = getCcceValues(object);
const { handleSubmit } = useFormSubmit();
// data profile id is the 'api key' for 3ce
const dataProfileId = useSelector(
(state) => state.preferences[DATA_PROFILE_ID]
);
// data profile id is the 'api key' for 3ce
const isDebugMode = useSelector((state) => {
const value = state.preferences[CCCE_DEBUG_MODE_PREFERENCE];
try {
return JSON.parse(value);
} catch (error) {
throw new Error(
`3CE configuration error - non-boolean value for ${CCCE_DEBUG_MODE_PREFERENCE}: ${value}`
);
}
});
const [showModal, setShowModal] = useState<boolean>(false);
const handleCloseModal = useCallback(() => setShowModal(false), []);
const handleShowModal = useCallback(() => setShowModal(true), []);
// state value to keep track of a current active classification
const [classifyInProgress, setClassifyInProgress] = useState<boolean>(false);
// handle results from 3ce
const handleResult = useCallback(
(result) => {
if (result?.hsCode) {
onChange(ccceResultAttribute, result.hsCode);
setResultCode(result.hsCode);
setClassifyInProgress(false);
handleSubmit(form);
}
},
[ccceResultAttribute, form, handleSubmit, onChange]
);
const handleCancelClassify = useCallback(() => {
setClassifyInProgress(false);
handleCloseModal();
}, [handleCloseModal]);
// handle 3ce classify (https://github.com/3CETechnologies/embed)
const handleClassify = useCallback(
(event?: SyntheticEvent<any>) => {
if (event) {
event.preventDefault();
}
if (classifyInProgress || !canClassify) {
return;
}
const ccce = window.ccce;
if (!ccceValues || !ccce) {
throw new Error("Unable to classify - no values or not initialised");
}
setClassifyInProgress(true);
const classificationParameters = {
...ccceValues,
...DEFAULT_EMBED_PROPS,
};
ccce.classify(
classificationParameters,
handleResult,
handleCancelClassify
);
},
[
classifyInProgress,
canClassify,
ccceValues,
handleResult,
handleCancelClassify,
]
);
return {
allowForClassification: canClassify && !classifyInProgress,
classifyInProgress,
dataProfileId,
embedID: EMBED_ID,
handleCancelClassify,
handleClassify,
handleCloseModal,
handleShowModal,
isDebugMode,
resultCode,
shouldShowModal: showModal,
};
};
I have added a comment on where I think this logic should be handled (search "//WHERE I THINK..") - however, I'm unsure how to go from knowing the value of the users input, to checking for an enter press, I'm happy just to be able to console.log a user's key press, I should be able to tie up the logic from there, any advice would be really helpful.
TIA!

keeping a local variable in react

I want to add a pagination to my app for this reason i coded below code but there is a problem.
Here is my useEffect:
useEffect(() => {
let x = null;
const unsubscribe = chatsRef
.orderBy("createdAt", "desc")
.limit(10)
.onSnapshot((querySnapshot) => {
const messagesFirestore = querySnapshot
.docChanges()
.filter(({ type }) => type === "added")
.map(({ doc }) => {
const message = doc.data();
x = message;
return { ...message, createdAt: message.createdAt.toDate() };
});
appendMessages(messagesFirestore);
if (latestMessage != null) {
if (
new Date(
latestMessage["createdAt"]["seconds"] * 1000 +
latestMessage["createdAt"]["nanoseconds"] / 1000000
) >
new Date(
x["createdAt"]["seconds"] * 1000 +
x["createdAt"]["nanoseconds"] / 1000000
)
) {
latestMessage = x;
}
} else {
latestMessage = x;
}
});
return () => unsubscribe();
}, []);
I got the data from my database and i saved the oldest data in to latestMessage (for pagination) but the problem is that:
I declared my latestMessage out of my function like that:
let latestMessage = null;
export default function ChatTutor({ route }) {
...
}
And I passed my props to ChatTutor component (chatRoomId, username...) and according to that id, the room and its data are rendered. But the latestMessage always set some value and when i go to parent component and clicked another chatRoom, ChatTutor has a value of latestMessage's other value(oldest value). How can i set latestMessage null when i go to the parent ?
You can use useRef to store local mutable data (it would not participate in re-renders):
export default function ChatTutor({ route }) {
const latestMessage = useRef(null); // null is initial value
// ...
latestMessage.current = 'some new message' // set data
console.log(latestMessage.current) // read data
return <>ChatTutor Component</>
}

Categories