Reputation: 664
So I am trying to fetch and display players that belong to team. I have a dropdown button which has a selection of said teams and on click, it should render the chosen team's players. The problem I have is that on click, it renders all of the players in my seed data, irrelevant of the team it belongs to. I am not sure what I am missing but here is the code I have. In Postman when I put "http://localhost:3000/api/teams/1/players" it is returning all of the players instead of just the one that belongs to team with id 1.
Edit: I fixed the issue with filtering data as now the url "http://localhost:3000/api/teams/1/players" in Postman works correctly. However I am getting 'TypeError: this.props.players.cards.map is not a function' from the DataContainer when it tries to render the players after clicking the team on the dropdown button. All I did was change the index in my controller. Also the terminal says "ActiveRecord::RecordNotFound (Couldn't find Team with 'id'=Chaos):"
Here is my players controller:
class PlayersController < ApplicationController
before_action :set_params, only: [:show]
def index
@players = Team.find(params[:team_id]).players
render json: @players, status: 200
end
def show
render json: @player, status: 200
end
private
def player_params
params.require(:player).permit(:name, :player_type, :cost,
:movement_allowance, :strength, :agility, :armour_value, :skills,
:team_id)
end
def set_params
@player = Player.find(params[:id])
end
My filter file which contains the dropdown button (edited):
class PlayerFilter extends Component {
constructor() {
super();
this.state = { data: [] };
}
async fetchButtonTeams() {
const response = await fetch(`/api/teams`);
const json = await response.json();
this.setState({ data: json });
}
componentDidMount(){
this.fetchButtonTeams()
}
handleTeamSelection = e => {
e.preventDefault();
const { target: { title }} = e;
this.props.setTeam(title);
this.props.fetchPlayers(title);
};
render() {
return (
<Container>
<Row>
<DropdownButton id="dropdown-player-button" title={this.props.team}>
{this.state.data.map(team => (
<div key={team}>
<Dropdown.Item onClick={this.handleTeamSelection} title={team.name}>{team.name}</Dropdown.Item>
</div>
))}
</DropdownButton>
</Row>
</Container>
)
}
}
const mapStateToProps = state => {
return {
team: state.players.team
}
};
const mapDispatchToProps = dispatch => {
return {
fetchPlayers: params => dispatch(fetchPlayers(params)),
fetchTeams: params => dispatch(fetchTeams(params)),
setTeam: team => dispatch({ type: "SET_TEAM", team })
}
};
export default connect(mapStateToProps, mapDispatchToProps)(PlayerFilter)
My player fetch being called in the filter (edited)
export const fetchPlayers = teamId => {
return dispatch => {
dispatch({ type: 'LOADING_PLAYERS' });
fetch(`/api/teams/${teamId}/players`)
.then(res => res.json())
.then(responseJSON => { dispatch({ type: 'ADD_PLAYERS', cards: responseJSON})
})
}
My DataContainer:
class DataContainer extends Component {
displayCards = () => {
switch(this.props.path) {
case "teams":
return (this.props.teams.cards.map(card => (
<NavLink style={{ color: "black" }} to={`/teams/${card.id}`} key={card.id}><TeamCard info={card} /></NavLink>
)));
case "players":
return (this.props.players.cards.map(card => (
<NavLink style={{ color: "black" }} to={`/players/${card.id}`} key={card.id}><PlayerCard info={card} /></NavLink>
)));
default:
return (<div>Empty</div>)
}
};
render() {
return (
<CardColumns>
{this.displayCards()}
</CardColumns>
)
}
}
const mapStateToProps = state => {
return {
teams: state.teams,
players: state.players
}
};
export default connect(mapStateToProps)(DataContainer)
My app.js:
import React from 'react';
import './App.css';
import { connect } from 'react-redux'
import NavButtonsContainer from './containers/NavButtonsContainer';
import DataContainer from "./containers/DataContainer";
import PlayerDisplay from "./components/PlayerDisplay";
import PlayerFilter from "./components/PlayerFilter";
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import 'bootstrap/dist/css/bootstrap.min.css';
import Container from "react-bootstrap/Container";
import Row from "react-bootstrap/Row"
import Col from "react-bootstrap/Col"
const App = props => {
return (
<Container>
<Router>
<Switch>
<Route path="/teams/:id">
<TeamDisplay info={props.info} />
</Route>
<Route path="/players/:id">
<PlayerDisplay info={props.info} />
</Route>
<Route path="/teams">
<Row>
<Col><TeamFilter path="teams" /></Col>
</Row>
<Row>
<NavButtonsContainer />
</Row>
<Row>
<DataContainer path="teams" />
</Row>
</Route>
<Route path="/players">
<Row>
<Col><PlayerFilter path="players" /></Col>
</Row>
<Row>
<NavButtonsContainer />
</Row>
<Row>
<DataContainer path="players" />
</Row>
</Route>
<Route path="/">
<NavButtonsContainer />
</Route>
</Switch>
</Router>
</Container>
);
};
const mapStateToProps = state => {
return {
info: state.piece
}
};
export default connect(mapStateToProps)(App);
Upvotes: 1
Views: 425
Reputation: 202846
It was a little confusing calling your action creator parameter params
, but I believe you need to access an (some) id
property from it to correctly construct your request URL path. Giving parameters better, more meaningful names goes a long way in code readability.
export const fetchPlayers = teamId => {
return dispatch => {
dispatch({ type: 'LOADING_PLAYERS' });
fetch(`/api/teams/${teamId}/players`) // interpolate id into path string
.then(res => res.json())
.then(responseJSON => { dispatch({ type: 'ADD_PLAYERS', cards: responseJSON});
});
}
The next part is to call this action with the correct parameter from the handler.
// Playerfilter
handleTeamSelection = e => {
e.preventDefault();
const { target: { title }} = e;
this.props.setTeam(title);
this.props.fetchPlayers(title); // This is what I'm not sure about, but I'll get to that later
};
I couldn't immediately tell from the react-bootstrap docs on Dropdown.Item
if it passes extra props to children (i.e. your title
prop) or what is passed with the onClick
and onSelect
handlers (other than an event), or if perhaps it is more like an html select element where your handler needs to be bound to the Dropdown
's onSelect
prop instead of on each item.
I also took a peek at your db directory to try and glean your teams
schema to figure out what the id
field is. I noticed there is nothing relating your teams
table to your players
table, i.e. the players
table has a field team_id
that looks like it should be a foreign key into teams
, so you can do a join. Maybe this is by design and you don't need to do multi-table queries.
In Postman when I put "http://localhost:3000/api/teams/1/players" it is returning all of the players instead of just the one that belongs to team with id 1
If this is your local server not returning filtered data, then this is indicative of an issue with your server code not querying your DB correctly and returning bad results.
I think you should first sort out your DB issue(s) and ensure it is returning correct responses, then tackle your client code to ensure it is making requests correctly.
Upvotes: 1