React: Reloading the page doesn't fetch the data anymore - javascript

When I am on my home page and click on a "lesson" component, the lesson page loads, takes the id of the lesson from the url and gets the data (from a local js file) to populate the lesson page. Well, that only happens when I click from the home page. But when I'm already on the lesson page with the data populated and I reload the page, I takes the id from the url but this time the data appear "undefined"... I don't understand why it doesn't take the data as previously?
Here is my component implementation:
const Lesson = () => {
const { id } = useParams();
const [lesson, setLesson] = useState(getLesson(id));
console.log("id: ", id);
console.log("lesson: ", lesson);
return (...);
};
Here is the console when I click on my lesson component from the home page:
the console when it works
There is the console when I simply reload the lesson page: the console when it doesn't work
I tried using useEffect() with a setLesson(getLesson(id)) inside but nothing changed...
I also tried this:
if (id) lesson = getLesson(id);
But again, it didn't work... :(
getLesson() gets its data from this file called fakeLessonsService.js:
import getHipHopLessons from "./hipHopLessons";
const lessons = [...getHipHopLessons()];
export function getLesson(lessonId) {
return lessons.find((lesson) => lesson._id === lessonId);
}
The file "./hipHopLessons" simply returns an array of lesson objects.
getLesson() is only loaded on this page.

The argument to useState is only used on initial render.
You should use a useEffect to update the state if the id changes
const [lesson, setLesson] = useState({});
useEffect(() => {
getLesson(id).then(val => setLesson(val));
}, [id]);
PS. Make sure getHipHopLessons is not asynchronous
If it is async, then you must write the code like
import getHipHopLessons from "./hipHopLessons";
export async function getLesson(lessonId) {
const lessons = getHipHopLessons();
return lessons.find((lesson) => lesson._id === lessonId);
}

Related

Fetching data from server in Remix.run

I was exploring Remix.run and making a sample app. I came across an issue that has been bothering me for some time now. Correct me if I am wrong: action() is for handling Form submission, and loader() is for fetching data initially.
For my application, I used mongoose to connect MongoDB, defined models, and defined querying function in a query.server.ts file. I want to fetch data from the database through a function defined in the query.server.ts file when an image is clicked on the UI. How can I do that without using forms? I cannot pre-fetch the data without knowing what image was clicked by the user.
You can create a resource route. These are like regular routes, but don't export a default component (no UI).
You can use the useFetcher hook and call fetcher.load() to call your resource route. The data is in fetcher.data.
// routes/query-data.ts
export const loader: LoaderFunction = async ({request}) => {
const url = new URL(request.url)
const img = url.searchParams.get('img')
const data = await getData(img)
return json(data)
}
// routes/route.tsx
export default function Route() {
const fetcher = useFetcher()
const handleImgClick = (e) => {
const img = e.target
fetcher.load(`/query-data?img=${img.attr('src')}`)
}
return (
<div>
<img onClick={handleImageClick} src="/images/file.jpg" />
<pre>{ JSON.stringify(fetcher.data, null, 2) }</pre>
</div>
)
}

Strange behavior after React state update with new entry

In my CRUD application, I am facing a problem every time the initial state ( array list ) updates with a new entry.
Clicking on an add button should open a modal and fill out a react-hook-form. On submit should update the state by adding the new entry.
Clicking on the edit button should open a modal and load data to the react-hook-form. On submit should update the state by updating the corresponding entry.
Everything works fine until I add a new entry in the state.
The entry is displayed in the table and clicking the edit button of that or any other entry works fine. When clicking the button of any entry, again the isEdit state stops to change as it should. I have a working demo here
In App.js
These are the states in the application. One for users list, one for distinguishing between add and update functions, and the last for passing the default values to the form when updating a user.
const [users, setUsers] = useState([]);
const [isEdit, setIsEdit] = useState(false);
const [defaultValues, setDefaultValues] = useState(initialDefaultValues);
There is a list of users coming from a GET request. When the request resolves successfully, I set the returned data to the state.
// get Users
const fetchUsers = async () => {
const res = await fetch("https://jsonplaceholder.typicode.com/users");
const resData = await res.json();
if (res.status === 200) {
setUsers(resData);
} else {
alert(resData);
}
};
// execute on component render
useEffect(() => {
fetchUsers();
}, []);
There is a component that renders a react-bootstrap-table-next and takes the data from the state.
<UsersTable
data={users}
prepareUpdateUser={prepareUpdateUser}
prepareDeleteUser={prepareDeleteUser}
/>
This table has two buttons for each entry. An edit button and a delete button. On click these buttons the two prepare functions are executed accordingly (prepareUpdateUser, prepareDeleteUser). This is the code for the prepareUpdateUser(). The function takes the user as an argument, changes the isEdit state to true, updates the defaultValues to pass to the react-hook-form, and finally opens the modal.
const prepareUpdateUser = (user) => {
setIsEdit(true);
setDefaultValues({
id: user.id,
name: user.name,
email: user.email
});
toggleUserModal();
};
When the modal close, I reset the isEdit and `defaultValues`` state to their initial values.
const [userModal, toggleUserModal] = useModali({
animated: true,
title: isEdit ? "Update User " : "Add User ",
onHide: () => {
isEdit && setIsEdit(false);
setDefaultValues(initialDefaultValues);
}
});
The problem is that after adding a new entry in the state and then try to click the edit button of any entry, everything works, but the next time you click the button the isEdit state stops updating every time the modal closes, and each time the prepareUpdateUser runs. The problem does not appear when updating an entry.
This is the code to add a new user to the users list
const addUser = (data) => {
const newArray = users.slice();
newArray.splice(0, 0, data);
setUsers(newArray);
toggleUserModal();
};
This is the code to update a user
const updateUser = (data) => {
const updatedUser = users.findIndex((user) => user.email === data.email);
const newArray = users.slice();
newArray[updatedUser] = data;
setUsers(newArray);
toggleUserModal();
};
If you are going to use the demo link, here are the steps to reproduce the problem.
Click on any edit button. Everything loads and the isEdit state is true.
Click the add button. The form is empty and the isEdit state is false.
Add a new entry.
The list updates with the new entry.
Click an edit button. Seems to work.
Click again an edit button and now isEdit is false and no data loaded in the form
Has anyone faced something similar? I am fairly new to react, and I cannot understand why this happens. Any advice will be really helpful.
Thank you all in advance.
**I have just modified below block of code**
const prepareUpdateUser = (user) => {
setIsEdit(true);
setDefaultValues({
id: user.id,
name: user.name,
email: user.email
});
};
**Newly added one useEffect**
useEffect(() => {
if (isEdit) {
toggleUserModal();
}
}, [isEdit]);
try this. Please give your valuable feedback

Wait for data in mapstate to finish loading

I have stored a userProfile in Vuex to be able to access it in my whole project. But if I want to use it in the created() hook, the profile is not loaded yet. The object exists, but has no data stored in it. At least at the initial load of the page. If I access it later (eg by clicking on a button) everything works perfectly.
Is there a way to wait for the data to be finished loading?
Here is how userProfile is set in Vuex:
mutations: {
setUserProfile(state, val){
state.userProfile = val
}
},
actions: {
async fetchUserProfile({ commit }, user) {
// fetch user profile
const userProfile = await fb.teachersCollection.doc(user.uid).get()
// set user profile in state
commit('setUserProfile', userProfile.data())
},
}
Here is the code where I want to acess it:
<template>
<div>
<h1>Test</h1>
{{userProfile.firstname}}
{{institute}}
</div>
</template>
<script>
import {mapState} from 'vuex';
export default {
data() {
return {
institute: "",
}
},
computed: {
...mapState(['userProfile']),
},
created(){
this.getInstitute();
},
methods: {
async getInstitute() {
console.log(this.userProfile); //is here still empty at initial page load
const institueDoc = await this.userProfile.institute.get();
if (institueDoc.exists) {
this.institute = institueDoc.name;
} else {
console.log('dosnt exists')
}
}
}
}
</script>
Through logging in the console, I found out that the problem is the order in which the code is run. First, the method getInstitute is run, then the action and then the mutation.
I have tried to add a loaded parameter and played arround with await to fix this issue, but nothing has worked.
Even if you make created or mounted async, they won't delay your component from rendering. They will only delay the execution of the code placed after await.
If you don't want to render a portion (or all) of your template until userProfile has an id (or any other property your users have), simply use v-if
<template v-if="userProfile.id">
<!-- your normal html here... -->
</template>
<template v-else>
loading user profile...
</template>
To execute code when userProfile changes, you could place a watcher on one of its inner properties. In your case, this should work:
export default {
data: () => ({
institute: ''
}),
computed: {
...mapState(['userProfile']),
},
watch: {
'userProfile.institute': {
async handler(institute) {
if (institute) {
const { name } = await institute.get();
if (name) {
this.institute = name;
}
}
},
immediate: true
}
}
}
Side note: Vue 3 comes with a built-in solution for this pattern, called Suspense. Unfortunately, it's only mentioned in a few places, it's not (yet) properly documented and there's a sign on it the API is likely to change.
But it's quite awesome, as the rendering condition can be completely decoupled from parent. It can be contained in the suspensible child. The only thing the child declares is: "I'm currently loading" or "I'm done loading". When all suspensibles are ready, the template default is rendered.
Also, if the children are dynamically generated and new ones are pushed, the parent suspense switches back to fallback (loading) template until the newly added children are loaded. This is done out of the box, all you need to do is declare mounted async in children.
In short, what you were expecting from Vue 2.

Link component interpolation error nextjs

I am getting this error in Next.js:
Error: The provided 'href' (/subject/[subject]) value is missing query values (subject) to be interpolated properly. Read more: https://err.sh/vercel/next.js/href-interpolation-failed`.
I have a dynamic page set up as /subject/[subject].tsx. Now in my navigation I have:
<Link href={'/subject/${subject}'} passHref><a>{subject}</a></Link>
It works fine when I access the page with different paths but when I am pressing on a button on the page it throws the error above which I imagine is because the component rerenders. If you go to the page in the error it says: Note: this error will only show when the next/link component is clicked not when only rendered.
I have tried to look for a solution and I tried doing:
<Link href={{pathname: '/subject/[subject]', query: {subject: subject}}}></Link>
but nothing changed. I read the docs and it seems that the as prop is only an optional decorator that is not used anymore so I fail to see how that can help me.
I got the same issue when trying to redirect user to locale. I did it in useEffect. After investigate I discovered that on first render router.query is empty, so it's missing required field id. I fix it by using router.isReady
export const useUserLanguageRoute = () => {
const router = useRouter()
useEffect(() => {
const {
locales = [],
locale,
asPath,
defaultLocale,
pathname,
query,
isReady // here it is
} = router
const browserLanguage = window.navigator.language.slice(0, 2)
const shouldChangeLocale =
isReady && // and here I use it
locale !== browserLanguage
&& locale === defaultLocale
&& locales.includes(browserLanguage)
if (shouldChangeLocale) {
router.push(
{
pathname,
query,
},
asPath,
{ locale: browserLanguage }
)
}
}, [router])
}
Another possible solution could be redirect using the router.push function:
const myRedirectFunction = function () {
if (typeof window !== 'undefined') {
router.push({
pathname: router.pathname,
query: {...router.query, myqueryparam: 'myvalue'},
})
}
}
return (
<>
<button onClick={() => {myRedirectFunction()}}> Continue </button>
</>
)
It is important to include ...router.query because there is where the [dynamic] current value is included, so we need to keep it.
Reference: https://github.com/vercel/next.js/blob/master/errors/href-interpolation-failed.md
You better do a null || undefined check in prior.
#Bentasy, Like you rightly pointed out, any re-render of the page is expecting you to pass in the dynamic route params again. The way I avoided page re-rendering with subsequent clicks on the page was to replace Link tag on the page with a label tag. I was using the Next Link tag to navigate between the tabs on this image which generated this same error. So I replaced the Link tags with label tags and the error was solved.
I was having the same problem, this is how I solved it.
While pushing something to the router, we need to give the old values ​​in it.
In that:
const router = useRouter()
router.push({
...router,
pathname: '/your-pathname'
})
try add this function, it work for me:
export async function getServerSideProps(context) {
return {
props: {},
};
}

React how to call a method from store and from props

I'm trying to make page pagintation in my model . I've used #material-ui TablePagination . The thing is, I'm using a web service to load my data and I don't want to store all the data inside the page, so I'm using the API paging offered (send parameters to the url for the correct page) .
Now to my code :
<TablePagination
component="div"
count={props.totalTableRows}
rowsPerPage={props.rowsPerPage}
page={props.brokersListPage}
onChangePage={props.setBrokersListPage}
rowsPerPageOptions = {[props.rowsPerPage]}
/>
And the setBrokersListPage :
export const setBrokersListPage = (event, page) => {
return dispatch => {
dispatch({
type: actionNames.SET_BROKERSLIST_PAGE,
page
})
getBrokers(page)
}
}
This code doesn't work . I need the dispatch action to refresh the state of the page , and I need the getBrokers to call the web service once again with the correct info . But all this does is update the page state without updating the data.
If I use this :
export const setBrokersListPage = (event, page) => {
return getBrokers(page)
}
Then the page refreshes , but then the state doesn'
tget refreshed .
How can I achieve both ?
You have to make an API call first, then pass this data to store, connected component will update afterwards. In your code i suppose you don't use the actual data from web service.
Here is simplified example:
Promise.resolve()
.then((page) => {
store.dispatch({
type: LOADING_BROKERS
});
return Api.getbrokers(page);
//get brokers here
})
.then(() => {
store.dispatch({
type: LOADED_DATA_BROKERS,
pageinfo // or whatever
});
})

Categories

Resources