Pass value from one component to another values in array ReactJS - javascript

I'm trying to pass value from one component to another but when I do that, I get route in my http address as undefined instead of value. I have response from server in this form:
I'm trying to pass id values, and based on them make some actions. I get the error that GET request cannot be done due to value undefined.
Here is my code:
class StationService {
getStationById(id) {
return axios.get(STATION_API + '/station/' + id);
}
updateStation(station, id) {
return axios.put(STATION_API + '/station/' + id, station);
}
}
import React, { Component } from 'react';
import StationService from '../services/StationService';
class CreateStationComponent extends Component {
constructor(props) {
super(props)
this.state = {
station: {
id: this.props.match.params.id,
city: '',
name: '',
trains: [
{
number: '',
numberOfCarriages: ''
}
]
}
}
}
componentDidMount() {
if (this.state.station.id === '_add') {
return;
} else {
StationService.getStationById(this.state.id).then((res) => {
let station = res.data;
this.setState({ name: this.state.station[0].name, city: station[0].city })
});
}
console.log(this.state.station.name + 'dfddddd');
}
saveStation = (e) => {
e.preventDefault();
let station = { city: this.state[0].city, name: this.state[0].name }
if (this.state.id === '_add') {
StationService.createStation(station).then(res => {
this.props.history.push('/stations');
});
}
}
}
}
render() {
return (
<div>
...
</div >
);
}
}
From this component I want to pass id value to CreateStationComponent. But I don't know what I'm doing wrong.
import React, { Component } from 'react';
import StationService from '../services/StationService';
class ListStation extends Component {
constructor(props) {
super(props)
this.state = {
stations: []
}
this.addStation = this.addStation.bind(this);
this.editStation = this.editStation.bind(this);
this.deleteStation = this.deleteStation.bind(this);
this.showTrains = this.showTrains.bind(this);
}
deleteStation(id) {
StationService.deleteStation(id).then(res => {
this.setState({ stations: this.state.stations.filter(station => station.id !== id) });
})
}
editStation(id) {
this.props.history.push(`/add-station/${id}`);
}
componentDidMount() {
StationService.getStations().then((res) => {
this.setState({ stations: res.data });
})
}
render() {
return (
<div>
</div>
<div className="row">
<tbody>
{this.state.stations.map(
station =>
<tr key={station.id}>
<td>{station.city}</td>
<td>{station.name}</td>
<td>
<button onClick={() => this.editStation(station.id)} className="btn btn-info">Modify</button>
</tr>
)}
</tbody>
</table>
</div>
</div>
);
}
}
Any help would be appreciated.

Inside the constructor this.prop doesn't exist yet. Just access props.
constructor(props) {
super(props)
this.state = {
station: {
id: props.match.params.id,
city: '',
name: '',
trains: [
{
number: '',
numberOfCarriages: ''
}
]
}
}
}
Also pointed out in a comment, this.state.id isn't defined
StationService.getStationById(this.state.id)
but this.state.station.id is. Change the reference.
StationService.getStationById(this.state.station.id)
Since this.state.station is an object and not an array, this.setState({ name: this.state.station[0].name, city: station[0].city }) is also incorrect. this.state.station[0] is undefined and should throw error when attempting to access name. Update the reference.
this.setState({
name: this.state.station.name,
city: station[0].city,
})
And same for saveStation, update the state references.
saveStation = (e) => {
e.preventDefault();
let station = {
city: this.state.station.city,
name: this.state.station.name }
if (this.state.station.id === '_add') {
StationService.createStation(station).then(res => {
this.props.history.push('/stations');
});
}
}

Related

How to get value from this.state. Property of undefined

I'm trying to pass value from one component to another. First one looks like this:
class ListStation extends Component {
constructor(props) {
super(props)
this.state = {
stations: []
}
this.editStation = this.editStation.bind(this);
}
editStation(id) {
this.props.history.push(`/add-station/${id}`);
}
componentDidMount() {
StationService.getStations().then((res) => {
this.setState({ stations: res.data });
})
}
}
render() {
return (
<div>
<tbody>
{this.state.stations.map(
station =>
<tr key={station.id}>
<td>{station.city}</td>
<td>{station.name}</td>
<td>
<button onClick={() => this.editStation(station.id)} className="btn btn-info">Modify</button>
...
</div>
</div>
);
}
}
export default ListStation;
And another looks like this:
import React, { Component } from 'react';
import StationService from '../services/StationService';
class CreateStationComponent extends Component {
constructor(props) {
super(props)
this.state = {
station: {
id: this.props.match.params.id,
city: '',
name: '',
trains: [
{
number: '',
numberOfCarriages: ''
}
]
}
}
this.changeCityHandles = this.changeCityHandles.bind(this);
this.changeNameHandles = this.changeNameHandles.bind(this);
this.saveStation = this.saveStation.bind(this);
}
componentDidMount() {
if (this.state.station[0].id === '_add') {
return;
} else {
StationService.getStationById(this.state.id).then((res) => {
let station = res.data;
this.setState({ name: station[0].name, city: station[0].city })
});
}
console.log(this.state.station.city + 'dfddddd');
}
But when I try to pass value from one component to another I get error: Property of undefined. The response I get from API looks like this:
I'm trying to edit values based on the id taken from the first component but it seems to fail.
if (this.state.station[0].id === '_add') {
return;
}
Have a look at this if statement from your codebase I think you should remove [0] after this.state.station ... this is because station is an object not an Array
Change it to if (this.state.station.id === '_add') {

Update state array by object Id

I want to update state array object by particular id.
Suppose I have following object in state. And I tried to update by following way using id but, it doesn't work for me.
It didn't update state data.
this.state = {
data: [{id:'124',name:'qqq'},
{id:'589',name:'www'},
{id:'45',name:'eee'},
{id:'567',name:'rrr'}]
}
publishCurrentProject = user => {
this.setState(prevState => ({
data: prevState.data.map(item =>
item.id === user.id ? { ...user } : item
),
}))
}
let user = {id:'124',name:'ttt'};
publishCurrentProject(user);
Any help would be greatly appreciated.
Maybe the problem is on how you called the publishCurrentProject(), maybe you put that function in the wrong context. I use your implementation and it still works
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [
{ id: "124", name: "qqq" },
{ id: "589", name: "www" },
{ id: "45", name: "eee" },
{ id: "567", name: "rrr" }
]
};
this.handleClick = this.handleClick.bind(this);
this.publishCurrentProject = this.publishCurrentProject.bind(this);
}
handleClick(e) {
let user = { id: "124", name: "ttt" };
this.publishCurrentProject(user);
}
publishCurrentProject(user) {
this.setState((prevState) => ({
data: prevState.data.map((item) =>
item.id === user.id ? { ...user } : item
)
}));
}
render() {
return (
<div className="App">
<h1>Test</h1>
{this.state.data.map((el) => (
<p>{el.name}</p>
))}
<button onClick={this.handleClick}>Change</button>
</div>
);
}
}
Codesandbox for worked example

React Redux Data not being passed to props

I am new to React/Redux and I am stuck in a problem. My fetched data from API is not being passed to props. It's always an empty object.
I see that there might be some issues that I am not even aware of but I don't have a clue where to look for.
Please check my codes below:
RegisterPage.jsx
import { connect } from 'react-redux';
import { userActions } from '../_actions';
class RegisterPage extends React.Component {
constructor(props) {
super(props);
this.state = {
user: {
first_name: '',
last_name: '',
properties_id: '',
email: '',
phone_number: '',
password: ''
},
submitted: false,
checked: false,
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
componentDidMount() {
this.props.dispatch(userActions.getAll());
}
handleChange(event) {
const { name, value } = event.target;
const { user } = this.state;
this.setState({
user: {
...user,
[name]: value
},
checked: !this.state.checked
});
}
handleSubmit(event) {
event.preventDefault();
this.setState({ submitted: true });
const { user } = this.state;
const { dispatch } = this.props;
if(this.state.checked) {
if (user.first_name && user.last_name && user.properties_id &&
user.email && user.phone_number && user.password) {
dispatch(userActions.register(user));
}
} else {
alert("Please tick the checkbox to agree to Terms and Conditions");
}
}
render() {
const { registering, properties } = this.props;
const { user, submitted } = this.state;
return (......)
}
function mapStateToProps(state) {
const { registering } = state.registration;
const { properties } = state;
return {
properties,
registering
};
}
const connectedRegisterPage = connect(mapStateToProps)(RegisterPage);
export { connectedRegisterPage as RegisterPage };
users.reducers.js
export function users(state = {}, action) {
switch (action.type) {
case userConstants.GETALL_REQUEST:
return {
loading: true
};
case userConstants.GETALL_SUCCESS:
return {
items: action.properties
//action.users
};
case userConstants.GETALL_FAILURE:
return {
error: action.error
};
default:
return state
}
}
user.actions.js
export const userActions = {
login,
logout,
register,
getAll,
delete: _delete
};
function getAll() {
return dispatch => {
dispatch(request());
userService.getAll()
.then(
properties => dispatch(success(properties)),
error => dispatch(failure(error.toString()))
);
};
function request() { return { type: userConstants.GETALL_REQUEST } }
function success(properties) { return { type: userConstants.GETALL_SUCCESS, properties } }
function failure(error) { return { type: userConstants.GETALL_FAILURE, error } }
}
user.service.js
// Get All Properties
function getAll() {
const requestOptions = {
method: 'GET'
};
return fetch(`${config.apiUrl}/api/properties`, requestOptions).then(handleResponse).then(
properties => {
return properties;
}
);
}
Here's the screenshot of the console:
It is clear that properties array is not empty. But when I am going to use properties, it is empty. I don't know what's wrong. If anyone could help figure out what's wrong with my code or something that I missed, your help will be greatly appreciated. I just need to fix this so I could move forward. Thanks in advance!
I thinking that your state tree might not contain state.properties but instead state.items. Unless if you did something in combineReducers() that changes the shape of it again.
case userConstants.GETALL_SUCCESS:
return {
items: action.properties
//action.users
};
This part would probably cause action.properties to be stored in state.items instead of state.properties
I'd recommend using ReduxDevTools to make your life with state easier

React and checking condition after passing function through props

I'm fighting with my app since long time and slowly there is progress however I have still problem with one thing
I want to pass function thought props from Form Component to List component, after that I wish to check if button add was clicked if yes then I wish to launch function getMovie() inside List component and send another request to json database. with edit and remove it works as there are in same component, with adding button it is a bit more tricky.
the problem is that if I write just
else if (this.props.addClick) {
this.getMovie();
}
it's keep sending requests to database over and over
below is my code
Form Component
class Form extends React.Component {
constructor(props) {
super(props)
this.state = {
name: '',
type: '',
description: '',
id: '',
movies: [],
errors: "",
}
}
handleSubmit = e => {
e.preventDefault()
const url = `http://localhost:3000/movies/`;
if (this.state.name != "" && this.state.type != "" && this.state.description != "") {
axios
.post(url, {
name: this.state.name,
type: this.state.type,
description: this.state.description,
id: this.state.id,
})
.then(res => {
this.setState({
movies: [this.state.name, this.state.type, this.state.description, this.state.id]
})
})
.then(this.setState({
isButtonRemoveClicked: true
}))
}
else {
this.setState({
errors:"Please, Fill all forms above"
})
}
}
return (
<div>
<form onSubmit={this.handleSubmit}>
<input type="text" placeholder="Movie" onChange={this.handleChangeOne}/>
<input type="text" placeholder="Type of movie" onChange={this.handleChangeTwo}/>
<textarea placeholder="Description of the movie"
onChange={this.handleChangeThree}></textarea>
<input id="addMovie" type="submit" value="Add movie" ></input>
<p>{this.state.errors}</p>
</form>
<List removeClick={this.handleRemove} editClick={this.editMovie} addClick={this.handleSubmit}/>
</div>
)
List Component
class List extends React.Component {
constructor(props) {
super(props)
this.state = {
movies: [],
isButtonRemoveClicked: false,
}
}
componentDidMount() {
this.getMovie()
}
componentDidUpdate() {
if (this.state.isButtonRemoveClicked === true) {
this.getMovie();
this.timer = setTimeout(() => {
this.setState({
isButtonRemoveClicked: false
})
}, 10)
}
else if (this.props.addClick === true) {
this.getMovie();
}
}
componentWillUnmount() {
clearTimeout(this.timer)
}
getMovie = () => {
const url = `http://localhost:3000/movies`;
axios
.get(url)
.then(res => {
const movies = res.data;
this.setState({
movies: movies,
})
})
.catch((err) => {
console.log(err);
})
}
There is nothing magical ;)
You're start loading data from componentDidUpdate() ... data loads, componentDidUpdate is fired again, again...
Don't handle events this way.
If your main objective is to call function in child component from parent component, then you can use refs.
Example in your code :-
class Form extends React.Component {
constructor(props) {
super(props)
this.state = {
name: '',
type: '',
description: '',
id: '',
movies: [],
errors: "",
}
}
handleSubmit = e => {
e.preventDefault()
const url = `http://localhost:3000/movies/`;
if (this.state.name != "" && this.state.type != "" && this.state.description != "") {
axios
.post(url, {
name: this.state.name,
type: this.state.type,
description: this.state.description,
id: this.state.id,
})
.then(res => {
this.setState({
movies: [this.state.name, this.state.type, this.state.description, this.state.id]
})
})
.then(
this.list.getMovie(); // call child function here
this.setState({
isButtonRemoveClicked: true
}))
}
else {
this.setState({
errors:"Please, Fill all forms above"
})
}
}
return (
<div>
<form onSubmit={this.handleSubmit}>
<input type="text" placeholder="Movie" onChange={this.handleChangeOne}/>
<input type="text" placeholder="Type of movie" onChange={this.handleChangeTwo}/>
<textarea placeholder="Description of the movie"
onChange={this.handleChangeThree}></textarea>
<input id="addMovie" type="submit" value="Add movie" ></input>
<p>{this.state.errors}</p>
</form>
<List
ref={list => this.list=list } // Create ref here
removeClick={this.handleRemove}
editClick={this.editMovie}
addClick={this.handleSubmit}/>
</div>
)
And in list component no need to use componentDidUpdate getMovie() call.
class List extends React.Component {
constructor(props) {
super(props)
this.state = {
movies: [],
isButtonRemoveClicked: false,
}
}
componentDidMount() {
this.getMovie()
}
getMovie = () => {
const url = `http://localhost:3000/movies`;
axios
.get(url)
.then(res => {
const movies = res.data;
this.setState({
movies: movies,
})
})
.catch((err) => {
console.log(err);
})
}
I think you are handling events in an overcomplicated manner. Why don't you lift props from inside the List component and just trigger the desired behaviour in the Form?. For example:
class List extends React.Component {
handleAddClick() {
this.props.onAddClick()
}
handleEditClick() {
this.props.onEditClick()
}
handleRemoveClick() {
this.props.onRemoveClick()
}
render() {
return (
<div>
<button onClick={() => this.handleAddClick()}>Add</button>
<button onClick={() => this.handleEditClick()}> Edit</button>
<button onClick={() => this.handleRemoveClick()} > Remove</button>
</div>
})
}
and
class Form extends React.Component {
getMovie() {
// Make AXIOS request
}
handleAdd() {
this.getMovie();
}
handleRemove() {
// REMOVE CODE
}
handleEdit() {
// EDIT CODE
}
render() {
<form>
{/* Form elements */}
<List
onAddClick={() => this.handleAdd()}
onRemoveClick={() => this.handleRemove()}
onEditClick={() => this.handleEdit()}
/>
</form>
}
}

React Enzyme Jest error jest.fn() should be called

My component is as below
import React from 'react';
import { connect } from 'react-redux';
import { Button } from 'react-bootstrap';
import UserActions from '../../../actions/sampleUserAction';
import UserForm from '../../../views/sample/userForm';
import UsersList from '../../../views/sample/usersList';
#connect(store => ({
users: store.sampleUserReducer.users,
}))
export default class UserComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
displayForm: false,
user: { id: '', fName: '', lName: '' },
isCreationMode: true,
};
this.addNewUser = this.addNewUser.bind(this);
this.handleChange = this.handleChange.bind(this);
this.submitForm = this.submitForm.bind(this);
this.editUser = this.editUser.bind(this);
this.deleteUser = this.deleteUser.bind(this);
}
addNewUser() {
this.setState({
displayForm: !this.state.displayForm,
isCreationMode: true,
user: { id: '', fName: '', lName: '' },
});
}
createUser(users) {
users.push({
id: users.length + 1,
fName: this.state.user.fName,
lName: this.state.user.lName,
});
return users;
}
updateUser(users) {
users.forEach((user) => {
if (user.id === this.state.user.id) {
user.fName = this.state.user.fName;
user.lName = this.state.user.lName;
}
});
return users;
}
submitForm(e) {
e.preventDefault();
let { users } = this.props;
if (this.state.isCreationMode) {
users = this.createUser(users);
} else if (!this.state.isCreationMode) {
users = this.updateUser(users);
}
this.addNewUser();
this.props.dispatch(UserActions.listUsers(users));
}
handleChange(e) {
const { id } = this.state.user;
let { fName, lName } = this.state.user;
if (e.target.name === 'fName') {
fName = e.target.value;
}
if (e.target.name === 'lName') {
lName = e.target.value;
}
this.setState({ user: { id, fName, lName } });
}
editUser(e, id) {
const { users } = this.props;
let user = users.filter(obj => obj.id === id);
user = user.length > 0 ? user[0] : null;
if (user != null) {
this.setState({
displayForm: true,
isCreationMode: false,
user: { id: user.id, fName: user.fName, lName: user.lName },
});
}
}
deleteUser(e, id) {
let { users } = this.props;
users = users.filter(user => user.id !== id);
this.props.dispatch(UserActions.listUsers(users));
}
render() {
console.log(this.state.displayForm);
return (
<div className="container-fluid">
<div className="well">
Sample Users App With Redux
</div>
<UserForm
displayForm={this.state.displayForm}
isCreationMode={this.state.isCreationMode}
submitForm={this.submitForm}
handleChange={this.handleChange}
user={this.state.user}
addNewUser={this.addNewUser}
/>
<UsersList
users={this.props.users}
editUser={this.editUser}
deleteUser={this.deleteUser}
/>
<div className="clearfix">
<Button bsStyle="primary" onClick={this.addNewUser}>Add User</Button>
</div>
</div>
);
}
}
and test file is as below
import React from 'react';
import { createMockStore } from 'redux-test-utils';
import { shallowWithStore } from 'enzyme-redux';
import { Button } from 'react-bootstrap';
import UserComponent from '../../../../src/components/containers/sample/userComponent';
import UserForm from '../../../../src/views/sample/userForm';
import UsersList from '../../../../src/views/sample/usersList';
describe('UsersComponent', () => {
let store;
let container;
const props = {
submitForm: jest.fn(),
addNewUser: jest.fn(),
};
beforeEach(() => {
const defaultState = { sampleUserReducer: { users: [] } };
store = createMockStore(defaultState);
container = shallowWithStore(<UserComponent />, store);
});
it('should work', () => {
expect(true).toEqual(true);
});
it('container should have UserForm component', () => {
expect(container.dive().find(UserForm)).toHaveLength(1);
});
it('container should have UsersList component', () => {
expect(container.dive().find(UsersList)).toHaveLength(1);
});
it('should have add new user button', () => {
expect(container.dive().find(Button)).toHaveLength(1);
expect(container.dive().find(Button).dive().text()).toEqual('Add User');
});
it('On click add user button', () => {
container.dive().find(Button).simulate('click');
expect(props.addNewUser).toHaveBeenCalled();
});
});
I'm using jest, enzyme, enzyme-redux. I'm new to react unit testing. Last test case is giving error as below. React version is 16.x. In last test case I'm trying to call mocked jest function on button click. For button using react-bootstrap inbuilt Button component
expect(jest.fn()).toHaveBeenCalled()
Expected mock function to have been called.
You are likely to need to add container.update(); which forces a re-render after external inputs like clicking.
http://airbnb.io/enzyme/docs/api/ShallowWrapper/update.html
Sometimes container.update() does not work and in such cases, try container.instance().forceUpdate() in your tests after the click which updates the component after the state changes.
Another option would be to use jest's spy to assert that addNewUser was called.
const spy = jest.spyOn(container.instance(), 'addNewUser');
container.dive().find(Button).simulate('click');
expect(spy).toBeCalled();

Categories