I am working on react app which uses redux for state management. In this app i have to implement search page. In which when user types on search box the app display the product related to that text input.
I have api endpoint like this api.XXXXXXX.com/buyer/product/filter?q=${inputValue}. I tried implement this but i am not able filter this. I haven't worked on this before and couldn't find good tutorial for this.
Here is my code
searchAction.js
export const searchProduct =(value) => dispatch => {
dispatch({
type: searchActionTypes.SEARCH_PRODUCT_LOAD
});
new _rest().get(`/buyer/product/filter?q=${value}`)
.then(res => {
console.log(res);
dispatch({
type: searchActionTypes.SEARCH_PRODUCT_SUCCESS,
payload: res
})
}).catch(err => {
console.log(err)
dispatch({
type: searchActionTypes.SEARCH_PRODUCT_ERROR,
error: err
})
});
}
searchActionreducer.js
const initialState = {
isFetching: false,
searchData: {},
isFetched: false,
isError: null
}
export default (state=initialState, action) =>{
switch (action.type) {
case searchActionTypes.SEARCH_PRODUCT_LOAD:
return {
...state,
isFetching: true
};
case searchActionTypes.SEARCH_PRODUCT_SUCCESS:
return {
...state,
isFetching: false,
searchData: action.payload.data._embedded,
isFetched: true
};
case searchActionTypes.SEARCH_PRODUCT_ERROR:
return {
...state,
isFetched: false,
};
default:
return {
...state
}
}
}
searchPage.js
class SearchPage extends React.Component {
state = {
inputValue: '',
}
componentDidMount() {
this.props.searchProduct('');
}
componentDidUpdate(prevProps, prevState, snapshot) {
console.log('component update', prevState)
if(this.state.inputValue !== prevState.inputValue) {
this.props.searchProduct(this.state.inputValue);
}
}
render() {
console.log('SearchPage Result', this.props.product);
const {product} = this.props;
const {inputValue} =this.state;
const updateInputValue = (e) => {
e.preventDefault();
this.setState({
inputValue: e.target.value
})
}
return(
<Grid container>
<div style={{width: '100%', display:'flex', justifyContent: 'center' }}>
<form noValidate autoComplete="off">
<TextField value={inputValue} onChange={updateInputValue} id="standard-basic" label="Search Product" />
</form>
</div>
<Grid container spacing={8} style={{padding: '30px'}}>
{product && product.productResourceList &&
product.productResourceList.map((data, index) => {
return(
<Grid item xs={6}>
<MainCards key={data.productId} data={data} />
</Grid>
)
})}
</Grid>
</Grid>
)
}
}
const mapStateToProps =(state) => {
return {
product: state.search.searchData
}
}
const mapDispatchToProps = {
searchProduct,
}
export default connect(mapStateToProps, mapDispatchToProps)(SearchPage);
Here In componentDidMount Initially when component mounts i made display all the product. After that in componentDidUpdate i trying get filtered data from api buy passing this.state.inputValue as value of q in api endpoint api.XXXXXXX.com/buyer/product/filter?q=${inputValue}. But component is not updating at all.
Related
I am trying to create a user authentication flow using react router dom v6 and and redux toolkit. But sometimes it seems the state is not updating in redux.
Here is how I have created my authentication slice.
AuthenticationSlice
const initialState = {
isAuthenticating: false,
isAuthenticated: false,
isAuthenticationError: false,
error: null,
};
const authSlice = createSlice({
name: "auth",
initialState,
reducers: {
loggingInUser: (state) => {
state.isAuthenticating = true;
state.isAuthenticated = false;
},
loggedInUser: (state, payload) => {
state.isAuthenticated = true;
state.isAuthenticating = false;
},
loggingUserFail: (state, payload) => {
state.isAuthenticated = false;
state.isAuthenticating = false;
state.error = payload.error;
},
logoutUser: (state, payload) => {
state.isAuthenticated = false;
state.isAuthenticating = false;
state.isAuthenticationError = false;
},
},
});
export const authSelector = (state) => state.auth;
export const {
loggedInUser,
loggingInUser,
loggingUserFail,
logoutUser,
} = authSlice.actions;
export default authSlice.reducer;
export function loginUserThunk(body) {
return async (dispatch) => {
dispatch(loggingInUser());
try {
const response = await axios({
method: "POST",
url: `${BASE_URL}/auth/login`,
data: body,
});
if (response.data.data) {
window.localStorage.setItem(
"access_token",
response?.data?.data?.access_token
);
setAuthorizationHeader(response.data.data);
dispatch(loggedInUser(response.data.data));
}
} catch (error) {
dispatch(loggingUserFail(error));
}
};
}
Login.js
import { loginUserThunk } from "../../redux/slices/auth";
function Login() {
const dispath = useDispatch();
const navigate = useNavigate();
const { handleBlur, handleChange, handleSubmit, errors, touched, values } =
useFormik({
initialValues: {
username: "",
password: "",
},
onSubmit: (values) => {
dispath(loginUserThunk(values));
navigate("/");
},
});
return (
<Container>
<Grid
container
justifyContent={"center"}
display={"flex"}
alignItems={"center"}
height={"100vh"}
width={"100%"}
>
<Grid item>
<Card sx={{ minWidth: 500 }}>
<Box p={2}>
<Typography variant="h5">Login to explore</Typography>
</Box>
<CardContent>
<form onSubmit={handleSubmit}>
<Stack>
<TextField
name="username"
onChange={handleChange}
value={values.username}
sx={{ marginTop: 2 }}
placeholder="UserName"
variant="outlined"
/>
<TextField
name="password"
onChange={handleChange}
value={values.password}
sx={{ marginY: 2 }}
placeholder="Password"
type={"password"}
variant="outlined"
/>
<Button type="submit" variant="contained">
Login
</Button>
<Box marginTop={2}>
<Typography>
New User ? <Link to={"/register"}>Register</Link>
</Typography>
</Box>
</Stack>
</form>
</CardContent>
</Card>
</Grid>
</Grid>
</Container>
);
}
export default Login;
Here even after dispatching dispatch(loggedInUser(response.data.data)); loggedInUser isAuthenticated state is not updating.
Upon the click of a single like, it is increasing the number of likes for both separate components. What is causing both like numbers to increase, and how can I code it to where only one like number increases upon clicking a like?
I have also include the console in the picture below where I have console logged the logic in my reducer. You can find the code for the reducer further below the picture.
Reducer code
import { GET_GOALS, GOAL_ERROR, UPDATE_LIKES } from "../actions/types";
const initialState = {
goals: [],
goal: null,
loading: true,
error: {}
};
export default function(state = initialState, action) {
const { type, payload } = action;
switch (type) {
case GET_GOALS:
return {
...state,
goals: payload,
loading: false
};
case GOAL_ERROR:
return {
...state,
error: payload,
loading: false
};
case UPDATE_LIKES:
return {
...state,
goals: state.goals.map(goal =>
console.log("goal id", goal._id) === console.log("payload id", payload.goalId) ? { ...goal, likes: payload.likes } : goal
),
loading: false
};
default:
return state;
}
}
Action code
import axios from "axios";
import { GET_GOALS, GOAL_ERROR, UPDATE_LIKES } from "./types";
// Get goals
export const getGoals = () => async dispatch => {
try {
const res = await axios.get("/api/goal/goalfeed");
dispatch({
type: GET_GOALS,
payload: res.data
});
} catch (error) {
dispatch({
type: GOAL_ERROR,
payload: { msg: error.response }
});
}
};
// Add like
export const addLike = goalId => async dispatch => {
try {
const res = await axios.put(`/api/goal/like/${goalId}`);
dispatch({
type: UPDATE_LIKES,
payload: { goalId, likes: res.data }
});
} catch (error) {
dispatch({
type: GOAL_ERROR,
payload: { msg: error.response }
});
}
};
// Remove like
export const removeLike = goalId => async dispatch => {
try {
const res = await axios.put(`/api/goal/unlike/${goalId}`);
dispatch({
type: UPDATE_LIKES,
payload: { goalId, likes: res.data }
});
} catch (error) {
dispatch({
type: GOAL_ERROR,
payload: { msg: error.response }
});
}
};
Goals component code
import React, { useEffect } from "react";
import Moment from "react-moment";
import PropTypes from "prop-types";
import { Link } from "react-router-dom";
import { connect } from "react-redux";
import { addLike, removeLike } from "../../actions/goal";
import { getGoals } from "../../actions/goal";
import Spinner from "../layout/Spinner";
import Navbar from "../dashboard/Navbar";
import ThumbUpAltIcon from "#material-ui/icons/ThumbUpAlt";
import ThumbDownAltIcon from "#material-ui/icons/ThumbDownAlt";
import ChatIcon from "#material-ui/icons/Chat";
import DeleteIcon from "#material-ui/icons/Delete";
import DoneIcon from "#material-ui/icons/Done";
import {
Typography,
Container,
CssBaseline,
makeStyles,
Grid,
Avatar,
Paper,
Button
} from "#material-ui/core";
const useStyles = makeStyles(theme => ({
paper: {
height: "auto",
marginBottom: theme.spacing(3)
},
actionButtons: {
marginTop: "3vh"
},
profileHeader: {
textAlign: "center",
marginBottom: 20
},
avatar: {
width: theme.spacing(7),
height: theme.spacing(7)
}
}));
const Goals = ({
getGoals,
auth,
addLike,
removeLike,
goal: { goals, user, loading }
}) => {
useEffect(() => {
getGoals();
}, [getGoals]);
const classes = useStyles();
return loading ? (
<>
<Navbar />
<Container component="main" maxWidth="xs">
<CssBaseline />
<div className={classes.paper}>
<Spinner />
</div>
</Container>
</>
) : (
<>
<CssBaseline />
<Navbar />
<main>
<Container>
<Typography variant="h2" className={classes.profileHeader}>
Goals
</Typography>
{/* parent grid */}
<Grid container spacing={4}>
{goals.map(singleGoal => (
<Grid
className={classes.paper}
key={singleGoal._id}
spacing={1}
container
item
direction="row"
alignItems="center"
component={Paper}
>
<Grid
item
container
direction="column"
justify="center"
alignItems="center"
xs={3}
>
<Avatar className={classes.avatar} src={singleGoal.avatar} />
<Typography variant="caption">
{singleGoal.first_name} {singleGoal.last_name}
</Typography>
<Typography variant="caption" className={classes.postedOn}>
Posted on{" "}
<Moment format="MM/DD/YYYY">{singleGoal.date}</Moment>
</Typography>
</Grid>
<Grid container item direction="column" xs={9}>
<Typography variant="body1">{singleGoal.text}</Typography>
<Grid item className={classes.actionButtons}>
<Button size="small" onClick={e => addLike(singleGoal._id)}>
<ThumbUpAltIcon />
</Button>
<Typography variant="caption">
{singleGoal.likes.length}
</Typography>
<Button
size="small"
onClick={e => removeLike(singleGoal._id)}
>
<ThumbDownAltIcon />
</Button>
<Button href={`/goal/${singleGoal._id}`} size="small">
<ChatIcon />
</Button>
{!auth.loading && singleGoal.user === auth.user._id && (
<Button size="small">
<DoneIcon />
</Button>
)}
{!auth.loading && singleGoal.user === auth.user._id && (
<Button size="small">
<DeleteIcon />
</Button>
)}
</Grid>
</Grid>
</Grid>
))}
</Grid>
</Container>
</main>
</>
);
};
Goals.propTypes = {
getGoals: PropTypes.func.isRequired,
goal: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
goal: state.goal,
auth: state.auth
});
export default connect(mapStateToProps, { getGoals, addLike, removeLike })(
Goals
);
There exists a flaw in your conditional test.
state.goals.map(goal =>
console.log("goal id", goal._id) === console.log("payload id", payload.goalId) // What is this? it will always evaluate to true
? { ...goal, likes: payload.likes }
: goal
)
console.log('EQUAL?', console.log() === console.log()); // true
console.log('EQUAL?', console.log(3) === console.log(3)); // true
console.log('EQUAL?', console.log(3) === console.log('three')); // true
console.log('EQUAL?', console.log('apple') === console.log({})); // true
console.log('EQUAL?', console.log(42) === console.log(-42)); // true
The function console.log is a void return, i.e. undefined, so you are comparing undefined === undefined, which is always true.
console.log(undefined === undefined); // true
You are spreading in the new 'likes' value to every goal object.
Try instead:
state.goals.map(
goal => goal._id === payload.goalId
? { ...goal, likes: payload.likes }
: goal
)
I'm having some trouble with updating information from my API with React.
For example, I have a js file that takes information about a client, after I change information about a client, for example balance, even if I click to another page and return information is not updated, only if I click F5 it will update info.
This is a file
class Portfolio extends Component {
render() {
const { authUser } = this.props;
return (
<Widget>
<h2 className="h4 gx-mb-3">Your Portfolio Balance</h2>
<Row>
<Col lg={12} md={12} sm={12} xs={24}>
<div className="ant-row-flex">
<h2 className="gx-mr-2 gx-mb-0 gx-fs-xxxl gx-font-weight-medium">{authUser ? authUser.balance : "Loading"}€</h2>
</div>
<p className="gx-text-grey"></p>
<div className="ant-row-flex gx-mb-3 gx-mb-md-2">
<Button className="gx-mr-2" type="primary">Deposit</Button>
<Button className="gx-btn-red" disabled>Withdraw</Button>
</div>
<p><Link className="gx-text-primary" to="/te">- Messages <Badge count={23} style={{ backgroundColor: '#52c41a' }} /></Link></p>
<p><Link className="gx-text-primary" to="/te">- Notifications <Badge count={320} style={{ backgroundColor: '#52c41a' }} /></Link></p>
<p><Link className="gx-text-primary" to="/te">- Active Servers <Badge count={5} style={{ backgroundColor: '#52c41a' }} /></Link></p>
<p><Link className="gx-text-primary" to="/te">- Billing </Link></p>
<p><Link className="gx-text-primary" to="/te">- Logout </Link></p>
</Col>
<Col lg={12} md={12} sm={12} xs={24}>
<div className="gx-site-dash">
<h4 className="gx-mb-3">Welcome back {authUser ? authUser.username : "Loading"}</h4>
<img alt="" width="160" src={authUser ? authUser.avatar : 'Loading'} />
</div>
</Col>
</Row>
</Widget>
)
}
}
const mapStateToProps = ({ auth }) => {
const { authUser } = auth;
return { authUser }
};
export default connect(mapStateToProps, { userSignOut })(Portfolio);
Do I need to use WebSockets to make it live and don't need to refresh the page or there is easier way?
Edit:
Reducer
const INIT_STATE = {
token: JSON.parse(localStorage.getItem('token')),
initURL: '',
authUser: JSON.parse(localStorage.getItem('user')),
};
export default (state = INIT_STATE, action) => {
switch (action.type) {
case INIT_URL: {
return {...state, initURL: action.payload};
}
case USER_DATA: {
return {
...state,
authUser: action.payload,
};
}
Action:
export const getUser = () => {
return (dispatch) => {
dispatch({ type: FETCH_START });
axios.get('http://127.0.0.1:8000/api/user/details/profile',
).then(({ data }) => {
if (data.username) {
dispatch({ type: FETCH_SUCCESS });
dispatch({ type: USER_DATA, payload: data });
} else {
dispatch({ type: FETCH_ERROR, payload: data.error });
}
}).catch(function (error) {
dispatch({ type: FETCH_ERROR, payload: error.message });
console.log("Error****:", error.message);
});
}
};
If my guess is correct, your component need to be:
class Portfolio extends PureComponent
One of the main difference between Component and PureComponent is the update method for props.
PureComponent update when props change, Component don't update.
PureComponent handle shouldComponentUpdate() for you.
I had fetched user data from local API server, which sends JSON data, and it worked before I added searching feature. I recognized the problem that these error occurs from React's lifecycle style, but because I haven't studied about React much, so I'm asking question to solve this problem with you.
macOS Mojave, Express.js, React.js and React-Router are running my local dev server. I never tried this searching feature before.
These are my code:
UsersList.js:
import React, {Component} from 'react';
import {List, Avatar, Button, Modal, Input} from 'antd';
import UserItem from "../components/UserItem";
import blue_check from '...';
const {Search} = Input;
class UsersList extends Component {
state = {
users: [],
modalVisible: false,
dataKey: 0,
keyword: '',
};
constructor(props) {
super(props);
this.state = {
users: [{
member_idx: 0,
nickname: 'loading...',
}]
};
}
componentDidMount() {
fetch('/api/getUsers')
.then(res => res.json())
.then(users => this.setState({users: users}));
}
showModal = (key) => {
this.setState({
modalVisible: true,
dataKey: key
});
};
handleClose = e => {
console.log(e);
this.setState({
modalVisible: false
});
};
handleSearch = (e) => {
this.setState({
keyword: e.target.value,
})
};
render() {
const {users, keyword} = this.state;
const filteredUsers = users.filter(
user => user.nickname.indexOf(keyword) !== -1
); // This line makes error
return (
<div>
<Search
placeholder="Input Search Text"
enterButton="Search"
onSearch={value => this.state.handleSearch(value)}
size="large"
/>
<List
itemLayout="horizontal"
dataSource={filteredUsers}
renderItem={item => (
<List.Item
actions={[<a onClick={() => this.showModal(item.member_idx)}>상세정보</a>, <a>이 프로필을...</a>]}>
<List.Item.Meta
style={{
display: 'flex',
alignItems: 'center'
}}
avatar={<Avatar src={item.image}/>}
title={
<span>{item.nickname === null ? "<undefined>" : item.nickname} {item.blue_check === 1 &&
<img src={blue_check} alt="BLUE" style={{height: '16px'}}/>
}</span>}
description={
<span><strong>{item.follow_count}</strong> Follower{item.follow_count !== 1 && "s"}</span>}
/>
</List.Item>
)}
/>
<Modal
title="상세 정보"
visible={this.state.modalVisible}
onClose={this.handleClose}
footer={[
<Button key="back" onClick={this.handleClose}>
닫기
</Button>
]}
centered
>
<UserItem dataKey={this.state.dataKey} users={this.state.users}/>
</Modal>
</div>
);
}
}
export default UsersList;
UserItem.js doesn't need in this post because it has no problem about this error.
I linked the error trace image here. Sorry about inconvenience.
It is possible that in your data nickname is null for one or more users and hence you should make a check for it too while filtering
const filteredUsers = users.filter(
user => user.nickname && user.nickname.indexOf(keyword) !== -1
);
I have an app where a user can search for car and see the result. The result page contains results of car along with map(source to destination is shown based on search). When i refresh the page, the state of car results is preserved and it is working well but i get an error for map as invalid latlng and the source to destination route is not shown either. I have used persistedState in redux.
Here is my code
localstorage.js
export const loadState = () => {
try {
const serializedState = localStorage.getItem('state');
if (serializedState === null) {
return undefined;
}
return JSON.parse(serializedState);
} catch (err) {
return undefined;
}
};
export const saveState = (state) => {
try {
const serializedState = JSON.stringify(state);
localStorage.setItem('state', serializedState);
} catch (err) {
// Ignore write errors
}
};
index.js
store.subscribe(() => {
saveState({
getCarResult: store.getState().getCarResult,
locale: store.getState().locale,
});
});
car-result.js
function renderMap(cityOrigenLat, cityOrigenLng, cityDestinoLat, cityDestinoLng) {
return (
<GoogleMap
cityOrigenLat={cityOrigenLat}
cityOrigenLng={cityOrigenLng}
cityDestinoLat={cityDestinoLat}
cityDestinoLng={cityDestinoLng}
/>
);
}
const GoogleMap = ({ cityOrigenLat, cityOrigenLng, cityDestinoLat, cityDestinoLng }) => {
console.log('cityOrigenLat', cityOrigenLat);
const position = cityOrigenLat ? [cityOrigenLat, cityOrigenLng] : [51.505, -0.09];
const icon = divIcon({
className: 'car-icon',
iconSize: [50, 50]
});
return (
<Map center={position} zoom={5} style={{ height: 600 }}>
<TileLayer
url='https://mt{s}.google.com/vt/lyrs=m&x={x}&y={y}&z={z}'
subdomains='0123'
/>
<Marker position={[cityOrigenLat, cityOrigenLng]} icon={icon} />
<Marker position={[cityDestinoLat, cityDestinoLng]} icon={icon} />
<CarRouting from={[cityOrigenLat, cityOrigenLng]} to={[cityDestinoLat, cityDestinoLng]} />
</Map>
);
};
class CarResult extends Component {
render() {
const { carResult, origen, destino, page, locale } = this.props;
const cityOrigen = origen && origen.label.split(', ')[0];
const cityOrigenLat = origen && origen.location.lat; // undefined when refreshing the page
const cityOrigenLng = origen && origen.location.lng;
const cityDestino = destino && destino.label.split(', ')[0];
const cityDestinoLat = destino && destino.location.lat;
const cityDestinoLng = destino && destino.location.lng;
const carResultList = renderCarList(
carResult.cars, cityOrigen, cityDestino,
startIndex, startCount, perPage, locale
);
const carResultMap = renderMap(cityOrigenLat, cityOrigenLng, cityDestinoLat, cityDestinoLng);
if (!carResult.fetching) {
return (
<div>
Hemos encontrado {carResultList.length} ofertas para ti.
<div className="car-result-container">
<Grid fluid>
<Row>
<Col xs={12} sm={12} md={6}>
{carResultList}
</Col>
<Col x={12} sm={12} md={6} className="kolabooMap">
{carResultMap}
</Col>
</Row>
</Grid>
</div>
</div>
);
}
return (
<Grid fluid>
<Row>
<Col xs={12} sm={12} md={6}>
<Row>
<Col xs={12} sm={12} md={6}>
loading ...
</Col>
</Row>
</Col>
</Row>
</Grid>
);
}
changePage(page) {
console.log('page', page);
this.props.dispatch(push('/car/?page=' + page));
}
}
function mapStateToProps(state) {
return {
page: Number(state.routing.locationBeforeTransitions.query.page) || 1,
locale: state.locale
};
}
export default connect(mapStateToProps)(CarResult);
car-routing.js
class CarRouting extends MapLayer {
componentWillMount() {
super.componentWillMount();
const { map, from, to, ...props } = this.props;
props.icon = L.divIcon({
className: 'car-icon',
iconSize: [50, 50]
});
this.leafletElement =
L.Routing.control({
position: 'topleft',
createMarker: () => null,
waypoints: [
L.latLng(from[0], from[1]),
L.latLng(to[0], to[1]),
],
}).addTo(map);
// L.marker(position, props);
}
render() {
return null;
}
}
export default CarRouting;
result-layout.js
<CarResult
origen={Origen}
destino={Destino}
carResult={carResult}
/>
const mapStateToProps = state => {
console.log('state', state);
const carValues = selector(state, 'Origen', 'Destino', 'daterange');
return {
carValues,
carResult: state.getCarResult
};
};
The problem is when i refresh the page, the result of car is shown(the state is preserved) but routing is not shown in map as i get an error of
Uncaught Error: Invalid LatLng object: (undefined, undefined). I get cityOrigenLat undefined during refreshing the page
UPDATE
let cars = [];
const initialState = {
fetching: false,
fetched: true,
cars: [],
currentPage: 0,
carsLength: 0,
error: null
};
export function getCarResult(state = initialState, action) {
switch (action.type) {
case 'CAR_FETCH_START':
return { ...state, fetching: true };
case 'CAR_FETCH_SUCCESS':
cars = action.payload;
return {
...state,
fetched: true,
fetching: false,
cars: action.payload,
currentPage: 0,
carsLength: action.payload.length
};
case 'CAR_FETCH_WITH_FILTER':
return { ...state, fetched: true, fetching: false, cars: carFilterFromPrice(action.payload) };
case 'CAR_FETCH_WITH_TIME_FILTER':
return { ...state, fetched: true, fetching: false, cars: carFilterFromTime(action.payload) };
case 'CAR_FETCH_FAILURE':
return { ...state, error: action.payload };
default:
return state;
}
}