Redux not working in a particularly Route after Refresh only - javascript

I am using React-Redux , React-Router , Saga , React-Thunk
All my application works well , one component which also dispatch action to be rendered gives me an error whenever I refresh the page OR type the url myself its a component that renders only when a photo clicked it display the particular photo after dispatching and filtering the state by the id which I send in the url , It works fine when I click the photo but after one refresh the state returns an empty array to be rendered here is my code
import React,{useEffect} from 'react';
import styled from 'styled-components';
import {connect} from 'react-redux';
import {fetchImages} from '../redux/actions/Actions';
const Photos = props => {
useEffect(()=>{props.fetchRequestedPhoto()},[props.requestedPhoto.length])
console.log(props.requestedPhoto)
}
const mapStateToProps = state =>( {
requestedPhoto:state.images
})
const mapDispatchToProps = () =>( {
fetchRequestedPhoto:()=>{return fetchImages()}
})
export default connect(mapStateToProps,mapDispatchToProps)(Photos);
navigation.js
import React from 'react'
import {BrowserRouter as Router , Route } from 'react-router-dom'
import HomePage from '../components/home'
import Categories from '../components/categories'
import Photos from '../components/photos.js'
import Category from '../components/category'
const Routes = () => {
return(
<Router>
<Route exact path='/' component={HomePage}></Route>
<Route exact path='/categories' component={Categories }></Route>
<Route exact path='/category/:category_id' component={Category}></Route>
<Route exact path='/photo/:photo_id' component={Photos}></Route>
</Router>
)
}
export default Routes;
Action.js
import axios from 'axios';
import { GetCategories,GetImages } from './actionTypes'
export function fetchCategories() {
var url = 'http://localhost:4000/all_categories';
return (dispatch) => {
return axios.get(url).then((res) => {
dispatch({
type: GetCategories,
payload: res.data
})
})
}
}
export function fetchImages(){
var url ='http://localhost:4000/all_images';
return(dispatch) => {
return axios.get(url).then((res)=>{
dispatch({
type:GetImages,
payload:res.data
})
})
}
}
saga.js
import {takeEvery,delay,put} from 'redux-saga/effects'
import axios from 'axios';
function* getImagesAsync(){
delay(4000)
const response=axios.get('http://localhost:4000/all_images');
const payloadImages = response;
yield put({type: 'GetImagesSaga', payload:payloadImages});
}
export function* watchFetchImages(){
yield takeEvery("GetImages",getImagesAsync);
}
Reducer.js
import { GetCategories , GetImages } from "../redux/actions/actionTypes"
const initialState = {
categories: [],
images:[]
};
const rootReducer = (state = initialState, action) => {
switch (action.type) {
case GetCategories:
return {...state, categories:action.payload}
case GetImages:
return{...state,images:action.payload}
default:
return state;
}
};
export default rootReducer;
Any one could help me with this??

Related

React Redux prop is undefined

Beginner question.
I want to pass a user object to a component from store as a prop, but the component doesn't get it (undefined). I get the user object from a third party service authentication service (google firebase)
The middleware actually logs out in the console that the action of type SET_CURRENT_USER takes place, and next state indeed will have a user.currentUser set to the object returned from the login service (NOT UNDEFINED).
However, the component doesn't re-render and doesn't seem to receive the object as prop
The component, in which the prop is undefined
import React from 'react';
import { connect } from 'react-redux';
import { auth } from "../../firebase/firebase.utils";
export const Navbar = ({ currentUser }) => {
return (
/* A LOT OF JSX CODE. currentUser IS UNDEFINED */
);
};
const mapStateToProps = state => ({
currentUser: state.user.currentUser
});
export default connect(mapStateToProps)(Navbar);
The App component, which has the above component as a child. Also, I'm trying to set the store to contain the user object in the componentDidMount()
import React from 'react';
import Homepage from "./pages/homepage";
import { Route, Switch } from 'react-router-dom';
import { connect } from 'react-redux';
import Login from "./pages/login";
import Register from "./pages/register";
import { Navbar } from "./components/navbar/navbar";
import { auth } from "./firebase/firebase.utils";
import { setCurrentUser } from "./redux/user/user.actions";
class App extends React.Component {
unsubscribeFromAuth = null;
componentDidMount() {
this.unsubscribeFromAuth = auth.onAuthStateChanged(async userAuth => {
if(userAuth) {
(async () => {
const rawResponse = await fetch(/* JUST AN ASYNC FUNCTION TO POST TO BACKEND*/);
})();
}
this.props.setCurrentUser(userAuth); /*HERE IM TRYING TO SET THE STORE*/
})
}
componentWillUnmount() {
this.unsubscribeFromAuth();
}
render() {
return (
<div>
<Navbar /> /* THE COMPONENT WHICH SHOULD GET THE USER OBJECT AS PROP */
<Switch>
<Route exact={true} path={'/register'} component={Register} />
<Route exact={true} path={'/login'} component={Login} />
<Route path={'/'} component={Homepage} />
</Switch>
</div>
);
}
}
const mapDispatchToProps = (dispatch) => ({
setCurrentUser: user => dispatch(setCurrentUser(user))
});
export default connect(null, mapDispatchToProps)(App);
The index component
import React from 'react';
import ReactDOM from 'react-dom';
import App from "./App";
import { BrowserRouter } from "react-router-dom";
import { Provider } from 'react-redux';
import store from "./redux/store";
ReactDOM.render(
<Provider store={store} > /* HERE IS STORE PROVIDED FROM IMPORT*/
<BrowserRouter>
<App/>
</BrowserRouter>
</Provider>,
document.getElementById('root')
);
Root reducer
import { combineReducers } from "redux";
export default combineReducers({
user: userReducer
});
User reducer
const INITIAL_STATE = {
currentUser: null
};
const userReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case 'SET_CURRENT_USER':
return {
...state,
currentUser: action.payload
};
default:
return state;
}
};
export default userReducer;
User action
export const setCurrentUser = user => ({
type: 'SET_CURRENT_USER',
payload: user
});
The store
import { createStore, applyMiddleware } from "redux";
import logger from 'redux-logger';
import rootReducer from './root-reducer';
const middlewares = [logger];
const store = createStore(rootReducer, applyMiddleware(...middlewares));
export default store;
You're doing both a named and default export for Navbar. The default export gets wrapped by the connect HOC that adds currentUser to its props. The named export does not.
You import it named like this: import { Navbar } from. Instead use the default export: import Navbar from.
Then I would suggest removing the named export to avoid future confusion.

How can I maintain my store's state while using react-router and redux?

I am building an app prototype that essentially simulates ecommerce. I have components that each have different items that can be added to a cart(below I just show an example of how one would basically work). These components are accessed via different routes using the react-router. There is a header component that displays the number of items currently in the cart. The header gets the number of items in the cart from the state in the redux store. However, if I navigate to a new route, the store goes back to the default state. I need the the store to keep its state when a new route is navigated to. For example, if I go to the ShoppingPage, add an item to the cart, and then go back to the Home page, I need the cart to still have an item in it.
actions.js
export const actionTypes = Object.freeze({
UPDATE_CART: Symbol('UPDATE_CART'),
});
export const updateCart = (payload) => {
return {
type: actionTypes.UPDATE_CART,
payload,
};
};
export default actionTypes;
reducer.js
import actions from './actions';
export const INITIAL_STATE = {
cart: [],
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case actions.UPDATE_CART: {
return {
...state,
cart: action.payload,
};
}
default: {
return state;
}
};
};
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { BrowserRouter } from 'react-router-dom';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import reducer, { INITIAL_STATE } from './reducer';
const store = createStore(reducer, INITIAL_STATE);
console.log(store.getState());
ReactDOM.render(
<Provider store ={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>
, document.getElementById('root'));
serviceWorker.unregister();
ShoppingPage.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { updateCart } from './actions';
class ShoppingPage extends Component {
addToCart = () => {
const cart = [...this.props.cart];
cart.push('new item');
this.props.modifyCart(cart);
render() {
return(
<div>
<button onClick={addToCart}>
Add To Cart
</button>
</div>
)
}
}
const mapDispatchToProps = dispatch => ({
modifyCart: payload => dispatch(updateCart(payload)),
});
const mapStateToProps = state => ({
cart: state.cart,
});
export default connect(
mapStateToProps,
mapDispatchToProps,
)(ShoppingPage);
Home.js
import React, { Component } from 'react';
import { ListGroup, ListGroupItem } from 'reactstrap';
class Home extends Component {
render() {
return(
<div>
<ListGroup>
<ListGroupItem><a href='/ShoppingPage'>ShoppingPage</a></ListGroupItem>
</div>
)
}
}
export default Home;
Header.js
import React, { Component } from 'react';
import { Navbar, NavbarBrand } from 'reactstrap';
import { connect } from 'react-redux';
class Header extends Component {
render() {
return(
<Navbar sticky='top' className='nav'>
<NavbarBrand href='/'>Online Shopping</NavbarBrand>
<span>{'Items in Cart: '}{this.props.cart.length}</span>
</Navbar>
)
}
}
const mapStateToProps = state => ({
cart: state.cart,
});
export default connect(
mapStateToProps
)(Header);
Routes.js
import React from 'react';
import { Switch, Route } from 'react-router-dom';
import Home from './Home';
import ShoppingPage from './ShoppingPage';
const Routes = () => (
<Switch>
<Route exact path='/' component={Home} />
<Route exact path='/ShoppingPage' component={ShoppingPage} />
</Switch>
);
export default Routes;
App.js
import React from 'react';
import Routes from './Routes';
import Header from './Header';
function App() {
return (
<div>
<Header />
<Routes />
</div>
);
}
export default App;
What's likely happening is that during navigation the web app "reloads" again (which is wiping the redux state). In order to navigate with react router you want to look at <Link>.
For example,
Home.js
<a href='/ShoppingPage'>ShoppingPage</a>
should be changed to:
<Link to="/ShoppingPage">ShoppingPage</Link>

Redux, React-Redux accessing variable from one page to next

I'm defining a variable in Page1 and would like to access it in Page2 and then when clicking back to Page1 retrieve the same variable
So far, the variable is set on Page1 but cannot be retrieved on Page2
index.js
import {createStore, applyMiddleware, combineReducers} from 'redux'
import thunk from 'redux-thunk'
import {composeWithDevTools} from 'redux-devtools-extension'
import {Provider} from 'react-redux'
import variableReducer from './reducers'
const store = createStore(
variableReducer,
composeWithDevTools(applyMiddleware(thunk))
)
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
serviceWorker.unregister();
actions/index.js
export const SET_MY_VARIABLE = 'SET_MY_VARIABLE'
export const setMyVariable = myVariable => ({
type: SET_MY_VARIABLE,
payload: {myVariable}
})
reducers/index.js
import {SET_MY_VARIABLE} from '../actions'
const initialState = {
myVariable: ''
}
const variableReducer = (state=initialState, action) => {
switch (action.type) {
case SET_MY_VARIABLE:
return {
...state,
myVariable: action.payload.myVariable
}
default:
return state
}
}
export default variableReducer
components/Page1.js
import React, {useEffect} from 'react'
import {connect, useDispatch} from 'react-redux'
import {setMyVariable} from '../actions'
const Page1 = (props) => {
const dispatch = useDispatch()
useEffect(() => {
dispatch(setMyVariable(5000))
}, [])
return (
<div>
Setting variable<br />
Go to page 2
</div>
)
}
const mapState = state => {
return {
myVariable: state.myVariable
}
}
export default connect(mapState)(Page1)
components/Page2.js
import React from 'react'
import {connect} from 'react-redux'
const Page2 = (props) => {
const {myVariable} = props
console.log('props: ', props)
return (
<div>
Variable: {myVariable}
</div>
)
}
const mapState = state => {
console.log('map2 ', state.myVariable)
return {
myVariable: state.myVariable
}
}
export default connect(mapState)(Page2)
I should be able to set variables to the store in one component and access them throughout the entire App. Instead, I'm not able to retrieve them
Instead of using action.payload.myVariable use action.payload in your reducer/index.js
I've discovered the answer to my problem. I needed to change the <a href tag to a <Link> from react-router-dom in the Page1 component. The <a href was causing a complete reload of all JS and losing state. Here's the corrected component:
components/Page1.js
import React, {useEffect} from 'react'
import {connect, useDispatch} from 'react-redux'
import {setMyVariable} from '../actions'
import {Link} from 'react-router-dom'
const Page1 = (props) => {
const dispatch = useDispatch()
useEffect(() => {
dispatch(setMyVariable(5000))
}, [])
return (
<div>
Variable: {myVariable}<br />
<Link to="/page2">Go to page 2</Link>
</div>
)
}
const mapState = state => {
return {
myVariable: state.myVariable
}
}
export default connect(mapState)(Page1)

reducer switch statements not working

I've just started out with Redux and trying to implement a simple MERN App (for practice).
Everything in my code is working fine, but my reducer function is showing unexpected behaviour. When an action (which gets fetches data from express api) is called my reducer correctly goes to the particular switch case data logs successfully but then three times the default case is passed and data on my component which I log is showing null. Please Help.
Here's my code:-
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { BrowserRouter as Router } from 'react-router-dom';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';
import { createStore, applyMiddleware, combineReducers } from 'redux';
import articlesReducer from './store/reducers/articlesReducer';
import registerServiceWorker from './registerServiceWorker';
const rootReducer = combineReducers({
articles: articlesReducer
});
const store = createStore(rootReducer, applyMiddleware(thunk));
const app = (
<Provider store={store}>
<Router>
<App />
</Router>
</Provider>
);
ReactDOM.render(app, document.getElementById('root'));
registerServiceWorker();
App.js
import React, {Component} from 'react';
import {Switch, Route} from 'react-router-dom';
import './App.css';
import Home from './components/Home/Home';
class App extends Component {
render() {
return (
<div>
<Switch>
<Route exact path="/" component={Home} />
</Switch>
</div>
);
}
}
export default App;
articles.js
export const getAllArticles = () => {
return dispatch => {
return (
fetch('http://localhost:5000/api/articles')
.then(res => res.json())
.then(data => {
dispatch({type: 'GET_ALL_ARTICLES', articles: data})
})
);
};
};
articlesReducer.js
const initialState = {
articles:null
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case 'GET_ALL_ARTICLES':
console.log('in reducer', action.type, action.articles[0]);
return {
...state,
articles: action.articles
};
default:
console.log('In default');
return state;
}
};
export default reducer;
myComponent
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { getAllArticles } from '../../store/actions/articles.js';
class MainPage extends Component {
componentWillMount() {
this.props.initArticles();
console.log(this.props.articles);
}
render() {
return (
<div className="container">
<br />
<h1>Here comes the articles!!</h1>
</div>
);
}
}
const mapStateToProps = state => {
return {
articles: state.articles
};
};
const mapDispatchToProps = dispatch => {
return {
initArticles: () => dispatch(getAllArticles())
};
};
export default connect(mapStateToProps, mapDispatchToProps)(MainPage);
The output in my console is somewhat like this:-
In default
In default
In default
{articles: null}
in reducer GET_ALL_ARTICLES {articles[0] Object}
I don't know what is the mistake. Thanks for help in advance.
I'm not sure whether this is actually the problem but you incorrectly access the articles. You have a root reducer with articles reducer:
const rootReducer = combineReducers({
articles: articlesReducer
});
which initial state is:
const initialState = {
articles:null
};
And in your mapDispatchToProps you "import" whole reducer state:
const mapStateToProps = state => {
return {
articles: state.articles
};
};
I think you wanted to access articles property
const mapStateToProps = state => {
return {
articles: state.articles.articles
};
};
Other than that everything seems to be fine. I would however as pointed in comment initialize articles as empty array [].
const initialState = {
articles: []
};

Testing react-router v4 routing with redux connected components

I am trying to verify that my routes are properly set up. As in when I have a certain URI, then the appropriate component gets loaded. Just to prevent me from mistyping a path accidentally.
Here's a sample connected page:
import React from 'react';
import {connect} from 'react-redux';
export const ConnectPage = (props) => {
return <div>{props.code}</div>;
};
export default connect(
(state) => ({}),
(dispatch) => ({})
)(ConnectPage);
Main Page:
import React from 'react';
import LoginPage from './LoginPage';
const MainPage = () => (
<LoginPage/>
);
export default MainPage;
Here's my routes file:
import React from 'react';
import {Route, Switch, Redirect} from 'react-router-dom';
import qs from 'query-string';
import MainPage from './MainPage';
import ConnectPage from './ConnectPage';
class Routes extends React.Component {
render() {
return (<Switch>
<Route path={'/connect/:provider'} render={(matches) => {
const search = qs.parse(matches.location.search);
if (search.code) {
return <ConnectPage code={search.code}/>;
}
return <Redirect to={'/'}/>;
}}/>
<Route exact path={'/'} component={MainPage}/>
</Switch>
);
}
}
export default Routes;
And here's my test that I'm having troubles with:
import React from 'react';
import {mount} from 'enzyme';
import {MemoryRouter} from 'react-router';
import configureStore from 'redux-mock-store';
import Chance from 'chance';
import Routes from './Routes';
import MainPage from './MainPage';
import ConnectPage from './ConnectPage';
import {createProvider as Provider} from 'react-redux';
const chance = new Chance();
const middlewares = [];
let mockStore;
describe('The Routes', () => {
beforeAll(()=>{
mockStore = configureStore(middlewares);
});
it('loads the Main Page on /', () => {
const component = mount(
<MemoryRouter initialEntries={['/']} initialIndex={0}>
<Routes store={mockStore}/>
</MemoryRouter>
);
expect(component.find(MainPage).length).toBe(1);
});
it('redirects to the main page if no code', () => {
const url = '/connect/someprovider';
const component = mount(
<MemoryRouter initialEntries={[url]} initialIndex={0}>
<Routes/>
</MemoryRouter>
);
expect(component.find(MainPage).length).toBe(1);
});
it('loads the Connect Page on /connect/provider', () => {
const code = chance.word({length: 32});
const url = `/connect/provider?code=${code}`;
const component = mount(
<Provider store={mockStore}>
<MemoryRouter initialEntries={[url]} initialIndex={0}>
<Routes/>
</MemoryRouter>
</Provider>);
try {
expect(component.find(ConnectPage).length).toBe(1);
} catch (e) {
console.log('Used url:', url);
throw (e);
}
});
});
I can't for the life of me figure out how to make the 3rd test pass. I would just want to make sure that the ConnectedPage gets put in place when it's appropriate.
In this state, I get:
Warning: Functions are not valid as a React child. This may happen if you return a Component instead of <Component /> from render. Or maybe you meant to call this function rather than return it.
in createProvider (created by WrapperComponent)
in WrapperComponent
I have found an answer in the meantime, so I'll post it here for prosperity. If you know anything better, please let me know!
Here's the revised test case:
it('loads the Connect Page on /connect/provider', () => {
const code = chance.word({length: 32});
const url = `/connect/provider?code=${code}`;
const component = mount(
<MemoryRouter initialEntries={[url]} initialIndex={0}>
<Routes/>
</MemoryRouter>,
{
context: {
store: mockStore()
},
childContextTypes: {
store: PropTypes.object.isRequired
}
});
try {
expect(component.find(ConnectPage).length).toBe(1);
} catch (e) {
console.log('Used url:', url);
throw (e);
}
});
Calling the mockStore seemed to be the main solution instead of just passing it as is in the context. I'm not sure why this works, but it does. I'll roll with it for now, but would love to hear some explanations so that I can learn.

Categories

Resources