React Router 4, How do I get a valid match url with route render or with withRouter? - react-router-v4

The match object I receive using either the render property on the Route component or using an HOC from withRouter always produces a match object that is wrong. The location object is correct. The match url property is always '/'
Here is a code pen that shows the problem React Router 4 match woes
const { render } = ReactDOM
const {
HashRouter,
Route,
Link
} = ReactRouterDOM
const App = () => (
<HashRouter>
<div>
<AddressBar/>
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/about">About</Link></li>
<li><Link to="/topics">Topics</Link></li>
</ul>
<hr/>
<Route exact path="/" component={Home}/>
<Route path="/about" component={About}/>
<Route path="/topics" component={Topics}/>
</div>
</HashRouter>
)
const Home = () => (
<div>
<h2>Home</h2>
</div>
)
const About = ({ match }) => (
<div>
<h2>About</h2>
<h3>Match: {match.url}</h3>
</div>
)
const Topics = ({ match }) => (
<div>
<h2>Topics</h2>
<ul>
<li><Link to={`${match.url}/rendering`}>Rendering with React</Link></li>
<li><Link to={`${match.url}/components`}>Components</Link></li>
<li><Link to={`${match.url}/props-v-state`}>Props v. State</Link></li>
</ul>
<Route path={`${match.url}/:topicId`} component={Topic}/>
<Route exact path={match.url} render={() => (
<h3>Please select a topic.</h3>
)}/>
</div>
)
const Topic = ({ match }) => (
<div>
<h3>{match.params.topicId}</h3>
<h3>Match: {match.url}</h3>
</div>
)
const AddressBar = () => (
<Route render={({ location: { pathname }, match: {url}, goBack, goForward }) => (
<div className="address-bar">
<div>
<button
className="ab-button"
onClick={goBack}
>◀︎</button>
</div>
<div>
<button
className="ab-button"
onClick={goForward}
>▶</button>
</div>
<div className="url">URL: {pathname} Match: {url}</div>
</div>
)}/>
)
render(<App/>, document.getElementById('root'))
Click on the About link (Topics has the same issue): notice the AddressBar, which uses Route render, at the top always shows
> Match: /
while at the bottom of the screen the Component displays the correct match.url
Match: /about
What am I doing wrong? How do I get a valid match object into the AddressBar?
Here is the ugly hack I used for my specific route where I need to know the specific 'league' parameter.
let keys = []
let path = '/*/:league/:id';
if (this.path.split('/').length === 3)
path = '/*/:league';
let re = pathToRegexp(path, keys);
const result = re.exec(this.path)
if (result && result[2])
this.store.setLeague(result[2]);

If I understand your question correctly you're looking to be able to use React Router's matching tools to pull paramaters out of a URL. This worked for me
import { matchPath } from 'react-router'
const match = matchPath('/users/123', {
path: '/users/:id',
exact: true,
strict: false
})
Source: https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/api/matchPath.md

I guess whenever you need to use match, you also need to combine it with pathname
example
var match = this.props.match;
<Link className = 'xxx' to = {{pathname:match.url + '/target'}}

Related

Why the text isn’t updated in Vue3?

I’m trying to display a name dynamically, but I get the same name forEach element. What I’m trying to do is:
<template>
<div class="app__projects">
<div
class="app__projects__container"
v-for="project in visibleProjects"
:key="project.id"
:id="project.id"
>
<div class="app__projects__image">
<img
:src="project.imgUrl"
alt="Project Image"
width="570"
height="320"
loading="lazy"
/>
</div>
<div class="app__projects__content">
<h3>{{ project.name }}</h3>
<p>
{{ project.description }}
</p>
<a
:href="project.link"
target="_blank"
class="app__projects__content-btn"
>
{{ displayNameButton }}
</a>
<hr class="app__projects__content--spacer" />
</div>
</div>
<button
v-if="showMoreProjectsButton"
class="app__projects__showMoreButton"
#click="loadMoreProjects"
>
show more projects
</button>
</div>
</template>
On the I'm trying to display a name dynamically, and all the time the same name is displayed, but I want to display the name based on the computed property that I wrote below.
Here is the visibleProjects:
const visibleProjects = computed(() => {
return storeProjects.projects.slice(0, maxProjectsShown.value);
});
I’m trying to iterate through an array of objects from the store like:
const displayNameButton = computed(() => {
const isObjPresent = storeProjects.projects.find((o => o.wordpress === 'yes')).wordpress;
console.log(isObjPresent);
if (isObjPresent === 'yes') return 'See Website';
else if (!isObjPresent) return 'See code';
})
The array of objects from the store is:
import { defineStore } from 'pinia';
import { v4 as uuidv4 } from 'uuid';
export const useProjectsStore = defineStore({
id: 'projects',
state: () => {
return {
projects: [
{
id: uuidv4(),
imgUrl: lightImg,
name: 'use this',
description:
'track of this',
wordpress: false,
},
{
id: uuidv4(),
imgUrl: recogn,
name: 'deep lear',
description:
'I tried my best',
wordpress: ‘yes’,
},
...
{},
{},
],
};
},
});
So the problem is with your computed property. It will always return the same value because there is no input based on which the function can determine which string should it returns. Based on the code you already have I think you should write a method that will return desired string.
const displayNameButton = (project) => {
return (project.wordpress === 'yes') ? 'See Website' : 'See code';
})
and in the template
<a
:href="project.link"
target="_blank"
class="app__projects__content-btn"
>
{{ displayNameButton(project) }}
</a>
OR you can modify your visibleProjects:
const visibleProjects = computed(() => {
return storeProjects.projects.slice(0, maxProjectsShown.value).map((e) => {
const project = {...e};
project.wordpress = (project.wordpress === 'yes') ? 'See Website' : 'See code';
return project;
});
});
and in the template
<a
:href="project.link"
target="_blank"
class="app__projects__content-btn"
>
{{ project.wordpress }}
</a>

Having problems calling an API id

Hi I am having some problems figuring out how to access an id. I am making a twitter cloner and I have an Icon I would like to click and see what people are commenting on it. But I have hit a wall and cant figure out how to access the Id when the chatbubble is clicked.
Any help would be greatly appreciated. The API works perfectly and I can call an id through postman.
const GetData = (id) => {
axios.get('https://localhost:44368/api/users/{id}').then((response) => {
console.log(response.data, "list of heroes ");
});
};
return (
<div className="post">
<div className="ppictur">
<Avatar src={avatar} />
</div>
<div className="post_body">
<div className="post_header">
<div className="post_headerText">
<h3>
{displayName}
<span className="post_headerSpecial">
<VerifiedIcon className="post_badge" />
#{username}
</span>
</h3>
</div>
<div className="post_headerDesription">
<p>{text}</p>
</div>
</div>
<img src={image} alt="" />
<div className="post_footer">
<ChatBubbleOutlineIcon onClick={GetData()} />
<RepeatIcon fontSize="small" />
<FavoriteBorderIcon fontSize="small" />
<PublishOutlinedIcon fontSize="small" />
</div>
</div>
</div>
);
}
export default Post;
const [posts, setPosts] = useState([]);
useEffect(() => {
axios.get('https://localhost:44368/api/users').then((response) => {
console.log(response.data, "list of heroes ");
setPosts(response.data);
});
}, []);
const icon = document.getElementById("Dark");
function DarkM() {
document.body.classList.toggle("dark-theme");
}
return (
<div className="commentbox">
<div className="header">
<h2>Home</h2>
<DarkModeOutlinedIcon id="Dark" onClick={() => DarkM(icon)} />
</div>
<Opinion />
{posts.map(post => (
<Post
avatar={post.profilePicture}
displayName={post.name}
username={post.userName}
text={post.post}
image={post.image}
bull
/>
)).reverse()}
</div>
);
}
export default Feed; ```
Use template literal ES6 with backticks.
const GetData = (id) => {
axios.get(`https://localhost:44368/api/users/${id}`).then((response) => {
console.log(response.data, "list of heroes ");
});
};
Also when you call it, make sure to pass arguments.
<ChatBubbleOutlineIcon onClick={GetData(9)} />
In my other component i wasnt pushing all the data through. that is my props didnt know what i was trying to call
const objectToPass = {
postId, avatar, displayName, username, text, image
}
const showSingleData = (value) => {
navigate(`/tweet/${value.postId}`)
console.log(value)
}```
it was a problem with my other component

how to select a default value in q-select from quasar?

I need my q-select to select the "name" depending on the previously assigned "id".
Currently the input shows me the "id" in number and not the name to which it belongs.
<q-select
class="text-uppercase"
v-model="model"
outlined
dense
use-input
input-debounce="0"
label="Marcas"
:options="options"
option-label="name"
option-value="id"
emit-value
map-options
#filter="filterFn"
#update:model-value="test()"
>
<template v-slot:no-option>
<q-item>
<q-item-section class="text-grey"> No results </q-item-section>
</q-item>
</template>
</q-select>
Example I would like the name that has the id: 12 to be shown loaded in the q-select.
const model = ref(12);
const options = ref([]);
const filterFn = (val, update) => {
if (val === "") {
update(() => {
options.value = tableData.value;
});
return;
}
update(() => {
const needle = val.toLowerCase();
options.value = tableData.value.filter((v) =>
v.name.toLowerCase().includes(needle)
);
});
};
I'm running into the same issue and couldn't find a proper way to set default value of object type.
I ended up use find() to look for the default value in the options and assign it on page created event.
created() {
this.model = this.options.find(
(o) => o.value == this.model
);
}

vuejs problem change route get userChoice in localstorage but push on the profile route before getting new userChoice?

the problem is that on click I increments the new userChoice according to the user chosen but it pushes on the profile route before retrieving the new userChoice click.
what to do here is my template I will put everything in the same function change use the push at the end but it does not work either then I do 2 functions but it does not work either what is the solution ??
<template>
<section
class="stopPadMarg container-fluid d-md-flex justify-content-between"
>
<div class="py-5 stopPadMarg bg-primary col-md-1">
<img
src="../assets/image/icon.png"
width="60px"
class="rounded-circle"
alt="logo"
/>
</div>
<div class="largeur80">
<form class="justify-content-center form-inline py-3 my-2 my-lg-0">
<input
v-model="searchKey"
id="search"
class="form-control mr-sm-2"
type="search"
placeholder="Search"
aria-label="Search"
/>
</form>
<div>
<h3
class="backPrimaire opacity mx-1 text-primary bordurePost bordureRond"
>
<b-icon-chevron-double-down
class="mr-5 my-1 pt-1 text-secondary"
animation="cylon-vertical"
font-scale="1"
></b-icon-chevron-double-down>
Vos collegues
<b-icon-chevron-double-down
class="ml-5 my-1 pt-1 text-secondary"
animation="cylon-vertical"
font-scale="1"
></b-icon-chevron-double-down>
</h3>
</div>
<div class="hauteur">
<div class="mt-5 d-flex flex-wrap">
<div
v-for="(user, id) in filteredList"
v-bind:key="id"
class="col-md-3 d-flex flex-column align-items-center align-content-center"
>
<div #click="changeUser(user)" class="cursor">
<img
#click="changeRoute"
v-if="user.image_url !== null || ''"
:src="user.image_url"
width="100px"
height="100px"
class=" justify-content-left bordureProfil
rounded-circle"
/>
<img
v-else
src="../assets/image/icon.png"
width="100px"
class=" justify-content-left bordureProfil rounded-circle"
/>
</div>
<div>
<h5 class="mt-2">
{{ user.nom.toUpperCase() }}
</h5>
<h6 class="mb-3">{{ user.prenom.toLowerCase() }}</h6>
</div>
</div>
</div>
</div>
</div>
<div class="py-5 stopPadMarg bg-primary col-md-1">
<img
src="../assets/image/icon.png"
width="60px"
class="rounded-circle"
alt="logo"
/>
</div>
</section>
</template>
<script>
import axios from "axios";
export default {
components: {},
data() {
return {
searchKey: "",
postes: [],
users: [],
user_id: localStorage.getItem("userId"),
userChoice: localStorage.getItem("userChoice"),
};
},
async created() {
this.postes = [];
this.users = [];
await axios
.get("http://localhost:3000/postes")
.then(
(response) => ((this.postes = response.data), console.log(response))
)
.catch((error) => console.log(error));
await axios
.get("http://localhost:3000/users")
.then(
(response) => ((this.users = response.data), console.log(this.users))
)
.catch((error) => console.log(error));
await axios
.get("http://localhost:3000/users")
.then(
(response) => (
(this.userDef = response.data.find((user) => {
return user.id;
})),
console.log(this.userDef)
)
)
.catch((error) => console.log(error));
await axios
.get(`http://localhost:3000/user/${this.user_id}`)
.then(
(response) => (
(this.userConnect = response.data), console.log(this.userConnect.id)
)
)
.catch((error) => console.log(error));
await axios
.get("http://localhost:3000/commentaires")
.then(
(response) => (
(this.comments = response.data), console.log(this.comments)
)
)
.catch((error) => console.log(error));
},
computed: {
filteredList() {
return this.users.filter((user) => {
return user.nom.toLowerCase().includes(this.searchKey.toLowerCase());
});
},
},
methods: {
async changeUser(user) {
await localStorage.removeItem("userChoice");
await localStorage.setItem("userChoice", user.id);
this.$router.push(`/profil/${this.userChoice}`);
},
async changeRoute() {
await this.$router.push(`/profil/${this.userChoice}`);
},
},
};
</script>
<style></style>
and the picture here
if I press a second time on the same profile it gives it to me if I return to the colleagues page but not if I change profile there is an empty page
here picture of the routes path
in fact the route does not change profile and remains on 58 here c the profile of that which is connected and if we change number on the route it launches a page page so this is the problem with the path of the route that the we see in the browser cache
Having looked at your code it's obvious why you'd get an empty page when changing routes. Let me explain:
Your routes say this:
Register a route /profil/${userChoice} (which is a value read from localStorage).
This route definition is only read once, at page intialisation. So, when your page loads only /profil/58 will be defined, /profil/59 wont.
What you are probably looking for is route parameters:
https://router.vuejs.org/guide/essentials/dynamic-matching.html
You'd want the number part of this url to be dynamic and respond to changes.
So, instead of reading the value from localStorage, you would write:
{
path: '/profil/:user_id',
name: 'ProfilUser',
...
}
Now when your Profil components is initialized instead of accessing localStorage you read the provided value as follows:
created() {
var userChoice = this.$route.params.user_id;
}
(note it is also possible to get this param as a prop, consult the vue-router docs on how to do this)
Another thing you need to keep in mind is that you need to respond when this parameter changes. Your component will not be refreshed/remounted.
To respond to parameter changes you can do the following:
watch: {
'$route.params.user_id'() {
this.reloadAllStuff();
}
}
I would recommend to not use localStorage for this use case, let the URL parameter be the main source of truth.
Further reading:
https://qvault.io/2020/07/07/how-to-rerender-a-vue-route-when-path-parameters-change/

How can i paginate table

I have the following code to display data from json file, i have more that 500 records i want to display 10 records per page. Here is my project in [code pen][1] . I tried react-pagination library but that doesn't work. what is the best way to do this? Open to use any library recommended -- i tried almost all of them.
here is how my code looks like
I'm sure there are a hundred different ways of doing it, but just to teach the idea of the mechanics, here is a very manual version:
{this.state.filteredData
.slice(this.state.activePage * 10, (this.state.activePage + 1) * 10)
.map(results => ( ...
))}
.....
{/*Pagination goes here */}
<button onClick={() => {this.setState({activePage: this.state.activePage - 1})}} >
prev</button>
<button onClick={() => {this.setState({activePage: this.state.activePage + 1})}} >
next</button>
That is, you take only a slice of the data before mapping it into DOM elements, and the buttons for advancing or going back just select the slice by setting the activePage state variable you already had.
You could have something along the lines of an index and offset and then create chunks of your array.
Give this a try:
import React, { Component } from "react";
import { render } from "react-dom";
import Hello from "./Hello";
import cardData from "./response.json";
import "./style.css";
class App extends Component {
constructor() {
super();
const offset = 5;
console.log(cardData);
this.state = {
name: "React",
index: 0,
offset,
chunks: this.chunkArray(cardData.data.Table, offset)
};
}
chunkArray(inputArray, chunkSize){
console.log("inputArray:: ", inputArray);
const results = [];
while (inputArray.length) {
results.push(inputArray.splice(0, chunkSize));
}
console.log("results:: ", results);
return results;
}
handleClick(index) {
this.setState({
index
})
}
render() {
console.log(this.state.chunks);
return (
<div>
{this.state.chunks && this.state.chunks[this.state.index].map(results => (
<div className="col-sm-3">
<h3>
{results.first_name} {results.last_name}
</h3>
<h3>{results.manager}</h3>
<div className="row">
<div className="col-md-3 col-sm-6"> {results.Department}</div>
</div>
<a
to={{ pathname: `/cards/${results.id}`, state: results }}
className={`card-wrapper restore-${results.id}`}
href={`/cards/${results.id}`}
>
View Detail
</a>
</div>
))}
<br />
{ this.state.chunks && this.state.chunks.map((item, index) => <button onClick={() => this.handleClick(index)}>{index + 1}</button>) }
</div>
);
}
}
render(<App />, document.getElementById("root"));
Here's a Working Code Sample Demo for your ref.
If you're using hooks, this will work otherwise it can be easily adapted. Basically, just store the index of where you are and then get the data you need based on that index:
const [index, setIndex] = React.useState(0);
const PAGE_SIZE = 10;
const tableData = cardData.data.Table.slice(index, index + PAGE_SIZE);
const table = {tableData.map(results => (
<div className="col-sm-3">
<h3>
{results.first_name} {results.last_name}
</h3>
<h3 >{results.manager}</h3>
<div className="row">
<div className="col-md-3 col-sm-6"> {results.Department}</div>
</div>
<Link
to={{ pathname: `/cards/${results.id}`, state: results }}
className={`card-wrapper restore-${results.id}`}
>
View Detail
</Link>
</div>
))}
const navigation = (
<div>
<div disabled={index <= 0 ? true : false} onClick={() => setIndex(index - PAGE_SIZE)}>Prev</div>
<div disabled={index <= cardData.data.Table.length ? true : false} onClick={() => setIndex(index + PAGE_SIZE)}>Next</div>
</div>
);