CartItems routing error using React-Router-Dom v6 - react-router

I am trying to introduce 'cartItems' functionality to my react-redux app and store the added data in the browser's local storage.
Indeed the problem raises when I try to show cart items by clicking on the cart link at the navbar section. The error message is 'GET http://localhost:3000/products/undefined 500 (Internal Server Error)' and 'Uncaught (in promise)'. and I don't know how to fix the issue.
Note: the same component 'CartScreen.js' would display the cart items in both cases, when adding new items to the cart & when also clicking on the cart link at the navbar.
Please follow the code snippets
Thanks & Regards
App.js
import Header from './components/Header';
import { Container } from 'react-bootstrap';
import HomeScreen from './screens/HomeScreen';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import ProductScreen from './screens/ProductScreen';
import CartScreen from './screens/CartScreen';
function App() {
return (
<Router>
<Header />
<main className="py-3">
<Container>
<Routes>
<Route path="/" element={<HomeScreen />} exact />
<Route path="/product/">
<Route path=":id" element={<ProductScreen />} />
<Route index element={<ProductScreen />} />
</Route>
<Route path="/cart" >
<Route index element={<CartScreen />} />
<Route path=":productid" element={<CartScreen />} />
</Route>
</Routes>
</Container>
</main>
<Footer />
</Router>
);
}
export default App;
ProductScreen.js
import { useParams, Link, useNavigate } from 'react-router-dom';
import {Row,Col,Image,ListGroup,Button,Card,Form} from 'react-bootstrap';
import Rating from '../components/Rating';
import { listProductDetails } from '../actions/productActions';
import { useDispatch, useSelector } from 'react-redux';
import Loader from '../components/Loader';
import Message from '../components/Message';
function ProductScreen() {
const { id } = useParams();
const navigate = useNavigate();
const [qty, setQty] = useState(1);
const dispatch = useDispatch();
const productListDetail = useSelector((state) => state.productDetail);
const { loading, error, product } = productListDetail;
useEffect(() => {
dispatch(listProductDetails(id));
}, [dispatch, id]);
const addToCartHandler = () => {
navigate(`/cart/${id}?qty=${qty}`);
};
return (
<div> <Link to={-1} className="btn btn-primary my-3">Go Back</Link>
{loading ? (<Loader />): error ? (<Message variant="danger">{error}</Message>) : (
<Row>
<Col md={6}>
<Image src={product.image} alt={product.name} fluid />
</Col>
<Col md={3}>
<ListGroup variant="flush">
<ListGroup.Item>
<h3> {product.name}</h3>
</ListGroup.Item>
<ListGroup.Item>
<Rating value={product.rating} text={`${product.numReviews} reviews`}
color={'#fae500'}/>
</ListGroup.Item>
<ListGroup.Item>Price: ${product.price}</ListGroup.Item>
<ListGroup.Item>
Description: {product.description}
</ListGroup.Item>
</ListGroup>
</Col>
<Col md={3}>
<Card>
<ListGroup variant="flush">
<ListGroup.Item>
<Row>
<Col> Price: </Col>
<Col>
<strong>${product.price} </strong>
</Col>
</Row>
</ListGroup.Item>
<ListGroup.Item>
<Row>
<Col> Status: </Col>
<Col>
<strong>
{product.countInStock > 0 ? 'In Stock' : 'Out of Stock'}
</strong>
</Col>
</Row>
</ListGroup.Item>
{product.countInStock > 0 && (
<ListGroup.Item>
<Row>
<Col> Qty </Col>
<Col xs="auto" className="my-1">
<Form.Control as="select" value={qty}
onChange={(e) => setQty(e.target.value)}>
{[...Array(product.countInStock).keys()].map((x) => (
<option key={x + 1} value={x + 1}>{x + 1}</option>))}
</Form.Control>
</Col>
</Row>
</ListGroup.Item>)}
<ListGroup.Item>
<Button onClick={addToCartHandler}
className="btn btn-primary container-fluid"
disabled={product.countInStock === 0}
type="button">
Add to Cart
</Button>
</ListGroup.Item>
</ListGroup>
</Card>
</Col>
</Row>
)}
</div>
);
}
export default ProductScreen;
CartScreen.js
import React, { useEffect } from 'react';
import { Col, ListGroup,Row,} from 'react-bootstrap';
import { useDispatch, useSelector } from 'react-redux';
import { useLocation, useNavigate, useParams, Link, Outlet } from 'react-router-dom';
import { addToCart } from '../actions/cartAction';
import Message from '../components/Message';
const CartScreen = () => {
const { search } = useLocation();
const { productid } = useParams();
const qty = search ? Number(search.split('=')[1]) : 1;
const dispatch = useDispatch();
const cart = useSelector((state) => state.cart);
const { cartItems } = cart;
useEffect(() => {
dispatch(addToCart(productid, qty));
},[dispatch, productid, qty]);
return (
<Row>
<Col md={8}> {cartItems.length === 0 ? (<Message variant="info">
Go Back To Home Page <Link to="/"></Link> </Message> ) : (
<ListGroup> {cartItems.map((x) => (
<ListGroup.Item key={x.product}>
{x.name} , {x.qty}
</ListGroup.Item> ))}
</ListGroup>)}
</Col>
<Col md={4}></Col>
</Row>
);
};
export default CartScreen;
cartReducers.js
import { CART_ADD_ITEM } from '../constants/cartConstants';
export const cartReducer = (state = { cartItems: [] }, action) => {
switch (action.type) {
case CART_ADD_ITEM:
const item = action.payload;
const existItem = state.cartItems.find((x) => x.product === item.product);
if (existItem) {
return {
...state, cartItems: state.cartItems.map((x) =>
x.product === existItem.product ? item : x),};}
else {
return {
...state, cartItems: [...state.cartItems, item],};}
default:
return state;
}
};
cartAction.js
import axios from 'axios';
import { CART_ADD_ITEM } from '../constants/cartConstants';
export const addToCart = (productid, qty) => async (dispatch, getState) => {
const { data } = await axios.get(`/products/${productid}`);
dispatch({
type: CART_ADD_ITEM,
payload: {
product: data._id,
name: data.name,
image: data.image,
price: data.price,
countInStock: data.countInStock,
qty,
},
});
localStorage.setItem('cartItems', JSON.stringify(getState().cart.cartItems));
};
store.js
import { legacy_createStore, combineReducers, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import { composeWithDevTools } from '#redux-devtools/extension';
import {
productDetailsReducer,
productListReducer,
} from './reducers/productReducers';
import { cartReducer } from './reducers/cartReducers';
const reducer = combineReducers({
productList: productListReducer,
productDetail: productDetailsReducer,
cart: cartReducer,
});
const cartItemsFromStorage = localStorage.getItem('cartItems')
? JSON.parse(localStorage.getItem('cartItems'))
: [];
const initialState = { cart: { cartItems: cartItemsFromStorage } };
const middleware = [thunk];
const store = legacy_createStore(
reducer,
initialState,
composeWithDevTools(applyMiddleware(...middleware))
);
export default store;

It seems the issue here might be resolved by using the same logic in my answer here to your other question regarding route matching. It wasn't explicitly called out as an issue or something the needed to be addressed/fixed (in other words, I thought you'd used the code and had an issue elsewhere), so adding an answer here for the specific axios issue and resolution.
It looks like navigating to "/cart" will result in both productid and qty being undefined/invalid values, and the useEffect hook is unconditionally dispatching the action to add the item & quantity. productid is undefined at axios.get(`/products/${productid}`) in the action creator.
You should only dispatch the addToCart action if there is a valid product id and a quantity to add.
const CartScreen = () => {
const { search } = useLocation();
const { productid } = useParams();
const qty = search ? Number(search.split('=')[1]) : 1;
const dispatch = useDispatch();
const cart = useSelector((state) => state.cart);
const { cartItems } = cart;
useEffect(() => {
if (productid && qty > 0) {
dispatch(addToCart(productid, qty)); // <-- only dispatch if valid
}
}, [dispatch, productid, qty]);
return (
<Row>
<Col md={8}>
{!cartItems.length
? (
<Message variant="info">
<Link to="/">
Go Back To Home Page
</Link>
</Message>
) : (
<ListGroup>
{cartItems.map((x) => (
<ListGroup.Item key={x.product}>
{x.name} , {x.qty}
</ListGroup.Item>
))}
</ListGroup>
)
}
</Col>
<Col md={4}></Col>
</Row>
);
};

Related

Rewriting code with useParams, deconstructuring not going well

Trying to upgrade react router to version 5.1.
I have a problem with the useParams, the exampleId is not correct. I'm definatelly missing something?
My Route file with the broken exampleId:
export default function MyExample() {
const { url } = useRouteMatch()
const history = useHistory()
const exampleCreateQuery = useExampleCreate()
return (
<>
<Header url={url} />
<Switch>
<Route path={url} exact>
</Route>
<Route path={`${url}/:id`} children={exampleId !== undefined ? <ExampleThing exampleId={exampleId} /> : null} />
</Switch>
</>
)
}
The route where the id comes from
export default function ExampleThing() {
const { id: exampleId } = useParams<{ id: string }>()
const exampleQuery = useExample(exampleId, { useErrorBoundary: false })
return (
<>
</>
)
}

TypeError: order is undefined

I'm very new to react-redux following this tutorial to create ecomme website ,so here's a order screen to place a payment after hitting the place order button but it is showing error saying 'order' is undefined
this is my OrderScreen.js i've tried adding order && in front of order but to no avail it return screen with no listed order items
import React ,{useEffect,useState}from 'react'
import { Link } from 'react-router-dom'
import { useDispatch,useSelector } from 'react-redux'
import { Row,Col,ListGroup,Image,Card,Button } from 'react-bootstrap'
import Loader from '../components/Loader'
import Message from '../components/Message'
import { getOrderDetails } from '../actions/orderActions'
//import axios from 'axios'
function OrderScreen({match}) {
const orderId=match.params.id
const orderDetails=useSelector(state=>state.orderDetails)
const{order,error,loading}=orderDetails
const dispatch = useDispatch()
if (!loading && !error){
order.itemsPrice=order.orderItems.reduce((acc,item)=>acc+item.price*item.qty,0).toFixed(2)
}
useEffect(()=>{
if ( !order || order._id !== Number(orderId)){
dispatch (getOrderDetails(orderId))
}
},[dispatch,order,orderId])
return (
<div>
<Col md={8}>
<ListGroup variant='flush'>
<ListGroup.Item>
<h2>Shipping</h2>
<p>
<strong>Shipping:</strong>
{ order.shippingAddress.address},{ order.shippingAddress.city}
{' '}
{ order.shippingAddress.postalCode},
{' '}
{ order.shippingAddress.country}
</p>
</ListGroup.Item>
<ListGroup.Item>
<h2>Payment Method</h2>
<p>
<strong>Method:</strong>
{ order.paymentMethod}
</p>
</ListGroup.Item>
<ListGroup.Item>
<h2>Order Items</h2>
{order.orderItems.length===0 ?
<h3>Your order is empty</h3>
:(
<ListGroup varaint='flush'>
{ order.orderItems.map((item,index)=>
<ListGroup.Item key={index}>
<Row>
<Col md={2}>
<Image src={item.image} alt={item.name} fluid rounded/>
</Col>
<Col>
<Link to={`/product/${item.product}`}>{item.name}</Link>
</Col>
<Col md={4}>
{item.qty} X ${item.price}=${(item.qty*item.price).toFixed(2)}
</Col>
</Row>
</ListGroup.Item>
)}
</ListGroup>
)}
</ListGroup.Item>
</ListGroup>
</Col>
<Col md={4}>
<Card>
<ListGroup variant='flush'>
<ListGroup.Item>
<h2>Order Summary</h2>
</ListGroup.Item>
<ListGroup.Item>
<Row>
<Col>Item:</Col>
<Col>${ order.itemsPrice}</Col>
</Row>
</ListGroup.Item>
<ListGroup.Item>
<Row>
<Col>Shipping:</Col>
<Col>${ order.shippingPrice}</Col>
</Row>
</ListGroup.Item>
<ListGroup.Item>
<Row>
<Col>Shipping:</Col>
<Col>${ order.taxPrice}</Col>
</Row>
</ListGroup.Item>
<ListGroup.Item>
<Row>
<Col>Total:</Col>
<Col>${ order.totalPrice}</Col>
</Row>
</ListGroup.Item>
</ListGroup>
</Card>
</Col>
</div>
)
}
export default OrderScreen
heres my reducer for order:
export const orderDetailsReducer = (state = { loading: true, orderItems: [], shippingAddress: {} }, action) => {
switch (action.type) {
case ORDER_DETAILS_REQUEST:
return {
...state,
loading: true
}
case ORDER_DETAILS_SUCCESS:
return {
loading: false,
order: action.payload
}
case ORDER_DETAILS_FAIL:
return {
loading: false,
error: action.payload
}
default:
return state
}
}
here's my action for order:
export const getOrderDetails = (id) => async (dispatch, getState) => {
try {
dispatch({
type: ORDER_DETAILS_REQUEST
})
const {
userLogin: { userInfo },
} = getState()
const config = {
headers: {
'Content-type': 'application/json',
Authorization: `Bearer ${userInfo.token}`
}
}
const { data } = await axios.get(
`/api/orders/${id}/`,
config
)
dispatch({
type: ORDER_DETAILS_SUCCESS,
payload: data
})
} catch (error) {
dispatch({
type: ORDER_DETAILS_FAIL,
payload: error.response && error.response.data.detail
? error.response.data.detail
: error.message,
})
}
}
here's my django view to get orders by id:
#api_view(['GET'])
#permission_classes([IsAuthenticated])
def getOrderById(request, pk):
user = request.user
try:
order = Order.objects.get(_id=pk)
if user.is_staff or order.user == user:
serializer = OrderSerializer(order, many=False)
return Response(serializer.data)
else:
Response({'detail': 'Not authorized to view this order'},
status=status.HTTP_400_BAD_REQUEST)
except:
return Response({'detail': 'Order does not exist'}, status=status.HTTP_400_BAD_REQUEST)
i've used the combine reducer to combine the remaining reducer the one that ive used in this que is :
orderDetails: orderDetailsReducer,
constants i've used for state:
export const ORDER_DETAILS_REQUEST = 'ORDER_DETAILS_REQUEST'
export const ORDER_DETAILS_SUCCESS = 'ORDER_DETAILS_SUCCESS'
export const ORDER_DETAILS_FAIL = 'ORDER_DETAILS_FAIL'
django url path:
path('<str:pk>/', views.getOrderById, name='user-order'),
enter image description here
I solved my issue with change "order.shippingAddress" to "order.ShippingAddress", I just check out the file models.py in the base folder and find out the exact name that is ShippingAddress, then try to run makemigrations and migrate. hope it's help.

Call function to update Context in React Native

I am having problems calling a function in React Native. I simply want to change the value of 'Context'. Here is some code, first the script for 'context':
//LogContext.js
import React, { useState } from 'react'
export const LogContext = React.createContext({
set: "en",
login: "false"
})
export const LogContextProvider = (props) => {
const setLog = (login) => {
setState({set: "jp", login: login})
}
const initState = {
set: "en",
login: "false"
}
const [state, setState] = useState(initState)
return (
<LogContext.Provider value={state}>
{props.children}
</LogContext.Provider>
)
}
and the 'app.js' code:
//app.js
import React, { useState, useContext } from 'react';
import { Button, Text, TextInput, View } from 'react-native';
import { NavigationContainer } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
import { LogContextProvider, LogContext } from './LogContext'
function HomeScreen({ navigation }) {
const state = useContext(LogContext);
return (
<>
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Passed config: {JSON.stringify({state})}</Text>
<Text>Home Screen</Text>
</View>
{state.login === 'false' ? (
<Button
title="Go to Login"
onPress={() => navigation.navigate('Login')}
/>
) : (
<Button title="Stuff" onPress={() => navigation.navigate('DoStuff')} />
)}
</>
);
}
function LoginScreen({ navigation }) {
const state = useContext(LogContext);
//do stuff to login here...
state.setLog('true'); //not functional...
return (
<LogContext.Provider value={'true'}> //value={'true'} also not functional...
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Login Screen</Text>
<Button title="Go to Home" onPress={() => navigation.navigate('Home')} />
</View>
</LogContext.Provider>
);
}
function StuffScreen({ navigation }) {
//do other stuff here...
}
const Stack = createStackNavigator();
function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Login" component={LoginScreen} />
<Stack.Screen name="DoStuff" component={StuffScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}
export default App;
Obviously I am not too familiar with React Native. Any advice on how to call the "setLog()" function as to enable an update of the value for the 'Context' global variable would be greatly appreciated. I thank you in advance.
I am trying to modify my "App()" function to wrap the Navigator within the provider as suggested by another user...however this following is completely non-functional...suggestions appreciated:
const Stack = createStackNavigator();
function App() {
const [data, setData] = useState({
set: 'en',
login: 'false',
});
const state = { data, setData };
return (
<LogContext.Provider value={state}>
<NavigationContainer>
{state.data.login === 'true' ? (
<Stack.Navigator>
<Stack.Screen name="BroadCast" component={VideoScreen} />
<Stack.Screen name="Logout" component={LogoutScreen} />
</Stack.Navigator>
) : (
<Stack.Navigator>
<Stack.Screen name="Login" component={LoginScreen} />
<Stack.Screen name="Details" component={DetailsScreen} />
<Stack.Screen name="Home" component={HomeScreen} />
</Stack.Navigator>
)}
</NavigationContainer>
</LogContext.Provider>
);
}
The issue you are having is not having a set function in your context and i dont see a need for a separate LogContext provider function.
You can simply do that part in your app.js or whatever the root function. The below example does that. You can see how a state value is passed along with a function to set the values and this can be modified from teh Login component which is inside the provider. If you use a separate provider its a bit confusing. The below is a working example without the navigation part to give you an idea.
const LogContext = createContext({
data: {
set: 'en',
login: 'false',
},
});
export default function App() {
const [data, setData] = useState({
set: 'en',
login: 'false',
});
const state = { data, setData };
return (
<LogContext.Provider value={state}>
<View style={{ flex: 1 }}>
<Text>{JSON.stringify(state.data)}</Text>
<Login />
</View>
</LogContext.Provider>
);
}
const Login = () => {
const state = React.useContext(LogContext);
return (
<View>
<Button
onPress={() => state.setData({ set: 'bb', login: 'true' })}
title="Update"
/>
</View>
);
};
To modify your code, you should wrap the main navigator inside the LogContext.Provider and maintain the state there which will help you do the rest.
Feel free to ask any further clarification :)

Consistent left justify on phone application

I’m writing a small web application, hopefully it looks aesthetic on phone. On a mobile phone, the font showing the selected clinics are center justified, I want to know how to insert a line break into the code, so that the options appear consistently justified left in the mobile version, regardless of the name of the options.
Update: I still maintain a spaced-out view on the computer version, after the change.
The problem can be reproduced if 670150 is keyed as postal code, age 22, Singapore, eligible, CHAS Orange, and just select the first option of GP and polyclinics.
Phone view
Computer view
Can someone help me out here? Here's the relevant code.
ResultsTab.js
import React from "react";
import PropTypes from "prop-types";
import SwipeableViews from "react-swipeable-views";
import { makeStyles, useTheme } from "#material-ui/core/styles";
import AppBar from "#material-ui/core/AppBar";
import Tabs from "#material-ui/core/Tabs";
import Tab from "#material-ui/core/Tab";
import Typography from "#material-ui/core/Typography";
// import MyMap from "./myMap";
import TestMap from "./TestMap";
import PcDialog from "./PcDialog";
import GpDialog from "./GpDialog";
// import { display } from "#material-ui/system";
import CompareDialog from "./CompareDialog";
import Grid from "#material-ui/core/Grid";
import Paper from "#material-ui/core/Paper";
function TabContainer({ children, dir }) {
return (
<Typography component="div" dir={dir} style={{ padding: 8 * 3 }}>
{children}
</Typography>
);
}
TabContainer.propTypes = {
children: PropTypes.node.isRequired,
dir: PropTypes.string.isRequired
};
const useStyles = makeStyles(theme => ({
root: {
backgroundColor: theme.palette.background.paper,
width: "100%"
}
}));
const ResultTabs = props => {
const classes = useStyles();
const theme = useTheme();
const [value, setValue] = React.useState(0);
const [state, setState] = React.useState({
sortByLoc: true
});
function handleChange(event, newValue) {
setValue(newValue);
}
function handleChangeIndex(index) {
setValue(index);
}
const [open, setOpen] = React.useState(false);
const [selectedGP, setSelectedGP] = React.useState({
properties: { HCI_NAME: "Please Choose a GP" },
distance: "x",
price: "x",
rating: "x"
});
const [selectedPC, setSelectedPC] = React.useState({
Name: "Please choose a Polyclinic",
distance: "x",
price: "x",
rating: "x"
});
const [GPName, setGPName] = React.useState("none");
const [PCName, setPCName] = React.useState("none");
function handleClickOpen() {
setOpen(true);
}
const handleGPClose = (clinic, name) => {
setOpen(false);
clinic.price = "$$";
clinic.rating = "4.3";
setSelectedGP(clinic);
setGPName(name);
};
const handlePCClose = (clinic, name) => {
setOpen(false);
clinic.price = "$";
clinic.rating = "4.0";
setSelectedPC(clinic);
setPCName(name);
};
return (
<div className={classes.root}>
<Grid style={{ flexGrow: 1 }} direction="row">
<Grid container justify="space-evenly">
<Grid item>Selected GP: {GPName}</Grid>
<Grid item>
<p style={{ fontSize: "1em" }}>Selected PolyClinic: {PCName}</p>
{/* {console.log(selectedGP)} */}
</Grid>
</Grid>
</Grid>
<Grid style={{ flexGrow: 1 }} direction="row">
<Grid container justify="center">
<CompareDialog
GP={selectedGP}
PC={selectedPC}
formData={props.formData}
/>
</Grid>
</Grid>
<hr />
<AppBar position="static" color="default">
<Tabs
value={value}
onChange={handleChange}
indicatorColor="primary"
textColor="primary"
variant="fullWidth"
>
<Tab label="GP" />
<Tab label="Polyclinic" />
<Tab label="Map View" />
</Tabs>
</AppBar>
<SwipeableViews
axis={theme.direction === "rtl" ? "x-reverse" : "x"}
index={value}
onChangeIndex={handleChangeIndex}
>
<TabContainer dir={theme.direction}>
{props.GP.map(clinic => {
return (
<div key={clinic.properties.id}>
<GpDialog
clinic={clinic}
selectedGP={selectedGP}
open={open}
onClose={handleGPClose}
/>
<hr />
</div>
);
})}
</TabContainer>
<TabContainer dir={theme.direction}>
{props.PC.map(clinic => {
return (
<div key={clinic.id}>
<PcDialog
clinic={clinic}
selectedPC={selectedGP}
open={open}
onClose={handlePCClose}
/>
<hr />
</div>
);
})}
</TabContainer>
<TabContainer dir={theme.direction}>
{props.currentLoc[0] !== 0 && (
<TestMap coord={props.currentLoc} GP={props.GP} PC={props.PC} />
)}
</TabContainer>
</SwipeableViews>
</div>
);
};
export default ResultTabs;
FilteredResults.js
import React from "react";
import GP from "./chas.json";
import * as turf from "#turf/turf";
import ResultTabs from "./ResultTabs.js";
import PC from "./polyclinics.json";
import Button from "#material-ui/core/Button";
import Switch from "#material-ui/core/Switch";
import Grid from "#material-ui/core/Grid";
const API_KEY = "";
// this component aims to display the filtered clinic after they fill in the form
//try not to abuse the API call, im using some kind of free credits from google for this
//api to be able to consistenly make the api call
//api in use here are: google geocode & turf
//everything works except for styling, but the content from the json file abit lacking,
// no opening hrs etc
class FilteredResult extends React.Component {
constructor(props) {
super(props);
this.state = {
formData: this.props.location.state, //this gets the info from react router from Form.js
userLng: 0,
userLat: 0,
sortByLoc: true
};
this.goBack = this.goBack.bind(this);
}
componentDidMount() {
fetch(
`https://maps.googleapis.com/maps/api/geocode/json?address=${
this.state.formData.postalCode
}&region=sg&key=${API_KEY}`
)
.then(res => res.json())
.then(json => {
this.setState({
userLng: json.results[0].geometry.location.lng,
userLat: json.results[0].geometry.location.lat
});
});
}
goBack() {
this.props.history.goBack();
}
render(props) {
const { userLat, userLng, formData } = this.state;
const filteredGP = GP.features.filter(clinic => {
const from = turf.point([userLng, userLat]);
const to = turf.point([
clinic.geometry.coordinates[0],
clinic.geometry.coordinates[1]
]);
const options = { units: "kilometers" };
const dist = turf.distance(from, to, options);
clinic.distance = dist;
if (formData.hasSubsidy === "Yes") {
return (
dist <= 3
// && clinic.properties.CLINIC_PROGRAMME_CODE.includes(formData.subsidyType)
);
}
return dist <= 3;
});
const filteredPC = PC.clinics.filter(clinic => {
const from = turf.point([userLng, userLat]);
const to = turf.point([clinic.coord[0], clinic.coord[1]]);
const options = { units: "kilometers" };
const dist = turf.distance(from, to, options);
clinic.distance = dist;
return dist <= 100;
});
function sortDist(a, b) {
if (a.distance < b.distance) {
return -1;
} else {
return 1;
}
}
const handleSwitch = name => event => {
this.setState({ [name]: event.target.checked });
};
const sortedGP = filteredGP.sort(sortDist);
const sortedPC = filteredPC.sort(sortDist);
//note: dangerouslySetInnerHTML cos the json is in string, but its actually HTML
return (
<div>
<Grid container justify="center">
<h2>
Filtered clinics for{" "}
<span style={{ fongWeight: "bold", textDecoration: "underline" }}>
S{formData.postalCode}
</span> {" "}
{formData.subsidyType === ""
? ""
: `with ${formData.subsidyType} subsidy`}
</h2>
</Grid>
{/* <Switch
checked={this.state.sortByLoc}
onChange={handleSwitch('sortByLoc')}
value="sortByLoc"
inputProps={{ 'aria-label': 'secondary checkbox' }}
/> */}
<div>
<hr />
<ResultTabs
GP={sortedGP}
PC={sortedPC}
formData={formData}
currentLoc={[this.state.userLng, this.state.userLat]}
/>
<Button onClick={this.goBack}>Go Back</Button>
</div>
</div>
);
}
}
export default FilteredResult;
In the ResultTab.js inside the return give justify="left" instead of "space-evenly" for the Grid and I hope you will achieve what you wanted for phone application
<Grid container justify="left">
<Grid item>Selected GP: {GPName}</Grid>
<Grid item>
<p style={{ fontSize: "1em" }}>Selected PolyClinic: {PCName}</p>
{/* {console.log(selectedGP)} */}
</Grid>
</Grid>
</Grid>

Render input fields dynamically inside a list

I have set of components where it would consist of input fields along with text rows.
As given in the image the users should be able to add categories and description. After adding them they will be rendered as a list of components. like this
Inside a category there will be tags as given in the above image and to add them i have to add a input component. This input component should be available only when the user clicks on the Add tag button below each category row. When a user clicks on it,it should enable the input(should render a input component inside the selected category row) and should be able to type the tag name on it and save it. I need to make this input field enable only when i click on the add tag button. and it should enable only in the selected category row. This is the code that i have tried.
import React, { Component, Fragment } from "react";
import { Button, Header, Input } from "semantic-ui-react";
import "semantic-ui-css/semantic.min.css";
import ReactDOM from "react-dom";
class App extends Component {
state = {
category: "",
description: "",
categories: []
};
onChange = (e, { name, value }) => {
this.setState({ [name]: value });
};
addCategory = () => {
let { category, description } = this.state;
this.setState(prevState => ({
categories: [
...prevState.categories,
{
id: Math.random(),
title: category,
description: description,
tags: []
}
]
}));
};
addTag = id => {
let { tag, categories } = this.state;
let category = categories.find(cat => cat.id === id);
let index = categories.findIndex(cat => cat.id === id);
category.tags = [...category.tags, { name: tag }];
this.setState({
categories: [
...categories.slice(0, index),
category,
...categories.slice(++index)
]
});
};
onKeyDown = e => {
if (e.key === "Enter" && !e.shiftKey) {
console.log(e.target.value);
}
};
tags = tags => {
if (tags && tags.length > 0) {
return tags.map((tag, i) => {
return <Header key={i}>{tag.name}</Header>;
});
}
};
enableTagIn = id => {};
categories = () => {
let { categories } = this.state;
return categories.map(cat => {
return (
<Fragment key={cat.id}>
<Header>
<p>
{cat.title}
<br />
{cat.description}
</p>
</Header>
<Input
name="tag"
onKeyDown={e => {
this.onKeyDown(e);
}}
onChange={this.onChange}
/>
<Button
onClick={e => {
this.addTag(cat.id);
}}
>
Add
</Button>
{this.tags(cat.tags)}
</Fragment>
);
});
};
render() {
return (
<Fragment>
{this.categories()}
<div>
<Input name="category" onChange={this.onChange} />
<Input name="description" onChange={this.onChange} />
<Button onClick={this.addCategory}>Save</Button>
</div>
</Fragment>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
This is the codesandbox url.
Any idea on how to achieve this?.
I changed your code by using function components and react hooks and i created category component which has it own state like this:
import React, { Fragment } from "react";
import { Button, Header, Input } from "semantic-ui-react";
import "semantic-ui-css/semantic.min.css";
import ReactDOM from "react-dom";
const App = () => {
const [Category, setCategory] = React.useState({
title: "",
description: ""
});
const [Categories, setCategories] = React.useState([]);
return (
<div>
{console.log(Categories)}
<Input
value={Category.title}
onChange={e => setCategory({ ...Category, title: e.target.value })}
/>
<Input
value={Category.description}
onChange={e =>
setCategory({ ...Category, description: e.target.value })
}
/>
<Button onClick={() => setCategories([...Categories, Category])}>
Save
</Button>
<div>
{Categories.length > 0
? Categories.map(cat => <CategoryItem cat={cat} />)
: null}
</div>
</div>
);
};
const CategoryItem = ({ cat }) => {
const [value, setvalue] = React.useState("");
const [tag, addtag] = React.useState([]);
const [clicked, setclicked] = React.useState(false);
const add = () => {
setclicked(false);
addtag([...tag, value]);
};
return (
<Fragment>
<Header>
<p>
{cat.title}
<br />
{cat.description}
</p>
</Header>
<Input
name="tag"
value={value}
style={{ display: clicked ? "initial" : "none" }}
onChange={e => setvalue(e.target.value)}
/>
<Button onClick={() => (clicked ? add() : setclicked(true))}>Add</Button>
<div>{tag.length > 0 ? tag.map(tagname => <p>{tagname}</p>) : null}</div>
</Fragment>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
and here a sandbox