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;
}
}
Related
I am making a watchlist component using redux/react/hooks. Component is in 3 pieces (form to add to list, container to map over securities, and security to display each item)
So far I have been able to add values by dispatching addStock in the form component. I tried to do the same thing in security component for deleteStock but it isn't working/ reloads page.
reducer:
const stockSelector = (state = STOCK_STATE, action) => {
switch (action.type) {
case STOCK_SELECTED:
return action.payload;
case FETCH_STOCK_LIST:
return { ...state, watchlist: action.payload.select, title: action.payload.name, loading: false};
case STOCK_LIST_LOADING:
return {...state, loading: true}
case ADD_STOCK:
return { ...state, watchlist: [action.payload, ...state.watchlist] };
case DELETE_STOCK:
return {
...state,
watchlist: [
...state.watchlist.slice(0, action.payload),
...state.watchlist.slice(action.payload + 1)
],
};
default:
return state;
}
action
export const deleteStock = (payload) => ({
type: DELETE_STOCK,
payload,
});
watchlist component
const Watchlist = ({selected, watchlists, number}) => {
const dispatch = useDispatch();
const [taskList, setTaskList] = useState(['AAPL', 'MSFT', 'AMZN'])
const [list, setList] = useState(selected)
const [allList, setAllList] = useState(watchlists)
const [selectNumber, setSelectNumber] = useState(number)
const selectWatchlist = async () => {
setList(selected)
setSelectNumber(number)
}
useEffect(() => {
selectWatchlist()
.then(dispatch(fetchStockList(selectNumber)))
}, []);
return (
<React.Fragment>
<Col className="watchlist-master-col">
<Row className="watchlist-form-row">
<Col>
<AddWatchlistForm className="watchlist-form" />
</Col>
</Row>
<Row className="watchlist-list-row">
<ListSecurityContainer
list={taskList}
className="watchlist-list"
number={number}
/>
</Row>
<Row>
<Modal className='modalOne' />
</Row>
</Col>
<Modal />
</React.Fragment>
)
}
const mapStateToProps = (state) => {
console.log(state)
return {
selected: state.Watchlist.stock.title,
watchlists: state.Watchlist.watchlist.watchlist,
watchlist: state.Watchlist.stock.watchlist,
number: state.Watchlist.watchlist.number,
}
}
container
const ListSecurityContainer = ({loading, stocks}) => {
const dispatch = useDispatch();
const handleCloseTask = (id) => {
dispatch(deleteStock(id))
}
if (loading === false) {
return (
<React.Fragment>
<Col>
{stocks.map((value, index) => (
<Security
key={stocks[index]}
id={index}
{...value}
name={value}
// onClose={handleCloseTask}
className="security-elem"
/>
))}
</Col>
</React.Fragment>
);
}
return <div>Loading...</div>
}
const mapStateToProps = (state) => {
console.log(state.Watchlist.stock.watchlist)
return {
stocks: state.Watchlist.stock.watchlist,
loading: state.Watchlist.stock.loading
}
}
security
const Security = (value) => {
const dispatch = useDispatch();
const [taskName, setTaskName] =useState(value.name)
const removeTask = () => {
dispatch(deleteStock(taskName))
}
return (
<div className="list-group-item">
{value.name}
<button onClick={removeTask()} style={{ float: 'right' }}>
<i className="glyphicon glyphicon-remove"></i>
</button>
</div>
);
}
Fixed this by correcting the issues listed in the comments and also fixing a type that I had in my constants.js file.
const Security = ({index, name}) => {
const dispatch = useDispatch();
const [taskName, setTaskName] =useState(name)
const removeTask = (e) => {
e.stopPropagation()
dispatch(removeStock(index))
}
return (
<Row className="list-group-item">
<div className="item-titles">
{name}
</div>
<button onClick={() => dispatch(removeStock(index))} className="remove-item">
<i className="glyphicon glyphicon-remove"></i>
</button>
</Row>
);
}
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.
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 can't solve why my input is rerendered when I dynamically add next input. In first step I click on select employe from multiple select and input is dynamically created with id and name of employee.Now I can add allocation for employee but when I add next employee. Allocation value of first employee is removed.
1.Step
2.Step
3.Step
4.Step
Component ProjectForm (I deleted some unnecessary parts of this code for this question )
export default class ProjectForm extends Component {
constructor(props) {
super(props);
const { project, timurProjects } = this.props;
this.state = {
employees:
project && project.currentEmployees ? project.currentEmployees : [],
technologies:
project && project.technologies
? project.technologies.map(mapToId)
: [],
allTimurProjects: timurProjects,
allocationValue:
project && project.currentEmployees ? project.currentEmployees : [],
projectTimur: null,
};
this.handleSubmit = this.handleSubmit.bind(this);
this.handleUpdateEmployees = this.handleUpdateEmployees.bind(this);
this.handleUpdateTechnologies = this.handleUpdateTechnologies.bind(this);
}
componentDidMount() {
const { project } = this.props;
const { allTimurProjects } = this.state;
this.setState({
projectTimur: allTimurProjects.find(projectTimur => {
return project ? projectTimur.code === project.code : false;
}),
});
}
handleSubmit = values => {
const { submitProjectForm, project, successText } = this.props;
const { employees, technologies, projectTimur } = this.state;
const value = {
...values,
code: projectTimur ? projectTimur.code : null,
projectManagerId: projectTimur
? projectTimur.projectManager.id
: values.projectManager,
};
return submitProjectForm(
value,
employees,
technologies,
(project && project.id) || null,
)
.then(res => {
displayNotification(successText, 'success');
const { id } = res.data.project;
browserHistory.push(`/projects/detail/${id}`);
})
.catch(error => {
throw new SubmissionError(error.response.data.error);
});
};
handleUpdateEmployees(employees) {
this.setState({
employees,
});
}
handleUpdateTechnologies(technologies) {
this.setState({
technologies,
});
}
handleUpdateAllocation = allocValue => {
console.log(allocValue, 'ALLOCAVALUE');
this.setState({
allocationValue: allocValue,
});
};
render() {
const {
projectData,
project,
handleSubmit,
error,
pristine,
submitting,
title,
submitText,
} = this.props;
const {
employees,
technologies,
projectTimur,
allTimurProjects,
allocationValue,
} = this.state;
const employeesPristine = isEqual(
employees,
project && project.currentEmployees ? project.currentEmployees : [],
);
const technologiesPristine = isEqual(
technologies,
project && project.technologies ? project.technologies.map(mapToId) : [],
);
const allocationValuePristine = isEqual(
allocationValue,
project && project.currentEmployees ? project.currentEmployees : [],
);
const formPristine =
employeesPristine &&
technologiesPristine &&
pristine &&
allocationValuePristine;
const defaultPMValue = projectTimur
? projectTimur.projectManager.name
: '--';
const owners =
projectTimur && projectTimur.owner.name ? [projectTimur.owner] : [];
const projectManagers = projectTimur
? [projectTimur.projectManager]
: projectData.projectManagers;
return (
<Container>
<Row>
<Col>
<Card className="card-project-add hs-box-shadow mx-auto">
<CardHeader>
{project ? (
<Row>
<Col>
<FontAwesome icon={['fab', 'product-hunt']} /> {title}
</Col>
<Col className="text-right">
<Link to={`/projects/detail/${project.id}`}>
Show project detail
</Link>
</Col>
</Row>
) : (
<span>
<FontAwesome icon={['fab', 'product-hunt']} /> {title}
</span>
)}
</CardHeader>
<CardBody>
{error && <Alert color="danger">{error}</Alert>}
<Form
onSubmit={handleSubmit(this.handleSubmit)}
onKeyPress={blockSubmitOnEnter}
>
<FormRow>
</Col>
<Col xs="12" lg="5">
<Employees
title="Project employees"
employees={projectData.employees}
defaultValue={
(project && project.currentEmployees) || []
}
onUpdate={this.handleUpdateEmployees}
/>
<EmployeeAllocationTable
title="Project skills/technologies"
employees={employees}
project={project}
onUpdateAllocation={this.handleUpdateAllocation}
defaultValue={
(project && project.currentEmployees) || []
}
/>
</Col>
</FormRow>
<FormRow>
<Col xs="12" md="4" lg="3">
<Button
block
color="success"
type="submit"
disabled={formPristine || submitting}
>
{submitText}
</Button>
</Col>
<Col className="text-md-right mt-3 mt-md-2">
<RequiredFieldsNote />
</Col>
</FormRow>
</Form>
</CardBody>
</Card>
</Col>
</Row>
</Container>
);
}
}
Component Employee
import PropTypes from 'prop-types';
import Select from 'react-select';
import { FormGroup } from 'reactstrap';
import { getFilteredEmployees } from '../../../util/employees';
class Employees extends Component {
handleChange(selectedOptions) {
const { onUpdate } = this.props;
onUpdate &&
onUpdate(
selectedOptions.map(option => ({
id: option.value,
label: option.label,
})),
console.log(selectedOptions, 'SEL'),
);
}
render() {
const { title, employees } = this.props;
let { defaultValue } = this.props;
defaultValue = defaultValue.map(employee => ({
value: employee.id,
label: employee.fullNameInverted || '[unknown name]',
allocationValue: employee.allocationValue,
}));
console.log(defaultValue, 'DV');
const filteredEmployees = getFilteredEmployees(employees);
const options = filteredEmployees.map(employee => ({
value: employee.id,
label: employee.fullNameInverted,
allocationValue: employee.allocationValue,
}));
return (
<FormGroup>
<label>{title}</label>
<Select
defaultValue={defaultValue}
isMulti
options={options}
onChange={this.handleChange.bind(this)}
/>
</FormGroup>
);
}
}
Employees.propTypes = {
title: PropTypes.any,
employees: PropTypes.array.isRequired,
defaultValue: PropTypes.array,
onUpdate: PropTypes.func,
};
export default Employees;
Component.EmployeeAllocationTable
import { Table, Input } from 'reactstrap';
export default class EmployeeAllocationTable extends Component {
constructor(props) {
super(props);
let { defaultValue } = this.props;
defaultValue = defaultValue.map(employee => ({
value: employee.id,
label: employee.fullNameInverted || '[unknown name]',
allocationValue: employee.allocationValue,
}));
this.state = {
value: defaultValue,
key: defaultValue,
allocationValue: '',
};
console.log('State', this.state);
}
handleChangeInput = e => {
const { employees } = this.props;
const { name, value } = e.target;
console.log(name, 'Name', value, 'Value');
const employeesList =
employees &&
employees.find(
employeeLabel => employeeLabel.id.toString() === e.target.name,
);
employeesList.allocationValue = e.target.value;
e.preventDefault();
this.props.onUpdateAllocation(employeesList.allocationValue);
this.setState({
[name]: value,
});
};
render() {
const { employees } = this.props;
const newEmployeesTable =
employees &&
employees.map(employee => {
const employeesList =
employees &&
employees.find(employeeLabel => employeeLabel.id === employee.id);
return (
<tr key={employee.id}>
<td>{employeesList.fullName || employeesList.label}</td>
<td>{employeesList.allocationValue} %</td>
<td>
<Input
name={employee.id.toString()}
value={employee.allocationValue}
onChange={this.handleChangeInput}
placeholder="Allocation on the project (%) "
maxLength="3"
/>
</td>
</tr>
);
});
return (
<div>
<Table>
<thead>
<tr>
<th>Name</th>
<th>Allocation on the project</th>
</tr>
</thead>
<tbody>{newEmployeesTable}</tbody>
</Table>
</div>
);
}
}
What is this?
const employeesList =
employees &&
employees.find(employeeLabel => employeeLabel.id === employee.id);
upd: if you dont want to rerender table you should not call onUpdate prop (handleUpdateEmployees) from on Select change
also you can do something with object
onUpdate(
selectedOptions.map(option => ({
id: option.value,
label: option.label,
//add here allocation and input value
})),
React , setState(...):, called setState() on an unmounted component ???
setState(...): This usually means you called set state() on an unmounted component.
Warning:
setState(...): Can only update a mounted or mounting component.
This usually means you called set state() on an unmounted component.
This is a no-op. Please check the code for the TestModal component.
I just can't figure out, what's wrong with this?
```jsx
class TestModal extends Component {
constructor(props, context) {
super(props, context);
this.state = {
loading: false,
visible: true
};
}
onTest = () => {
// copy state, do test fetch
this.testOK();
};
handleSubmit = (e) => {
e.preventDefault();
this.props.form.validateFields(
(err, values) => {
if (!err) {
console.log('Received values of form: ', values);
console.log('fetch url = \n', values.textarea);
// fetch data
let url = values.textarea;
this.props.checkTestCommands(url);
this.onTest();
}else{
throw new Error(Error.name, Error.message);
}
}
);
};
testOK = () => {
this.setState({
loading: true
});
this.props.hideModal();
setTimeout(() => {
this.setState({
loading: false,
visible: false
});
}, 1000);
}
testCancel = () => {
this.setState({
visible: false
});
this.props.hideModal();
}
render() {
const {getFieldsValue, getFieldValue, setFieldsValue, getFieldDecorator} = this.props.form;
const formItemLayout = {
labelCol: {
span: 6
},
wrapperCol: {
span: 14
}
};
return (
<div>
{/* query object */}
{/* no footer */}
<Modal
title="命令行"
onOk={this.testOK}
onCancel={this.testCancel}
visible={this.state.visible}
footer={[]}
>
{/* loading={this.state.loading} */}
<Form
onSubmit={this.handleSubmit}
layout="horizontal">
<FormItem
label="测试命令行"
hasFeedback
{...formItemLayout}>
{
getFieldDecorator('textarea', {
rules: [
{
required: false,
message: ' url 长度必须 30 个字符之间'
}
],
initialValue: `http://10.1.5.31:8080/http/report/query?{"ApiName":"JY.Topic.Market_profile.Investors_data_statistics.AccountStatistics"}`,
})(
<Input
type="textarea"
placeholder="请先点击 “开始测试” 按钮!"
rows="10"
cols="70"
/>
)
}
</FormItem>
{/* onClick={this.props.checkTestCommands} */}
<FormItem style={{textAlign: "center"}}>
<Button
onClick={this.onTest}
type="primary"
htmlType="submit"
icon="hourglass"
style={{margin: "auto 10px"}}
loading={this.state.loading}>
开始测试
</Button>
<Button
onClick={this.testCancel}
size="large"
style={{margin: "auto 10px"}}
icon="close">
关闭
</Button>
{/* ref="submit_btn" */}
</FormItem>
</Form>
</Modal>
</div>
);
}
}
```
this is a new version code, but it still doesn't work?
class TestModal extends Component {
constructor(props, context) {
super(props, context);
this.state = {
loading: false,
visible: true
};
}
onTest = () => {
// copy state, do test fetch
this.testOK();
};
/* componentWillUnmount(){
this.props.hideModal();
this.setState({
loading: false,
visible: false
});
} */
handleSubmit = (e) => {
e.preventDefault();
this.props.form.validateFields(
(err, values) => {
if (!err) {
console.log('Received values of form: ', values);
console.log('fetch url = \n', values.textarea);
// fetch data
let url = values.textarea;
this.props.checkTestCommands(url);
this.onTest();
}else{
throw new Error(Error.name, Error.message);
}
}
);
};
testOK = () => {
this.setState({
loading: true
});
setTimeout(() => {
this.setState({
loading: false,
visible: false
});
this.props.hideModal();
}, 1000);
}
testCancel = () => {
this.setState({
visible: false
});
this.props.hideModal();
}
render() {
const {getFieldsValue, getFieldValue, setFieldsValue, getFieldDecorator} = this.props.form;
const formItemLayout = {
labelCol: {
span: 6
},
wrapperCol: {
span: 14
}
};
return (
<div>
{/* query object */}
{/* no footer */}
<Modal
title="命令行"
onOk={this.testOK}
onCancel={this.testCancel}
visible={this.state.visible}
footer={[]}
>
{/* loading={this.state.loading} */}
<Form
onSubmit={this.handleSubmit}
layout="horizontal">
<FormItem
label="测试命令行"
hasFeedback
{...formItemLayout}>
{
getFieldDecorator('textarea', {
rules: [
{
required: false,
message: ' url 长度必须 30 个字符之间'
}
],
initialValue: `http://10.1.5.31:8080/http/report/query?{"ApiName":"JY.Topic.Market_profile.Investors_data_statistics.AccountStatistics"}`,
})(
<Input
type="textarea"
placeholder="请先点击 “开始测试” 按钮!"
rows="15"
cols="500"
/>
)
}
</FormItem>
{/* onClick={this.props.checkTestCommands} */}
<FormItem style={{textAlign: "center"}}>
<Button
onClick={this.onTest}
type="primary"
htmlType="submit"
icon="hourglass"
style={{margin: "auto 10px"}}
loading={this.state.loading}>
开始测试
</Button>
<Button
onClick={this.testCancel}
size="large"
style={{margin: "auto 10px"}}
icon="close">
关闭
</Button>
{/* ref="submit_btn" */}
</FormItem>
</Form>
</Modal>
</div>
);
}
}
a screencut gif!
In your testOK method you are calling setState after 1sec delay when the TestModal form is already closed (that is the component is already unmounted). This is a noop.
this is where you have the problem
this.props.hideModal();
setTimeout(() =>
{
this.setState({
loading: false,
visible: false
});
}, 1000);
you hide the modal and after 1 second you try to change the state
here is one of my solutions!
Hooray, it works now! Just need to remove the duplicate set visible=false, it should be always visible=true;
because it should be call parent component's methods to realize show/hide itself!
Thanks to everybody, who made his answer to this question!
```jsx
class TestModal extends Component {
constructor(props, context) {
super(props, context);
this.state = {
loading: false,
visible: true
};
}
onTest = () => {
// copy state, do test fetch
this.testOK();
};
handleSubmit = (e) => {
e.preventDefault();
this.props.form.validateFields(
(err, values) => {
if (!err) {
console.log('Received values of form: ', values);
console.log('fetch url = \n', values.textarea);
// fetch data
let url = values.textarea;
this.props.checkTestCommands(url);
// this.onTest();
}else{
throw new Error(Error.name, Error.message);
}
}
);
};
testOK = (e) => {
// e.preventDefault();
console.log(`testOK e`, e);
this.setState({
loading: true
});
setTimeout(() => {
this.setState({
loading: false
});
this.props.hideModal();
}, 1000);
}
testCancel = () => {
this.props.hideModal();
}
render() {
const {getFieldsValue, getFieldValue, setFieldsValue, getFieldDecorator} = this.props.form;
const formItemLayout = {
labelCol: {
span: 6
},
wrapperCol: {
span: 14
}
};
// destructuring assignment
const {visible, loading} = this.state;
return (
<div>
{/* query object */}
{/* no footer */}
<Modal
title="命令行"
onOk={this.testOK}
onCancel={this.testCancel}
visible={visible}
footer={[]}
>
{/* loading={this.state.loading} */}
<Form
onSubmit={this.handleSubmit}
layout="horizontal">
<FormItem
label="测试命令行"
hasFeedback
{...formItemLayout}>
{
getFieldDecorator('textarea', {
rules: [
{
required: false,
message: ' url 长度必须 30 个字符之间'
}
],
initialValue: `10.1.5.31:8080/http/report/query?{"SecuCode":"000011","Names":"阳琨","ApiName":"fund.f9.fund_profile.FundManager.BasicInformations","WriteType":"json"}`,
})(
<Input
type="textarea"
placeholder="请先点击 “开始测试” 按钮!"
rows="15"
cols="500"
/>
)
}
</FormItem>
{/* onClick={this.props.checkTestCommands} */}
<FormItem style={{textAlign: "center"}}>
<Button
onClick={this.onTest}
type="primary"
htmlType="submit"
icon="hourglass"
style={{margin: "auto 10px"}}
loading={loading}>
开始测试
</Button>
<Button
onClick={this.testCancel}
size="large"
style={{margin: "auto 10px"}}
icon="close">
关闭
</Button>
{/* ref="submit_btn" */}
</FormItem>
</Form>
</Modal>
</div>
);
}
}
```