Maybe I am missing something obvious here but I have a Sidebar component that is used on 2 pages of my app; Blog and Post. In my Sidebar component I have a dispatch to fetch some data for "pinned articles" and in my App I have some routes. When I click on the link in the sidebar, the link works but if I click a different link in the sidebar, nothing happens. In my redux store I have enabled logging middleware and I see the first click on the link is SUCCESS but clicking a second link in the sidebar shows nothing but CLEAR. Any suggestions?
App.jsx
[... unrelated code omitted ...]
render() {
const { alert } = this.props;
return (
<div>
{alert.message &&
<div className={`alert ${alert.type}`}>{alert.message}</div>
}
<Router history={history}>
<div>
<PrivateRoute exact path="/" component={HomePage} />
<PrivateRoute exact path="/resource-centre" component={Blog} />
<PrivateRoute path="/resource-centre/:id" component={Post} />
<Route path="/login" component={LoginPage} />
<Route path="/register" component={RegisterPage} />
</div>
</Router>
</div>
);
}
Sidebar.jsx
import React from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { articleActions } from '../../actions';
import _ from 'lodash';
class Sidebar extends React.Component {
componentDidMount() {
this.props.dispatch(articleActions.fetchPinnedArticles());
}
render() {
const { pinnedArticles } = this.props;
return (
<div>
<h2 className="widget-title">Pinned</h2>
<ul>
{pinnedArticles.items && pinnedArticles.items.map(article =>
<li className="tag-cloud-link" key={article.id}>
<Link to={{ pathname: `/blog/${article.id}` }}>{article.title}</Link>
</li>
)}
</ul>
</div>
)
}
}
function mapStateToProps(state) {
const { pinnedArticles } = state;
return {
pinnedArticles
}
}
const connectedSidebar = connect(mapStateToProps)(Sidebar);
export { connectedSidebar as Sidebar };
articleAction.js
function fetchPinnedArticles() {
return dispatch => {
dispatch(request());
articleService.fetchPinnedArticles()
.then(
pinnedArticles => dispatch(success(pinnedArticles)),
error => dispatch(failure(error))
);
};
function request() {
return { type: articleConstants.FETCH_PINNED_REQUEST }
}
function success(pinnedArticles) {
return { type: articleConstants.FETCH_PINNED_SUCCESS, pinnedArticles }
}
function failure(error) {
return { type: articleConstants.FETCH_PINNED_FAILURE, error }
}
}
articleService.js
function fetchPinnedArticles() {
const requestOptions = {
method: 'GET',
headers: authHeader()
};
return fetch(`${config.apiUrl}/articles/pinned`, requestOptions).then(handleResponse, handleError);
}
articleReducer.js
export function pinnedArticles(state = {}, action) {
switch (action.type) {
case articleConstants.FETCH_PINNED_REQUEST:
return {
loading: true
}
case articleConstants.FETCH_PINNED_SUCCESS:
return {
items: action.pinnedArticles
};
case articleConstants.FETCH_PINNED_FAILURE:
return {
error: action.error
};
default:
return state;
}
}
Related
So I'm trying to use useParams reactrouterhook to get id of superhero, to further make request to my server with such id. When in RQsuperherous.js I click on Link and get transported on adress http://localhost:3000/SuperHeroes/:2 as intended, yet in console.log my id equils = ':1', ':2', ':3', and so on, and I cant make request to server.
I'm using react router 6.x
I'm not sure about adress '/SuperHeroes/:id' in App.js. Maybe it should be nested.
my App.js
import { Route, Routes } from 'react-router-dom';
import { BasicLayout } from './pages/BasicLayout';
import { SuperHerous } from './pages/SuperHerous.page';
import { RQSuperHeroes } from './pages/RQSuperHeroes.page';
import { Home } from './pages/Home.page';
import { SuperHero } from './pages/SuperHero.page';
function App() {
return (
<Routes>
<Route path="/" element={<BasicLayout />}>
<Route index element={<Home/>} />
<Route path="/SuperHerous" element={< SuperHerous />} />
<Route path="/SuperHeroes/:id" element={< SuperHero />} />
<Route path="/RQSuperHeros" element={< RQSuperHeroes />} />
</Route>
</Routes>
);
}
export default App;
my superHero page
import { useParams } from 'react-router-dom';
import { useSuperHero } from '../hooks/useSuperHero';
export const SuperHero = () => {
const id = useParams();
const { isLoading, data, isError, error } = useSuperHero({id});
console.log(id)
if (isLoading) {
return <h2>Loading...</h2>
}
if (isError) {
return <h2>{error.message}</h2>
}
return (
<div>
<h1>{data.name}</h1>
<h1>{data.alterago}</h1>
xfcghjk
</div>
)
}
my RQsuperherous.js
import { useSuperHero } from "../hooks/useSuperHero"
import { Link } from "react-router-dom";
export const RQSuperHeroes = () => {
const onSuccess = (data) => {
console.log('Perform side effect after data fetching', data)
}
const onError = (error) => {
console.log('Perform side effect after encountering error', error.message)
}
const { data, isLoading, isError, error, isFetching, refetch } = useSuperHero({onSuccess, onError});
if (isLoading) {
return <h2>Loading...</h2>
}
if (isError) {
return <h2>{error.message}</h2>
}
console.log(isLoading, isFetching)
return (
<>
<div>RQ SuperHeroes Page</div>
<button onClick={refetch}>Fetch heroes</button>
{
data && data.length && (
data.map(el => {
return (
<Link to={`SuperHeroes/:${el.id}`} key={el.id}>
<div className='super' >
<h2>{el.name}</h2>
<p>{el.alterego}</p>
</div>
</Link>
)
})
)
}
</>
)
}
Edited:
if in RQsuperherous component in Link path change to SuperHeroes/${el.id}
I have an error in console : No routes matched location
"/RQSuperHeros/SuperHeroes/1"
I think the problem is here:
http://localhost:3000/SuperHeroes/:2
The : in the route definition is not meant to be in the real route
Use this address and check the id variable
http://localhost:3000/SuperHeroes/2
I am trying to dynamically create routes using react-router-dom. The route will depend on the id of a state I have in one of my components. How can I achieve this?
App.js:
function App() {
return (
<div className="App">
<Router>
<Route path="/" component={Categories}></Route>
<Route path="/:cat" component={Recipes}></Route>
</Router>
</div>
);
}
Categories.js which I want to get the id (:cat) from - the state categories has an id value:
class Categories extends React.Component {
state = {
categories: []
}
componentDidMount() {
fetch('http://localhost:8000/api/categories/')
.then(response => response.json())
.then(data => {
this.setState({categories: data});
});
}
render() {
return (
);
}
}
I have seen others use useParams but I can't do that since Categories is a class.
Thank you for the help.
Use
this.props.match.params
i.e:
import { BrowserRouter, Route, Router, Switch } from "react-router-dom";
import React from "react";
function App() {
return (
<div className="App">
<p>R</p>
<BrowserRouter>
<Switch>
<Route path="/categories/:id" component={Recipes}></Route>
<Route path="/" component={Categories}></Route>
</Switch>
</BrowserRouter>
</div>
);
}
class Categories extends React.Component {
state = {
categories: []
};
componentDidMount() {
fetch('http://localhost:8000/api/categories/')
.then(response => response.json())
.then(data => {
this.setState({categories: data});
});
}
render() {
return (
<div>
{this.state.categories.map((c) => (
<div>CAT: {c.name}</div>
))}
</div>
);
}
}
class Recipes extends React.Component {
state = {};
componentDidMount() {
console.log('Params:',this.props.match.params)
}
render() {
return <div></div>;
}
}
export default App;
having some trouble rendering props in a component for a project using Reactjs. The information is showing in props in the react dev tools, yet I am unable to render them on the browser. When console logging, there is no value showing...
I'm wondering if I need to dig deeper into the api in order to grab what I need?
CocktailRecipe.js
````import React, { Component } from 'react'
// import Spinner from '../layout/Spinner'
class CocktailRecipe extends Component {
componentDidMount(){
this.props.getCocktail(this.props.match.params.idDrink);
// console.log(this.props.match.params.idDrink)
}
render() {
const {strDrink} = this.props.cocktailrecipe;
console.log(strDrink);
// console.log(this.props.cocktailrecipe.strDrink);
// const {loading} = this.props.loading;
// if (loading) {
// <Spinner />
// }else{
return (
<div>
<h3>{strDrink}</h3>
<h3>This is the title</h3>
</div>
)
// }
}
}
export default CocktailRecipe````
app.js
````import { Component, Fragment } from 'react';
import { BrowserRouter as Router, Switch, Route} from 'react-router-dom';
import './App.css';
import Navbar from './layout/Navbar';
import CocktailList from './cocktail/CocktailList';
import axios from 'axios';
import Search from './cocktail/Search';
import Alert from './layout/Alert';
import About from './pages/About';
import CocktailRecipe from './cocktail/CocktailRecipe';
class App extends Component {
state={
cocktails: [],
cocktailrecipe:{},
loading: false,
msg:'',
type:''
}
async componentDidMount() {
try {
this.setState({loading: true})
const res = await axios.get('https://www.thecocktaildb.com/api/json/v1/1/search.php?s=');
// console.log(res.data);
this.setState({cocktails: res.data.drinks, loading: false})
} catch(error) {
console.log(error)
}
}
handleSearchCocktails= async (text) => {
try{
const res = await axios.get(`https://www.thecocktaildb.com/api/json/v1/1/search.php?s=${text}`);
// console.log(res.data);
this.setState({cocktails: res.data.drinks, loading: false})
} catch(error) {
console.log(error)
}
}
// Get cocktail recipe
getCocktail = async (idDrink) => {
try {
const res = await axios.get(`https://www.thecocktaildb.com/api/json/v1/1/lookup.php?i=${idDrink}`);
// console.log(res.data.drinks);
this.setState({cocktailrecipe: res.data.drinks.id, loading: false})
} catch(error) {
console.log(error)
}
}
handleClearCocktails= () => {
this.setState({cocktails:[], loading: false})
}
handleSetAlert=(msgfromSearch, typefromSearch)=>{
this.setState({ msg:msgfromSearch, type:typefromSearch })
setTimeout(()=>this.setState({msg:'', type:''}), 5000)
}
render() {
const {cocktails, loading, cocktailrecipe} = this.state;
return (
<Router>
<div className="App">
<Navbar title="COCKTAIL LIBRARY" />
<div className="container">
<Alert msg={this.state.msg} type={this.state.type} />
<Switch>
<Route exact path='/' render={props=>(
<Fragment>
<Search searchCocktails={this.handleSearchCocktails} clearCocktails={this.handleClearCocktails} showClear={this.state.cocktails.length>0?true:false} setAlert={this.handleSetAlert} />
<CocktailList loading={loading} cocktails={cocktails} />
</Fragment>
)} />
<Route exact path='/about' component={About} />
<Route exact path='/cocktailRecipe/:idDrink' render={props => (
<CocktailRecipe {...props} getCocktail={this.getCocktail} cocktailrecipe={cocktailrecipe} loading={loading}/>
)} />
</Switch>
</div>
</div>
</Router>
);
}
}
export default App;````
In your screenshot, props cocktailrecipe is an array of object.
Use array desctructuring instead of object on CocktailRecipe.js
- const {strDrink} = this.props.cocktailrecipe;
+ const [strDrink] = this.props.cocktailrecipe;
So turns out that my wifi connection is part of the problem. And grabbing the wrong object.
In CocktailRecipe.js I added in:
line 22:
const { drinks } = this.props.cocktailrecipe;
and then put into the render():
{drinks && drinks[0].strDrink }
I'm told that this may not be the most elegant or efficient solution, so if anybody has a better way, please let me know.
I'm using React and Redux. I'm new to React, but not to development. This is my first independent project after training. I want to really learn this and I don't understand what is happening right now.
I have a Delete page that I route to for List Items. I pass in the prop of list_item_id via Route. I'm getting that prop with no problem, and can access it properly through my method. This page is also using Modal, in case that's a factor.
Here's the Route section from App.js
return(
<div className="ui container">
<Router history={history}>
<div>
<Header />
<Switch>
<Route path="/" exact component={List} />
<Route path="/lists" exact component={List} />
<Route path="/list/:list_id" exact component={ListItemList} list_id={this.props.list_id} />
<Route path="/listitems/new/:list_id" exact component={ListItemCreate} />
<Route path="/listitems/edit/:list_item_id" exact component={ListItemEdit} />
<Route path="/listitems/delete/:list_item_id" exact component={ListItemDelete} />
<Route path="/listitems/:list_item_id" exact component={ListItemShow} />
</Switch>
</div>
</Router>
</div>
);
I'm getting the error onclick of the Delete button.
The prop that is undefined I'm getting from Redux via mapStatetoProps:
const mapStateToProps = (state, ownProps) => {
return {
listItem: state.listItems[ownProps.match.params.list_item_id]
};
};
When the page initially renders, it has the correct value. But when I click the Delete button, for some reason it's re-rendering, listItem is undefined, and it breaks.
Here's the component:
import React from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import Modal from '../Modal';
import history from '../../history';
import { fetchListItem, deleteListItem } from '../../actions';
class ListItemDelete extends React.Component {
componentDidMount(){
this.props.fetchListItem(this.props.match.params.list_item_id);
}
renderActions(){
const list_item_id =this.props.match.params.list_item_id;
console.log('listItem');
console.log(this.props.listItem);
const list_id = this.props.listItem.list_id; // <--- breaking here
//console.log('list_id is ' + list_id);
return(
<React.Fragment>
<button onClick={() => this.props.deleteListItem(list_id, list_item_id)} className="ui button negative">Delete</button>
<Link to="/" className="ui button ">Cancel</Link>
</React.Fragment>
);
};
renderContent(){
if(!this.props.listItem){
return 'Are you sure you want to delete this list item?';
}
return `Are you sure you want to delete list item: ${this.props.listItem.list_item}?`;
}
render(){
return (
<Modal
title="Delete List Item"
content={this.renderContent()}
actions={this.renderActions()}
onDismiss={() => history.push("/")}
/>
);
}
}
const mapStateToProps = (state, ownProps) => {
return {
listItem: state.listItems[ownProps.match.params.list_item_id]
};
};
export default connect(mapStateToProps, { fetchListItem, deleteListItem })(ListItemDelete);
In case it's relevant, here's the action creator:
export const deleteListItem = (list_id, list_item_id) => async dispatch => {
let data = new FormData();
data.append('request', 'delete');
data.append('list_item_id', list_item_id);
await listItems.post(`/listItemsMaintenance`, data);
dispatch({ type: DELETE_LIST_ITEM, payload: list_item_id });
history.push(`/list/${list_id}`);
};
And the reducer (It's also not deleting properly, which is why I'm looking into it):
import _ from 'lodash';
import {
FETCH_LIST_ITEM,
FETCH_LIST_ITEMS,
CREATE_LIST_ITEM,
EDIT_LIST_ITEM,
DELETE_LIST_ITEM,
MARK_COMPLETED_LIST_ITEM
} from '../actions/types';
export default (state = {}, action) => {
const emptyObj = {};
switch (action.type) {
case FETCH_LIST_ITEMS:
return { ...state, ..._.mapKeys(action.payload, 'list_item_id') };
case FETCH_LIST_ITEM:
return { ...state, [action.payload.list_item_id]: action.payload };
case CREATE_LIST_ITEM:
return { ...state, [action.payload.list_item_id]: action.payload };
case EDIT_LIST_ITEM:
return { ...state, [action.payload.list_item_id]: action.payload };
case MARK_COMPLETED_LIST_ITEM:
return { ...state, [action.payload.list_item_id]: action.payload };
case DELETE_LIST_ITEM:
console.log('Now Reducing DELETE');
console.log(action.payload);
return _.omit(state, action.payload);
default:
return state;
}
};
And WHY is it calling the renderActions method so many times? Twice when it correctly gets the value of listItem and twice when it doesn't?? Here's an image of the error and console.
For some reason, my routes only render half the time - seems like a race condition of some sort. It'll print out the "OK" but nothing from the routes, not even the 404. Pretty clear cut.
If I remove the loading bit it'll always render the switch block as intended.
Is there a better / different way to do this?
v4.2.0
render() {
const { hasInitialized } = this.props;
if (!hasInitialized) {
return (
<div>Loading...</div>
);
}
return (
<div style={{ height: '100%', width: '100%' }}>
<Helmet titleTemplate="%s - Resilient" defaultTitle="Resilient" />
<div>OK</div>
<Switch>
<Redirect from="/" to="/auth/check" exact={true} />
<Route path="/auth" component={AuthLayout} />
<AuthenticatedRoute path="/x" component={AdminLayout} />
<Route component={Miss404} />
</Switch>
</div>
);
}
https://github.com/ReactTraining/react-router/issues/5621
I read the react-router docs many times, and the part about Blocked Updates seems relevant. But, when I put a debugger line in <Layout />, location and history always have the right info, and still, none of the routes would render.
I still don't understand what the issue was, but here's the workaround I came up with. The code below wraps my <Layout /> component, which contains all the routes.
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
import LocalStorageManager from 'utils/LocalStorageManager';
import { selectCurrentUser, selectHasInitialized } from 'client/selectors/authSelectors';
import { setAccessToken, getProfile } from 'shared/api';
import { setHasInitialized, signIn } from 'modules/auth/actions.js';
import SinglePageCard from 'components/layout/SinglePageCard';
const mapStateToProps = (state) => {
return {
currentUser: selectCurrentUser(state),
hasInitialized: selectHasInitialized(state),
};
};
export default (WrappedComponent) => {
class Layout extends Component {
componentWillMount() {
const accessToken = LocalStorageManager.getAccessToken();
if (!accessToken) {
this.props.setHasInitialized();
return;
}
setAccessToken(accessToken);
getProfile().then((response) => {
console.log(response);
const { user } = response.data.data;
this.props.signIn(user);
}).catch((error) => {
console.log(error);
this.props.setHasInitialized();
});
}
render() {
const { currentUser, hasInitialized, ...rest } = this.props;
if (!hasInitialized) {
return (
<SinglePageCard>
<div>Initializing...</div>
</SinglePageCard>
);
}
return (
<WrappedComponent {...rest} />
);
}
}
return withRouter(connect(mapStateToProps, { setHasInitialized, signIn })(Layout));
};