I'm learning React by making a Spotify clone and for now, what I'm trying to do is to show Spotify sections such as "last played songs", "top artists" and "top songs" through a component called Body.js.
I get the data from a Spotify official API library created by jmperez in a useEffect hook in the App.js component. Once I get the data from the API, I store it in an object called initialState in a file called reducer.js.
This reducer.js file contains the initial state and the reducer function for a custom hook called useDataLayer.js which is a useContext hook that passes as value a useReducer to all the branches of my program. This way what I do is update the initialState from App.js and access this object through the useDataLayer hook in the different branches of my program (among them the Body component).
The problem I am having now is that it is not rendering the three sections mentioned before in Spotify, but only shows me one which is the "top songs". The weird thing is that for a second it does render the other components as if it was getting the data and rendering but then it updates and they disappear. Please if someone can help me with this problem and explain to me why this happens it would be great.
App.js code
import React, {useState, useEffect} from 'react';
import Login from './components/Login';
import Player from './components/Player';
import { getTokenFromResponse } from './spotify';
import './styles/App.scss';
import SpotifyWebApi from "spotify-web-api-js";
import { library } from '#fortawesome/fontawesome-svg-core';
import { fas } from '#fortawesome/free-solid-svg-icons';
import { fab } from '#fortawesome/free-brands-svg-icons';
import { useDataLayer } from './components/Hooks/useDataLayer';
library.add(fas, fab);
//spotify library instance
const spotify = new SpotifyWebApi();
function App() {
const [token, setToken] = useState(null);
const [{ user }, dispatch] = useDataLayer();
// where I get the necessary data from the api
useEffect(() => {
// function to get access token
let accessToken = getTokenFromResponse();
window.location.hash = '';
if(accessToken){
spotify.setAccessToken(accessToken);
setToken(accessToken);
//FROM HERE I GET THE DATA I NEED
// here I get the data of my user
spotify.getMe().then((data) =>{
dispatch({
type: "GET_USER",
user: date
})
});
spotify.getUserPlaylists().then((data) => {
dispatch({
type: "GET_PLAYLISTS",
playlists: dates
})
});
spotify.getMyTopTracks({limit: 4}).then((data) => {
dispatch({
type: "GET_TOP_TRACKS",
top_tracks:data,
})
});
spotify.getMyRecentlyPlayedTracks({limit: 4}).then((data) => {
dispatch({
type: "RECENTLY_PLAYED",
recently_played: date,
})
});
spotify.getMyTopArtists({limit: 4}).then((data) => {
dispatch({
type: "GET_TOP_ARTISTS",
top_artists: date,
})
});
}
}, [token])
//if the token is valid enter Player.js where Body.js is inside and if not return to the login component
return (
<div className="App">
{ token ? <Player spotify= {spotify} /> : <Login />}
</div>
);
}
export defaultApp;
Body.js code
import React from 'react'
import '../styles/Body.scss'
import { useDataLayer } from "./Hooks/useDataLayer.js";
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome';
function Body({ spotify }) {
//get the properties of the necessary data that I want to display in this component with useDataLayer
const [{ spotify_recommendations, recently_played, top_tracks, top_artists }, dispatch] = useDataLayer();
return (
<div className= "main-body">
<div className= "body-option">
<span className= "see-more">See All</span>
<FontAwesomeIcon icon={['fas', 'arrow-right']} />
<div>
{
//to show the image and info of the track
recently_played?.items.map((item, index) =>{
return (
<div className= "track" key= {index}>
<img src= {item.track.album.images[1].url} alt= "recently played track"></img>
<div className= "track-data">
<h3>{item.track.name}</h3>
<p>{item.track.artists.map(artist => artist.name).join(", ")}</p>
</div>
</div>
)
})
}
</div>
</div>
<div className= "body-option">
<span className= "see-more">See All</span>
<FontAwesomeIcon icon={['fas', 'arrow-right']} />
<div>
{
//to show the image and info of the track
top_tracks?.items.map((topArtist, index) => {
return (
<div className= "track" key= {index}>
<img src= {topArtist.album.images[1].url} alt= "recently played track"></img>
<div className= "track-data">
<h3>{topArtist.name}</h3>
<p>{topArtist.artists.map(artist => artist.name).join(", ")}</p>
</div>
</div>
)
})
}
</div>
</div>
<div className= "body-option">
<span className= "see-more">See All</span>
<FontAwesomeIcon icon={['fas', 'arrow-right']} />
<div>
{
//to show the image and info of the artist
top_artists?.items.map((topTrack, index) => {
return (
<div className= "track" key= {index}>
<img src= {topTrack.images[1].url} alt= "recently played track"></img>
<div className= "track-data">
<h3>{topTrack.name}</h3>
<p>{topTrack.genres.join(", ")}</p>
</div>
</div>
)
})
}
</div>
</div>
</div>
)
}
export default Body
Code of my custom hook useDataLayer.js
import React, {useContext, createContext, useReducer} from 'react'
let DataContext = createContext();
export function DataLayer({reducer, initialState, children}) {
return (
<DataContext.Provider value= {useReducer(reducer, initialState)}>
{children}
</DataContext.Provider>
)
}
export let useDataLayer = () => useContext(DataContext);
SideBar.js: the component where i display the user's playlist
import React from 'react';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome';
import { useDataLayer } from './Hooks/useDataLayer.js';
import '../styles/SideBar.scss';
function SideBar({ spotify }){
const [{ playlists }, dispatch] = useDataLayer();
return (
<div className= "side-bar">
<div className= "side-bar-options">
<div className= "spotify-logo">
<FontAwesomeIcon icon={['fab', 'spotify']} size= "3x"/>
<h1 className= "spotify-title">Spotify</h1>
</div>
<a className= "option" href= "./Player.js">
<FontAwesomeIcon icon={['fas', 'home']} />
<p className= "option-title" >Inicio</p>
</a>
<a className= "option" href= "./Player.js">
<FontAwesomeIcon icon={['fas', 'search']} />
<p className= "option-title">Buscar</p>
</a>
<a className= "option" href= "./Player.js">
<FontAwesomeIcon icon={['fas', 'headphones']} />
<p className= "option-title" >Tu Biblioteca</p>
</a>
</div>
<p className= "playlist-title">PLAYLISTS</p>
<div className= "playlists">
{
playlists?.items?.map(
(list, index) =>
<p className="playlists-option"
key={index}
>
{list.name}
</p>
)
}
</div>
</div>
)
}
export default SideBar;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
Player.js
import React from 'react';
import "../styles/Player.scss";
import SideBar from "./SideBar.js";
import Body from "./Body.js";
function Player({spotify}) {
return (
<div className= "player-container">
<div className= "player_body">
<SideBar spotify= {spotify} />
<Body spotify= {spotify} />
</div>
</div>
)
}
export default Player;
spotify.js: the code where i get the token from the URL
const authEndpoint = "https://accounts.spotify.com/authorize";
const clientId = '84c134b175474ddabeef0e0b3f9cb389'
const redirectUri = 'http://localhost:3000/'
const scopes = [
"user-read-currently-playing",
"user-read-recently-played",
"user-read-playback-state",
"user-top-read",
"user-modify-playback-state",
"user-follow-modify",
"user-follow-read",
"playlist-modify-public",
"playlist-modify-private",
"playlist-read-private",
"playlist-read-collaborative"
];
//obtain acces token from url
export const getTokenFromResponse = () => {
let params = new URLSearchParams(window.location.hash.substring(1));
let token = params.get("access_token");
return token;
};
//acces url
const accessUrl = `${authEndpoint}?client_id=${clientId}&redirect_uri=${redirectUri}&scope=${scopes.join("%20")}&response_type=token&show_dialog=true`;
export default accessUrl;
Thank you very much for your time and attention.
By the look of it, your useEffect hook is being called twice in quick succession. It has the dependency of token, which starts as null for your first render. Inside the hook you then read the accessToken and set the state for it to your token. Doing this will trigger another render because the dependency changed from null to the value of your access token.
I would suggest to simply remove token from your useEffect dependency array (so that your hook acts as an "on mounted" event) and see if that gets you the desired effect. You should eventually move the getTokenFromResponse outside the hook, assuming it is not available on first render, as I'm just guessing it is immediately available.
Post comments, it might be better to separate your initializing code into a useEffect that only runs once, when the component is first mounted. This way, as I now suspect getTokenFromResponse() returns a new Object every call, you only need to call it once. Note, if it returns a Promise, this won't work, so you should verify this first before trying.
To better illustrate my point, I've put it in code with some comments:
useEffect(() => {
setToken(getTokenFromResponse());
// this only needs to happen once
window.location.hash = '';
}, []; // <- empty dependency array will only run once, on mount
useEffect(() => {
// out of the 2 times this will be called,
// it should only pass this condition on the last/2nd
if(token){
spotify.setAccessToken(token);
//FROM HERE I GET THE DATA I NEED
// here I get the data of my user
spotify.getMe().then((data) =>{
dispatch({
type: "GET_USER",
user: date
})
});
spotify.getUserPlaylists().then((data) => {
dispatch({
type: "GET_PLAYLISTS",
playlists: dates
})
});
spotify.getMyTopTracks({limit: 4}).then((data) => {
dispatch({
type: "GET_TOP_TRACKS",
top_tracks:data,
})
});
spotify.getMyRecentlyPlayedTracks({limit: 4}).then((data) => {
dispatch({
type: "RECENTLY_PLAYED",
recently_played: date,
})
});
spotify.getMyTopArtists({limit: 4}).then((data) => {
dispatch({
type: "GET_TOP_ARTISTS",
top_artists: date,
})
});
}
// this will trigger the effect any time token changes,
// which should now only be twice:
// 1st at it's default state value (null)
// 2nd after the previous useEffect sets its state
}, [token]);
Hope that helps but, if it's a promise, you'll need to handle it according (suggestion: moving the setToken into the then)
Good luck!
Related
I have two components "search" and "Maindata". I am passing the input value from the search component to maindata component where I want to replace the city attribute with the input value(location) in API. but the browser display went blank and the console give an undefined 'city' error, etc. I got stuck in this problem if anyone has a solution?
Here "search" component;
import React , {useState} from "react";
import Maindata from "./Maindata";
import "../Componentstyle/search.css";
export default function Search() {
const [location, setLocation] = useState();
<Maindata city={location}/>
return (
<div className="main">
<nav className="istclass">
<form className="form">
<div className="search">
<input
value={location}
placeholder="search city"
className="searchbox"
onChange={(e)=>setLocation(e.target.value)}
/>
<button className="nd" onClick={(e)=>setLocation(e.target.value)}>
Submit
</button>
</div>
</form>
</nav>
</div>
);
}
Here "Maindata" component;
import React, { useState, useEffect } from "react";
import "../Componentstyle/Main.css";
export default function Maindata(props) {
const [data, setData] = useState(null);
let city = console.log(props.city);
let weather = async () => {
const key = "1ab6ef20384db1d7d9d205d609f7eef0";
await fetch(
`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${key}&units=metric&formatted=0`
)
.then((response) => response.json())
.then((actualData) => setData(actualData));
};
useEffect(() => {
weather();
}, []);
if (!data) {
return <div>Loading...</div>;
}
const link = `http://openweathermap.org/img/w/${data.weather[0].icon}.png`;
return (
<div className="maindata">
<div className="city">{data.name}</div>
<div className="temp">{data.main.temp} C</div>
<div className="icon">
<img src={link} alt="not found" />{" "}
</div>
<div className="feel">feels Like {data.main.feels_like} C</div>
<div className="wind">Wind {data.wind.speed} Km/hr</div>
<div className="cloudy">{data.weather[0].main}</div>
<div className="humidity">humidity {data.main.humidity}%</div>
<div className="sunrise">
sunrise :- {new Date(data.sys.sunrise * 1000).toUTCString()}{" "}
</div>
<div className="sunset">
sunset :- {new Date(data.sys.sunset * 1000).toUTCString()}
</div>
</div>
);
}
<Maindata city={location}/>
keep this line of code inside the return
In your example, there is no meaningful connection between the Search and Maindata components. Meaning Maindata component will not get rendered on the page because it is not in the return statement of the Search component.
The Maindata component as below, is in JSX format, when you use JSX in your code in React, under the hood, React.createElement() method is being called.
Each call to React.createElement returns an object describing what to render to that part of the page. So it makes sense to put the Maindata component in the return statement. That is responsible for rendering the HTML elements from that component when you're loading a page containing that component.
<Maindata city={location}/> // is JSX and should be in the return statement to get rendered on the page and showing the right location
As I understand I should be able to access Redux state inside a React Component if I use Connect from react-redux and then use function mapStateToProps which should send redux state as props to component?
In this following code I try to do that, and I tried all sorts of ways, but it does not work.
Here is code of a react component.
After the imports I write mapStateToProps functions, after which I i thought Id get state.
I know state contains a bit of data, for example profile. So I thought Id make test = state.profile and a bit later in code I will JSON stringify props, and results is only a article json string.
After code you will see an image from Chrome Dev Tools where you can see that state contains many things.
Id like to access them in this component. What am I doing wrong and how can I make it work?
From Image you see state has 'common' field, where there is 'token' and 'currentUser' data inside. Id like to check those values specifically in this component.
import React from 'react';
import { Link } from 'react-router-dom';
import agent from '../agent';
import { connect } from 'react-redux';
import { ARTICLE_FAVORITED, ARTICLE_UNFAVORITED } from '../constants/actionTypes';
const mapStateToProps = (state) => {
return {test: state.profile}
}
const FAVORITED_CLASS = 'btn btn-sm btn-primary';
const NOT_FAVORITED_CLASS = 'btn btn-sm btn-outline-primary';
const mapDispatchToProps = dispatch => ({
favorite: slug => dispatch({
type: ARTICLE_FAVORITED,
payload: agent.Articles.favorite(slug)
}),
unfavorite: slug => dispatch({
type: ARTICLE_UNFAVORITED,
payload: agent.Articles.unfavorite(slug)
})
});
const ArticlePreview = props => {
**console.log(JSON.strinify(props));**
const article = props.article;
const favoriteButtonClass = article.favorited ?
FAVORITED_CLASS :
NOT_FAVORITED_CLASS;
const handleClick = ev => {
ev.preventDefault();
if (article.favorited) {
props.unfavorite(article.slug);
} else {
props.favorite(article.slug);
}
};
return (
<div className="article-preview">
<div className="article-meta">
<Link to={`/#${article.author.username}`}>
<img src={article.author.image} alt={article.author.username} />
</Link>
<div className="info">
<Link className="author" to={`/#${article.author.username}`}>
{article.author.username}
</Link>
<span className="date">
{new Date(article.createdAt).toDateString()}
</span>
</div>
<div className="pull-xs-right">
<button className={favoriteButtonClass} onClick={handleClick}>
<i className="ion-heart"></i> {article.favoritesCount}
</button>
</div>
</div>
<Link to={`/article/${article.slug}`} className="preview-link">
<h1>{article.title}</h1>
<p>{article.description}</p>
<span>Read more...</span>
<ul className="tag-list">
{
article.tagList.map(tag => {
return (
<li className="tag-default tag-pill tag-outline" key={tag}>
{tag}
</li>
)
})
}
</ul>
</Link>
</div>
);
}
export default connect(() => ({}), mapDispatchToProps)(ArticlePreview);
I'm new in React and I'm doing a little app with PokeAPI. I have a component called PokemonDetail in which I want to show the details of a pokemon, but the app throws me the next error
TypeError: Cannot read property 'front_default' of undefined
my component looks like this:
import React from "react";
const PokemonDetail = ({ pokemon }) => {
return (
<div>
<div className="text-center">{pokemon.name}</div>
<img src={pokemon.sprites.front_default} alt={pokemon.name} />
{pokemon.id}
</div>
);
};
export default PokemonDetail;
And the App component from which the PokemonDetail recive the prop of pokemon looks like this:
import React from "react";
import PokeAPI from "../apis/PokeAPI";
import SearchBar from "./SearchBar";
import PokemonDetail from "./PokemonDetail";
class App extends React.Component {
state = { pokemon: '' };
onTermSubmit = async term => {
try {
const response = await PokeAPI.get(`pokemon/${term}`);
this.setState({ pokemon: response.data });
console.log(response);
} catch (error) {
console.log("No existe");
}
};
render() {
return (
<div className="container">
<div className="row mt-3">
<div className="col">
<SearchBar onFormSubmit={this.onTermSubmit} />
</div>
</div>
<div className="row mt-3">
<div className="col-9" />
<div className="col-3">
<PokemonDetail pokemon={this.state.pokemon} />
</div>
</div>
</div>
);
}
}
export default App;
I don't understand why it throws me this error because only throws it with this and other properties of the json. With the name property works and wait until I send it some props, same with the id but no with the front_default property, which is a url of a image.
Because ajax is slower than react rendering, you can use a loading component before you get the data.
const PokemonDetail = ({ pokemon }) => {
if(pokemon.sprites == undefined){
return(
<div>
Loading...
</div>
);
}
return (
<div>
<div className="text-center">{pokemon.name}</div>
<img src={pokemon.sprites.front_default} alt={pokemon.name} />
{pokemon.id}
</div>
);
};
Very likely just an AJAX issue, your component renders before it has time to complete your request to the API. Try adding an additional check before rendering the image.
import React from "react";
const PokemonDetail = ({ pokemon }) => {
return (
<div>
<div className="text-center">{pokemon.name}</div>
{pokemon.sprites ? (
<img src={pokemon.sprites.front_default} alt={pokemon.name} />
) : (
null
)
}
{pokemon.id}
</div>
);
};
export default PokemonDetail;
#ZHAOXIANLONG gave you the best solution (use a loading component until you receive data), but, if you do not use a loading component, you can use the get method from lodash library [1] in order to avoid a possible error.
import React from "react";
import _ from 'lodash';
const PokemonDetail = ({ pokemon }) => {
const front_default = _.get(pokemon, 'sprites.front_default', 'DEFAULT_VALUE');
const name = _.get(pokemon, 'name', 'DEFAULT_VALUE');
return (
<div>
<div className="text-center">{pokemon.name}</div>
<img src={pokemon.sprites.front_default} alt={pokemon.name} />
{pokemon.id}
</div>
);
};
export default PokemonDetail;
where the third parameter ('DEFAULT_VALUE') is a default value that will be used if the lodash can not retrieve a value for your query.
PS: I advise you to use lodash even in #ZHAOXIANLONG solution if you know that your API Server can be changed.
[1] https://lodash.com/docs/4.17.11#get
The initial state is { pokemon: '' }; pokemon is an empty string. PokemonDetail is referring to pokemon.sprites.front_default, but pokemon is initially a string and a string does not have a field called sprites.
If you are expecting pokemon to eventually become an object, you could initialize it to something that looks like an object:
state = { pokemon: { sprites: {front_default: '' }}};
I am creating a todo List application using reactJS. If I write two different logic in two separate files it works just fine but while combining those two files it gives an error.
RenderRemaining.js file:
import React from 'react';
import { connect } from 'react-redux';
import store from '../store/store';
import RenderRemainingData from './RenderRemainingData';
const RenderRemaining = (props) => {
return (
<div>
<h2>Tasks: </h2>
<hr />
{props.list.map((detail) => {
return <RenderRemainingData key={detail.id} {...detail} />
})}
</div>
);
}
const mapStateToProps = (state) => {
return {
list: state.todoReducer
};
}
export default connect(mapStateToProps)(RenderRemaining);
RenderRemainingData.js file:
import React from 'react';
import { connect } from 'react-redux';
import removeTodo from '../actions/removeTodo';
const RenderRemainingData = ({ dispatch, todo, id, description, isCompleted }) => {
if (!isCompleted) {
return (
<div key={id}>
<h4>{todo}
<span className="float-right">
<a href="#" title="Remove" onClick={() => {
dispatch(removeTodo({todo, description, id}));
}}><i className="fas fa-times"></i></a>
</span>
</h4>
<p>{description}</p>
</div>
);
}
return false;
}
export default connect()(RenderRemainingData);
Now above code works just fine.
After combining above two files as one js file in RenderRemaining.js and deleting RenderRemainingData.js file.
RenderRemaining.js file: (after combining)
import React from 'react';
import { connect } from 'react-redux';
import store from '../store/store';
import removeTodo from '../actions/removeTodo';
const RenderRemainingData = ({ dispatch, todo, id, description, isCompleted }) => {
if (!isCompleted) {
return (
<div key={id}>
<h4>{todo}
<span className="float-right">
<a href="#" title="Remove" onClick={() => {
dispatch(removeTodo({todo, description, id}));
}}><i className="fas fa-times"></i></a>
</span>
</h4>
<p>{description}</p>
</div>
);
}
return false;
}
const RenderRemaining = (props) => {
return (
<div>
<h2>Tasks: </h2>
<hr />
{props.list.map((detail) => {
return <RenderRemainingData key={detail.id} {...detail} />
})}
</div>
);
}
const mapStateToProps = (state) => {
return {
list: state.todoReducer
};
}
connect()(RenderRemainingData);
export default connect(mapStateToProps)(RenderRemaining);
Now when an event of onClick occurs it gives an error as dispatch is not a function in console.
I don't know why is this happening.
This is because when you are rendering the RenderRemainingData component inside RenderRemaining you are not passing the dispatch, but in case of separate file, component will receive the dispatch from connect.
Possible Solutions:
1- Pass the dispatch in props to RenderRemainingData component:
return <RenderRemainingData key={detail.id} {...detail} dispatch={props.dispatch} />
And remove this line:
connect()(RenderRemainingData);
2- Another possible solution is:
Use a wrapper component and instead of rendering RenderRemainingData render that Wrapper component. Like this:
const WrapperRenderRemainingData = connect()(RenderRemainingData);
return <WrapperRenderRemainingData key={detail.id} {...detail} />
Calling connect()(SomeRandomComponent) means you are calling a function which will return you a value, a new Component that you can use.
So in the case of two separate files, first you create a new Component with connect()(RenderRemainingData), then you export the return value.
These two are equivalent.
export default connect()(SomeRandomComponent)
and
const newComponent = connect()(SomeRandomComponent)
export default newComponent
Now, if we look at bottom of your file containing the combined code.
connect()(RenderRemainingData);
export default connect(mapStateToProps)(RenderRemaining);
First expression, creates a newComponent by wrapping connect around RenderRemainingData. But since you didn't assign the return value to a new identifier or RenderRemainingData( you can't because latter is a const , by the way). Also, when you pass a function as a parameter, it is passed by value, so altering the parameter function inside the calling function will not affect its usage outside the calling function.
Easiest Solution for you will be the one mentioned below.
const RenderRemainingData = connect()(props => {
///Add the implementation here
})
There you go, you have a connected component in the same file, with dispatch available.
I have a list of items in table. I want to be able to click on a details link on any item and go to a new page where I see the details of the item. Below is my code.
import React from 'react';
import { connect } from 'react-redux';
import toastr from 'toastr';
import { Link } from 'react-router';
import { bindActionCreators } from 'redux';
import * as documentActions from '../../actions/documentActions';
class DocumentDetailsPage extends React.Component {
constructor(props) {
super(props);
this.deleteDoc = this.deleteDoc.bind(this);
}
deleteDoc(id) {
this.props.actions.deleteDocument(id)
.then(res => toastr.success('Document deleted successfully!'));
}
render() {
const { document } = this.props;
return (
<div className="col s12">
<div className="card qBox">
<div className="card-content white-text">
<span className="card-title">{document.title}</span>
<p
dangerouslySetInnerHTML={{ __html: document.content }}
className="document-content"></p>
<br />
<p>Access Type:
<span>{(document.access).toUpperCase()}</span></p><br />
<div>
Published Date :
<p>{(document.createdAt) ?
document.createdAt.split('T')[0] : ''}</p>
<p id="owner">Author:
{document.owner.firstName} {document.owner.lastName}</p>
</div>
</div>
<div className="card-action">
<Link to="/">back</Link>
{this.props.auth.user.userId === document.ownerId &&
<div className="right">
<Link to={`/document/${document.id}`}>Edit</Link>
<Link to="/" onClick={this.deleteDoc()}> Delete </Link>
</div>
}
</div>
</div>
</div>
);
}
}
DocumentDetailsPage.propTypes = {
document: React.PropTypes.object.isRequired,
actions: React.PropTypes.object.isRequired,
auth: React.PropTypes.object.isRequired,
};
function getDocumentById(documents, id) {
const document = documents.filter(item => item.id === id);
if (document) return document[0];
return null;
}
function mapStateToProps(state, ownProps) {
const documentId = ownProps.params.id; // from the path `/document/:id`
let document;
if (documentId && state.documents.length > 0) {
document = getDocumentById(state.documents, parseInt(documentId, 10));
}
return {
document,
auth: state.auth,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(documentActions, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(DocumentDetailsPage);
When I click on the details link, it should take me to this page with route document/:id where I can read the id from the route and fetch the corresponding document from state and render in the component. But when i go to this page, it tells me that document is undefined. Any reason for this? or is there a better way to get the param id from the route than using mapStateToProps?
You have two problems here:
1) Your deleteDoc class method expects an id argument which is passed to your action creator. Because you already have the full document as a prop, get it from the object instead of receiving it:
deleteDoc() {
const { actions, document } = this.props
actions.deleteDocument(document.id)
.then(res => toastr.success('Document deleted successfully!'));
}
2) You're CALLING the deleteDoc function in your onClick handler!
// Wrong
<Link to="/" onClick={this.deleteDoc()}> Delete </Link>
Remove the parens and simply set a reference to your function as the onClick prop, not the result from an actual call. :)
// Right
<Link to="/" onClick={this.deleteDoc}> Delete </Link>