Accessing Parameters in SolidJS Router - solid-js

I am trying to rewrite a project I made in React in Solid. I am trying to use the Solid Router as the documentation advises.
Here are my components so far.
index.js
import { render } from 'solid-js/web'
import { Router } from '#solidjs/router'
import './index.css'
import App from './App'
render(
() => (
<Router>
<App />
</Router>
),
document.getElementById('root')
)
App.jsx
import Header from './components/Header'
import styles from './App.module.css'
import Navbar from './components/Navbar'
import Topics from './components/Topics'
function App() {
return (
<div className={styles.container}>
<Header />
<Navbar />
<Routes>
<Route path="/" element={<Articles />} />
<Route path="/:topic" component={<Topics />} />
</Routes>
</div>
)
}
export default App
Navbar.jsx
import { NavLink } from '#solidjs/router'
import { getTopics } from '../utils/api'
const Navbar = () => {
const [topics, setTopics] = createSignal([])
onMount(() => {
getTopics().then(({ topics }) => {
setTopics(topics)
})
})
return (
<nav>
<ul>
<For each={topics()}>
{topic => (
<li>
<NavLink href={`/${topic.slug}`}>{topic.slug}</NavLink>
</li>
)}
</For>
</ul>
</nav>
)
}
export default Navbar
The problem I think seems to be in the component below
Topics.jsx
import { useParams } from '#solidjs/router'
import { createSignal, For, onMount, createResource } from 'solid-js'
import { getTopicArticles } from '../utils/api'
const Topics = () => {
const { topic } = useParams()
console.log(topic)
return (
<div>
<h1>{topic}</h1>
</div>
)
}
export default Topics
The params seem to be undefined no matter what. I understand that Solid router is not exactly the same as React-Router but for this simple example I can't see where I am going wrong.
The desired outcome is to be able to click on the NavLink in the Navbar.jsx component and that routes to the desired path, for example http://localhost:3000/cooking and render the topic I need, but the params are always undefined.
This is the result of the api call, api/articles?topic=undefined
The desired result is to attach the param at the end of the api with useParams, just like in my React version
Edit: below is the Topics.jsx component updated to a working version, not sure if it is the best way.
import { useParams } from '#solidjs/router'
import { getTopicArticles } from '../utils/api'
import Article from './Article'
const Topics = () => {
const params = useParams()
const [articles, setArticles] = createSignal([])
const [loading, setLoading] = createSignal(true)
createEffect(() => {
setLoading(true)
getTopicArticles(params.topic).then(({ articles }) => {
setArticles(articles)
setLoading(false)
})
})
return (
<>
{loading() && <div>Loading...</div>}
<h2>{params.topic}</h2>
<For each={articles()}>{article => <Article article={article} />}</For>
</>
)
}
export default Topics

Could be related to the object returned from useParams is being reactive. console.log returns an empty object but destructing outputs the values as expected. That is because of the proxy and totally normal.
Retrieves a reactive, store-like object containing the current route path parameters as defined in the Route.
https://github.com/solidjs/solid-router#useparams
Also regular query parameters like ?id=1&name=John does not work with useParams, for those use useSearchParams.
import { render } from "solid-js/web";
import {
Router,
useParams,
useSearchParams,
Route,
Routes,
Link
} from "#solidjs/router";
const Home = () => {
const [params, setParams] = useSearchParams();
console.log({ ...params });
return <div>Home</div>;
};
const Blog = () => {
const params = useParams();
console.log({ ...params });
return <div>Blog {JSON.stringify(params)}</div>;
};
const App = () => {
return (
<Router>
<ul>
<li>
<Link href="/?id=1&name=john">Home</Link>
</li>
<li>
<Link href="/blog/js/1">Blog</Link>
</li>
</ul>
<Routes>
<Route path="/" component={Home} />
<Route path="/blog/:category/:id" element={Blog} />
</Routes>
</Router>
);
};
render(App, document.getElementById("app")!);
Check https://codesandbox.io/s/solid-router-demo-forked-71ef9x?file=/index.tsx for live demo.
Also, we pass component name to the component prop like so:
<Route path="/" component={Home} />

Related

Navigatiion Component using #mui/material not rendering [duplicate]

I have been learning React for few days and I wrote simple project (single page application). Problem is that my page doesn't show anything - it's a blank page.
App.js
import './App.css';
import {BrowserRouter as Router,Routes,Route,} from "react-router-dom";
import { Home } from './components/Home';
import { Wallet } from './components/Wallet';
function App() {
return (
<Router>
<Routes>
<Route exact path="/" component={Home}/>
<Route path="/wallet" component={Wallet}/>
</Routes>
</Router>
);
}
export default App;
Wallet.js
import React from "react";
export function Wallet() {
return (
<div>
<h1>Wallet Page!</h1>
</div>
);
}
Home.js
import React from "react";
export function Home() {
return (
<div>
<h1>Home Page!</h1>
</div>
);
}
So when I go to http://localhost:3001/ or http://localhost:3001/wallet I receive blank page. Could someone point me where I made a mistake?
In react-router-dom#6 the Route components render the routed content on the element prop as a ReactNode, i.e. as JSX. There is no longer any component, or render and children function props.
Routes and Route
declare function Route(
props: RouteProps
): React.ReactElement | null;
interface RouteProps {
caseSensitive?: boolean;
children?: React.ReactNode;
element?: React.ReactNode | null;
index?: boolean;
path?: string;
}
Move the components into the element prop and pass them as normal JSX instead of as a reference to a component.
<Router>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/wallet" element={<Wallet />} />
</Routes>
</Router>

Cannot read properties of undefined (reading 'label')

I am trying to implement Autocomplete using materialUI and nextJs. I encountered this error whenever I start the search.
My code is like this
client:
import Head from 'next/head'
import Image from 'next/image'
import styles from '../styles/Home.module.css'
import React, {useState} from 'react'
import TextField from "#mui/material/TextField"
import Autocomplete from "#mui/material/Autocomplete"
import axios from "axios"
const getString = async (str) =>{
try{
// let searchableString = str.replace(/,/g, "")
let url = "http://localhost:4000/searchpop?search=" + str;
let { data } = await axios.get(url)
return data
} catch (error){
console.log(error);
}
}
export default function Home() {
const [searchOption, setOption] = useState([]);
searchOption.map((obj)=>{
console.log(obj.population_mesh.cui_str);
})
const onChangeOne = async (e) =>{
if(e.target.value) {
let data = await getString(e.target.value)
setOption(data);
}
}
return (
<div className={styles.container}>
<Head>
<title>Create Next App</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<div style={{marginTop: 50}}>
<Autocomplete
freeSolo
filterOptions={(x)=> x}
onChange={(e)=> console.log(e)}
options= {searchOption ? searchOption.map((obj)=> obj.population_mesh.cui_str): []}
// options = {hello}
renderInput={(params)=>(
<TextField
{...params}
label="Search String"
onChange={(e) => onChangeOne(e)}
/>
)}
/>
</div>
</div>
)
}
I am very new to these technologies. So please help me in resolving this issue.
I also dont understand below. here is population_mesh is the field and cui_str is is subfield in the data base which i want to print for autocomplete
options= {searchOption ? searchOption.map((obj)=> obj.population_mesh.cui_str): []}

Problem whit sending my local position Leaflet

When I'm initializing the const it's like that
-lat and leg are available only inside the function and when I'm send them to the map (I'm usinge Leaflet) it doesn't show them and it gave me that error:
Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.
import {Component,useState,useEffect,useRef, ReactNode, CSSProperties, Link} from 'react';
import { MapContainer, TileLayer, Marker, Popup,useLeaflet,leafletElement} from 'react-leaflet'
function HomePage(){
const [lat, setLat] = useState(null);
const [lng, setLng] = useState(null);
const [status, setStatus] = useState(null);
if (!navigator.geolocation) {
setStatus('Geolocation is not supported by your browser');
} else {
setStatus('Locating...');
navigator.geolocation.getCurrentPosition((position) => {
setStatus(null);
setLat(position.coords.latitude);
setLng(position.coords.longitude);
console.log(lat,lng)
}, () => {
setStatus('Unable to retrieve your location');
});
}
const position = [lat,lng]
return(
<MapContainer center={position} zoom={13} scrollWheelZoom={false}>
<TileLayer
attribution='© OpenStreetMap contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<Marker position={position}>
<Popup>
A pretty CSS3 popup. <br /> Easily customizable.
</Popup>
</Marker>
</MapContainer>
)};
export default HomePage
Your component is re-rendering constantly. I'm not sure but I think below should work. You have to use useEffect hook and add a check on navigator at the start of component if(!navigator.geolocation) { return null; }

Use 'navigation' and 'route' inside header present in class - React-navigation v5

I'm stuck as I want to switch to the V5 version of react-navigation.
With v4, I used to pass my params and use them with :
Set :
this.props.navigation.navigate('MyDestination', {myParam: 'value'})
Get :
this.props.navigation.getParam('myParam')
With v5, some things changed and I now can't use the this.props.navigation since it's not seemed to be known by the app.
My code is splitted so I have my App.js that only refer to the Navigation class :
import React from 'react';
import { StyleSheet, Text, View } from 'react-native'
import Navigation from './navigation/Navigation'
export default function App() {
return (
<Navigation/>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
Then my Navigation file contains all the navigation mechanism (I did not added my TabBar yet, since I want to fix the base navigation first) :
import { NavigationContainer } from '#react-navigation/native'
import { createStackNavigator } from '#react-navigation/stack'
import { createBottomTabNavigator } from 'react-navigation-tabs'
import { StyleSheet, Image } from 'react-native'
import React from 'react'
import Home from '../components/Home'
import LendList from '../components/LendList'
import AddMoney from '../components/AddMoney'
import AddStuff from '../components/AddStuff'
import Settings from '../components/Settings'
import Test from '../components/Test'
function HomeScreen() {
return(
<Home/>
)
}
function LendListScreen() {
return(
<LendList/>
)
}
const Stack = createStackNavigator()
function App() {
return(
<NavigationContainer>
<Stack.Navigator initialRouteName="Home">
<Stack.Screen name="Home"
component={Home}
options={{ title: "Home Page"}}
/>
<Stack.Screen name="LendList"
component={LendList}
options={{ title: 'Liste' }}
/>
<Stack.Screen name="AddMoney"
component={AddMoney}
options={{ title: "Ajout Money"}}
/>
<Stack.Screen name="AddStuff"
component={AddStuff}
options={{ title: "Ajout Stuff"}}
/>
<Stack.Screen name="Settings"
component={Settings}
options={{ title: "Settings"}}
/>
<Stack.Screen name="Test"
component={Test}
options={{ title: "Test"}}
/>
</Stack.Navigator>
</NavigationContainer>
)
}
export default App
And then come each of my pages (coded with classes), and here is one example, Home.js (I removed all the Style part to shorten the code displayed here) :
import React from 'react'
import { StyleSheet, Text, Image, View, Button, TouchableOpacity } from 'react-native'
import Moment from 'react-moment'
import { CommonActions } from '#react-navigation/native'
import { useNavigation } from '#react-navigation/native'
class Home extends React.Component {
static navigationOptions = () => {
return {
headerRight: () => <TouchableOpacity style={styles.settings_touchable_headerrightbutton}
onPress={() => this.goToSettings()}>
<Image style={styles.settings_image}
source={require('../assets/ic_settings.png')} />
</TouchableOpacity>
}
}
constructor(props) {
super(props)
this._goToSettings = this._goToSettings.bind(this)
}
_updateNavigationParams() {
navigation.setParams({
goToSettings: this._goToSettings
})
}
componentDidMount(){
console.log("navigation")
this._updateNavigationParams()
}
_checkMoneyDetails(navigation){
navigation.navigate('LendList', {type: 'Money'})
}
_checkStuffDetails(navigation){
navigation.navigate('LendList', {type: 'Stuff'})
}
_checkPeopleDetails(navigation){
navigation.navigate('LendList', {type: 'People'})
}
_goToSettings = () => {
navigation.navigate('Settings')
}
render(){
const date = new Date();
const { navigation } = this.props;
return(
<View style={styles.main_container}>
<View style={styles.header_view}>
<Text style={styles.header_text}>GiViToMe</Text>
<Text style={styles.header_text}>Nous sommes le :{' '}
{/* TODO: Penser à gérer ensuite les formats de date étrangers */}
<Moment element={Text} format="DD/MM/YYYY" date={date}/>
</Text>
</View>
<View style={styles.lend_view}>
<Text style={styles.title_lend_text}>Vos prêts :</Text>
<View style={styles.money_stuff_view}>
<View style={styles.money_view}>
<View style={styles.money_data_view}>
<Image source={require('../assets/ic_money.png')} style={styles.home_img} />
<Text>XXX $</Text>
</View>
<Button title='Money' onPress={() => {this._checkMoneyDetails(navigation)}}/>
</View>
<View style={styles.stuff_view}>
<View style={styles.stuff_data_view}>
<Image source={require('../assets/ic_box.png')} style={styles.home_img} />
<Text>XXX objets</Text>
</View>
<Button title='Stuff' onPress={() => {this._checkStuffDetails(navigation)}}/>
</View>
</View>
<View style={styles.people_view}>
<View style={styles.people_data_view}>
<Image source={require('../assets/ic_people.png')} style={styles.home_img} />
<Text>XXX people</Text>
</View>
<Button title='People' onPress={() => {this._checkPeopleDetails(navigation)}}/>
</View>
</View>
<View style={styles.footer_view}>
<Text style={styles.text_footer_view}>a.vescera inc.</Text>
</View>
</View>
)
}
}
export default Home
My problem is that, per the online documentation, I saw that to use "navigation" or "route" within a class, I should use the const navigation = { this.props } after the render() function.
This problem is that, to use one specific function within the header, I have to bind it after the componentDidMount() function, but the value present under render() is not yet known.
How could I solve this ? (sure that in the given example, having all the code in the navigation part allow to use navigation and route easily but you understand that I have to split my code.
Thanks.
Ok, so each time the same, I try many days solving my problem, and when I finally decide to post on stack, I find a solution :).
So, if there's some performance issue or other you may see by looking at my code, do not hesitate to correct me. I just found that this solution solved my problem.
So within my Navigation.js file, I just passed the navigation and route objects to make them usable thanks to the props into my classes, like this :
function App() {
return(
<NavigationContainer>
<Stack.Navigator initialRouteName="Home">
<Stack.Screen name="Home"
component={Home}
options={({route, navigation}) => (
{headerTitle: 'Home Page',
route: {route},
navigation: {navigation}}
)}
/>
</Stack.Navigator>
</NavigatorContainer>
)}
then within my classes I just call to this.props.navigation or this.props.route and gather form these objects what I need.
Other thing is that, for those who would use this code to build something similar, I also had to change the way I display the header button.
I do not use the static navigationOptions = () => {} anymore. I just add directly the navigation.setOptions piece of code within the ComponentDidMount function like this:
navigation.setOptions({
headerRight: () => <TouchableOpacity style={styles.settings_touchable_headerrightbutton}
onPress={() => route.params.goToSettings()}>
<Image style={styles.settings_image}
source={require('../assets/ic_settings.png')} />
</TouchableOpacity>
})
I have to do it this way since I'm using a function declared in my class, so I have to bind it in the constructor like this this._goToSettings = this._goToSettings.bind(this) and then add it to the navigation.setParams function.
When navigation.setOptions code is written inside componentDidMount add this.props before navigation and route keyword. Below is the code snippet that worked for me.
this.props.navigation.setOptions({
headerRight: () => <TouchableOpacity style={styles.settings_touchable_headerrightbutton}
onPress={() => this.props.route.params.goToSettings()}>
<Image style={styles.settings_image}
source={require('../assets/ic_settings.png')} />
</TouchableOpacity>
})

react-google-maps StandaloneSearchBox set specific country restriction?

I am trying to set a specific country restriction using react-google-maps StandaloneSearchBox.
I have tried componentRestrictions, but I'm not sure how to use it.
Sharing my code below:
export const AutoCompleteSearchBox = compose(
withProps({
googleMapURL:googleMapUrl,
loadingElement: <div style={{ height: `100%` }} />,
containerElement: <div style={{ height: `400px`, top:'3px' }} />,
}),
lifecycle({
componentWillMount() {
const refs = {}
this.setState({
types: ['(regions)'],
componentRestrictions: {country: "bd"},
onSearchBoxMounted:ref =>{ refs.searchBox = ref; },
onPlacesChanged:()=>{
const places = refs.searchBox.getPlaces();
this.props.onPlacesChanged(places);
},
})
const options = {
types: ['(regions)'],
componentRestrictions:{ country: 'bd' }
}
},
}),
withScriptjs
)`(props =>
<div data-standalone-searchbox="">
<StandaloneSearchBox
ref={props.onSearchBoxMounted}
bounds={props.bounds}
onPlacesChanged={props.onPlacesChanged}
controlPosition={ window.google.maps.ControlPosition.TOP_LEFT}
>
<TextField
className={props.inputClass}
placeholder={props.inputPlaceholder}
label={props.inputLabel}
name={props.inputName}
value={props.inputValue}
onChange={props.inputOnChange}
helperText={props.inputHelperText}
error={props.inputError}
/>
</StandaloneSearchBox>
</div>
);`
How can I solve this problem?
You can't add such restrictions for the SearchBox results, but you can specify the area towards which to bias query predictions. Predictions are biased towards, but not restricted to, queries targeting these bounds.
If you want to show only specific places, then you can Google Place Autocomplete feature. For it you don't event need to use additional React libraries for Google Maps. Here's the example:
import React, { Component } from 'react';
import Script from 'react-load-script'
class LocationMap extends Component {
handleScriptLoad() {
const inputEl = document.getElementById('address-input');
/*global google*/
var options = {
//types: ['address'],
componentRestrictions: {country: 'by'}
};
this.autocomplete = new google.maps.places.Autocomplete(inputEl, options);
this.autocomplete.addListener('place_changed', this.handlePlaceSelect.bind(this));
}
handlePlaceSelect() {
console.log(this.autocomplete.getPlace());
}
render() {
return (
<section>
<Script
url="https://maps.googleapis.com/maps/api/js?key=API_KEY&v=3.33&libraries=places&language=en&region=US"
onLoad={this.handleScriptLoad.bind(this)}
/>
<div className="form-group">
<label htmlFor="address-map">Enter address</label>
<input type="text"
autoComplete="new-password"
className="form-control"
id="address-input"
name="address"/>
</div>
</section>
);
}
}
export default LocationMap;
Don't forget to add react-load-script package: npm i react-load-script --save