callback render loses router context for child react-router & redux - javascript

I've been developing an idea but am getting stuck on something unusual (my brain hurts on react-router).
I am trying to dynamically render a list of items using .map from a returned object (of multiple similar objects) and appending them to the render(){return(<div />)}.
I just dont know another way than call a function then .map the result for this callback.
I think that the way I'm doing this means the rendered items lose context. The react-router <Link /> will function as expected in the normal flow (placed inside the render(){return(<div />)} ) but not when the item is created from outside of the render. I have posted the error below the code.
I have read Many different ways of getting around this using context and location/history and withRouter. Frankly I'm lost.
I would appreciate if someone could look at my example below and guide me in the right direction.
A few notes:
- main focus appears to be in mystuff
- i have many unnecessary imports i know
- stripped down for clarity, i would get lost otherwise
index
import _ from 'lodash';
import React from 'react';
import { render } from 'react-dom';
import { Router, Route, IndexRoute, browserHistory } from 'react-router';
import { Provider } from 'react-redux';
import { store, history } from './store';
import Main from './Main';
import { routyr } from './Menu';
// remaining paths in Menu.js (routyr) for menu visibility
const router = (
<Provider store={store}>
<Router history={history}>
<Route path="/" component={Main}>
{routyr}
</Route>
</Router>
</Provider>
)
render (router, document.getElementById('app'));
Main
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import * as actionCreators from './actionCreators';
import App from './app';
function mapStateToProps(state){
return{
info: state.info,
myProfile: state.myProfile
}
}
function mapDispatchToProps(dispatch){
return { actions: bindActionCreators(actionCreators, dispatch) }
}
const Main = connect(mapStateToProps, mapDispatchToProps)(App);
export default Main;
routyr
import React from 'react';
import { Link } from 'react-router';
import { Router, Route, IndexRoute, browserHistory } from 'react-router';
import { Provider } from 'react-redux';
import { store, history } from './store';
//pages
import App from './app';
import Landing from './Landing';
import Me from './mystuff';
import ViewStuff from './viewStuff';
//Routes for index.js
export const routyr = (
<span>
<IndexRoute component={Landing} />
<Route path="/myStuff" component={Me} />
<Route path="/viewStuff" component={ViewStuff} />
</span>
)
//Menu types
//loaded by app.js
export const menuLoggedIn = (
<div className="MainMenu">
<Link to='/' className="buttonA green">Home</Link>
<Link to='myStuff' className="buttonA green">My Stuff</Link>
</div>
);
export const menuLoggedOut = (
<div className="MainMenu">
<Link to='/login' className="buttonA green">Login</Link>
</div>
);
app
import React from 'react';
import _ from 'lodash';
import { Link } from 'react-router';
import auth from './auth';
import Landing from './Landing';
import Header from './Header';
import { menuLoggedIn, menuLoggedOut } from './Menu';
export default class App extends React.Component {
constructor(){
super();
this.state={
auth: auth.loggedIn(),
menu: null
};
}
componentWillMount(){
if (this.state.auth==true) {
this.setState({
menu: menuLoggedIn
})
}else{
this.setState({
menu: menuLoggedOut
});
}
}
render(){
return (
<div>
<Header />
{this.state.menu}<br />
<div id="view">
{React.cloneElement(this.props.children, this.props)}
</div>
</div>
);
}
};
mystuff
import React, { PropTypes } from 'react';
import { render } from 'react-dom';
import { Link } from 'react-router';
import { withRouter } from 'react-router';
import { Provider } from 'react-redux';
import * from './whacks';
export default class Me extends React.Component{
constructor(){
super();
}
componentDidMount() {
function listThem(oio){
oio.map(function(ducks){
render(
<div className="ListItem">
<Link to="/viewStuff"> _BROKEN_ View Stuff</Link>
<div className="listLabel">{ducks.type}</div>
<h3>{ducks.description.title}</h3>
{ducks.description.long}
</div>, document.getElementById('fishes').appendChild(document.createElement('div'))
);
});
}
var some = new Whacks();
some.thing(more, (close, open) => {
if(close){
console.log(close));
} else {
doIt(open);
}
});
}
render(){
return(
<div>
<Link to="viewStuff"> _WORKING_ View Stuff</Link>
<div id="fishes">
</div>
</div>
)
}
}
store
import { createStore, compose } from 'redux';
import { syncHistoryWithStore } from 'react-router-redux';
import { browserHistory } from 'react-router';
import { combineReducers } from 'redux';
import { routerReducer } from 'react-router-redux';
/*-------ROOT REDUCER---------*/
/*-------DEFAULT STATES---------*/
/*-------CREATE STORE---------*/
/*-------INTEGRATE HISTORY---------*/
import me from './reducers/obj';
import myProfile from './reducers/myProfile';
const rootReducer = combineReducers(
{
routing: routerReducer,
me,
myProfile
}
);
//TEMP remove harcoded var
const uuidSet = "fa78d964";
export const defaultState = {
uuid: uuidSet,
};
export const store = createStore(rootReducer, defaultState, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());
export const history = syncHistoryWithStore(browserHistory, store);
actionCreators
export function me (obj){
return {
type: "ADD_OBJECTLIST",
obj
}
}
export function myProfile (dump){
return {
type: "MY_DATA",
dump
}
}
from package.json
"react-redux": "^5.0.2",
"react-router": "^3.0.2",
"react-router-redux": "^4.0.7",
"redux": "^3.6.0",
error
Uncaught Error: s rendered outside of a router context cannot navigate.
#UG,
I have tried the following in mystuff:
constructor(){
super();
this.state={
oio: {}
};
}
and
some.thing(more, (close, open) => {
if(close){
console.log(close));
} else {
this.setState({
oio: open
});
}
});
and
render(){
let flat = this.state.oio;
flat.map(function(ducks){
return (
<div className="ListItem">
<Link to="/viewStuff">View Stuff</Link>
<div className="listLabel">{ducks.type}</div>
<h3>{ducks.description.title}</h3>
{ducks.description.long}
</div>
)
})
}
and receive
Uncaught TypeError: flat.map is not a function
at Me.render

I am not sure if I get your issue completely. But I think you want to use Link inside render() method of myStuff
You can change that to following :
render(){
return(
<div>
<Link to="viewStuff"> _WORKING_ View Stuff</Link>
<div id="fishes">
{
oio.map(function(ducks){
return (
<div className="ListItem">
<Link to="/viewStuff"> _BROKEN_ View Stuff</Link>
<div className="listLabel">{ducks.type}</div>
<h3>{ducks.description.title}</h3>
{ducks.description.long}
</div>
);
}
</div>
</div>
)
}
As per the comment from James,
You should use react state to maintain oio object.
constructor() {
super();
//init
this.setState({oio : {}});
}
and update the state in async call, when state updates, component can be rerendered.

Huge thanks to UG_ for smacking me in the ear with state.
I have pulled in a component and created each components props from the callback objects.
My Working solution is as follows in mystuff:
constructor(props){
super(props);
this.state={
oio: []
}
}
componentDidMount() {
let listThem = (stuff) => {
let ioi = [];
stuff.forEach(function(dood, index, array) {
let lame = <MyItem plop={dood} key={index} />;
ioi.push(lame);
});
return (
this.setState({
oio: ioi
})
);
}
var some = new Whacks();
some.thing(more, (close, open) => {
if(close){
console.log(close));
} else {
listThem(open);
}
});
}
render(){
return(
<div>
{this.state.oio}
</div>
)
}
Which renders a new copy of the MyItem component with props from each returned object. So now my returned items contain context!

Related

React context router is undefined

I'm trying to use the context router in a component and I can't understand why it is undefined.
After searching for similar questions I've tried different solutions, such as:
wrapped the withRouter hoc into my component
added the contextTypes with router
made sure that the App component is wrapped with BrowserRouter
tried to access the router with both this.props.router and this.context.router
This is my code:
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { BrowserRouter as Router } from 'react-router-dom';
import { AppRedux } from 'components';
import store from './store';
ReactDOM.render(
<Provider store={store}>
<Router>
<AppRedux />
</Router>
</Provider>,
document.getElementById('app')
);
AppRedux.js
import { connect } from 'react-redux';
import AppContainer from './AppContainer';
const mapDispatchToProps = (dispatch) => ({
// ...
})
const mapStateToProps = (state) => ({
// ...
})
export default connect(mapStateToProps, mapDispatchToProps)(AppContainer);
AppContainer.js - here is where I'm trying to access the context router
import React from 'react';
import { withRouter } from 'react-router'
class AppContainer extends React.Component {
constructor() {
super();
this.state = {};
}
render() {
console.log(this.context) // { router: undefined }
console.log(this.props.router) // { router: undefined }
return (
// ...
);
}
}
AppContainer.contextTypes = {
router: PropTypes.object.isRequired
}
export default withRouter(AppContainer);

I get error when I setup react-alert with my react app Uncaught Error: Objects are not valid as a React child

I got this error :
react-dom.development.js:14748 Uncaught Error: Objects are not valid as a React child (found: object with keys {$$typeof, render}). If you meant to render a collection of children, use an array instead.
in Unknown (created by App)
in Provider (created by App)
in div (created by App)
in App
in Provider
at throwOnInvalidObjectType
this is my Alerts component
import React, { Component , Fragment} from 'react'
import { withAlert } from "react-alert";
export class Alerts extends Component {
componentDidMount(){
this.props.alert.show("it worked");
}
render() {
return (
<Fragment>
</Fragment>
);
}
}
export default withAlert(Alerts);
and this is my App.js
import React ,{ Component, Fragment } from "react";
import { render } from "react-dom";
import {Provider as AlertProvider} from 'react-alert';
import AlertTemplate from 'react-alert-template-basic';
import Header from './components/Header';
import Leads from './components/leads';
import AddLead from './components/AddLead';
import Alerts from './components/Alerts'
import { Provider } from 'react-redux';
import store from './store';
import { connect } from 'react-redux';
const alertOptions = {
timeout:3000,
position:"top center"
}
class App extends Component{
render(){
return(
<div>
<AlertProvider template={AlertTemplate} {...alertOptions}>
<Fragment>
<Header/>
<Alerts/>
<Leads/>
<AddLead/>
</Fragment>
</AlertProvider>
</div>
)
}
}
export default App;
render(<Provider store={store}><App/></Provider>, document.getElementById("app"));
my errors reducer :
import { GET_ERRORS } from '../actions/Types';
const initialState = {
msg: {},
status:null
}
export default function (state = initialState, action) {
switch(action.type){
case GET_ERRORS:
return {
msg:action.payload.msg,
status:action.payload.status
};
default:
return state;
}
}
Change the last line of Alert component from export default withAlert(Alerts); to
export default withAlert()(Alerts); according to react-alert docs.

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>

ReactJs/Redux Invariant Violation: Could not find "store" in either the context or props of "Connect(LoginContainer)"

Not sure why I'm getting this error, it happened when I added connect from redux to my Login component, so I could connect my store.
FAIL src/components/auth/Login.test.js
● Test suite failed to run
Invariant Violation: Could not find "store" in either the context or props of "Connect(LoginContainer)". Either wrap the root component in a <Provider>, or explicitly pass "store" as a prop to "Connect(LoginContainer)".
Index.js
import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from "react-redux"
import { createCommonStore } from "./store";
import App from './App'
import css from './manage2.scss'
const store = createCommonStore();
const element = document.getElementById('manage2');
console.log("Index.js Default store", store.getState());
ReactDOM.render(
<Provider store={store}> // <-- store added here
<App />
</Provider>, element);
store.js
import React from "react"
import { applyMiddleware, combineReducers, compose, createStore} from "redux"
import thunk from "redux-thunk"
import { userReducer } from "./reducers/UserReducer"
import { authReducer } from "./reducers/AuthReducer"
export const createCommonStore = (trackStore=false) => {
const reducers = combineReducers({
user: userReducer,
user: authReducer
});
//noinspection JSUnresolvedVariable
const store = createStore(reducers,
compose(
applyMiddleware(thunk),
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
)
);
if (trackStore) {
store.subscribe((() => {
console.log(" store changed", store.getState());
}));
}
return store;
};
App.js
import React from 'react'
import { BrowserRouter as Router } from 'react-router-dom'
import Routes from './components/Routes'
const supportsHistory = "pushState" in window.history
export default class App extends React.Component {
render() {
return (
<Router forceRefresh={!supportsHistory}>
<Routes />
</Router>
);
}
}
Routes.js
import React from 'react'
import { Route, Switch } from 'react-router-dom'
import LoginContainer from './auth/Login'
import Dashboard from './Dashboard'
import NoMatch from './NoMatch'
const Routes = () => {
return (
<Switch>
<Route exact={ true } path="/" component={ LoginContainer }/>
<Route path="/dashboard" component={ Dashboard }/>
<Route component={ NoMatch } />
</Switch>
);
}
export default Routes
Finally Login.js (code removed for brevity
import React from 'react'
import { connect } from "react-redux"
import { bindActionCreators } from 'redux';
import { setCurrentUser } from '../../actions/authActions'
import * as api from '../../services/api'
const mapDispatchToProps = (dispatch) => {
console.log('mapDispatchToProps', dispatch);
return {
setUser: (user) => {
bindActionCreators(setCurrentUser(user), dispatch)
}
}
}
class LoginContainer extends React.Component {
constructor(props) {
super(props)
this.state = {};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(e) {
}
handleSubmit(e) {
}
render() {
return (
<div className="app-bg">
...
</div>
)
}
}
export default connect(null, mapDispatchToProps)(LoginContainer);
Login.test
import React from 'react'
import ReactTestUtils from 'react-dom/test-utils'
import { mount, shallow } from 'enzyme'
import toJson from 'enzyme-to-json'
import { missingLogin } from '../../consts/errors'
import Login from './Login'
import Notification from '../common/Notification'
const loginComponent = shallow(<Login />);
const fakeEvent = { preventDefault: () => '' };
describe('<Login /> component', () => {
it('should render', () => {
const tree = toJson(loginComponent);
expect(tree).toMatchSnapshot();
});
it('should render the Notification component if state.error is true', () => {
loginComponent.setState({ error: true });
expect(loginComponent.find(Notification).length).toBe(1);
});
});
describe('User Login', () => {
it('should fail if no credentials are provided', () => {
expect(loginComponent.find('.form-login').length).toBe(1);
loginComponent.find('.form-login').simulate('submit', fakeEvent);
expect(loginComponent.find(Notification).length).toBe(1);
const notificationComponent = shallow(<Notification message={ missingLogin }/>);
expect(notificationComponent.text()).toEqual('Please fill out both username and password.');
});
it('input fields should be filled correctly', () => {
const credentials = { username: 'leongaban', password: 'testpass' };
expect(loginComponent.find('#input-auth-username').length).toBe(1);
const usernameInput = loginComponent.find('#input-auth-username');
usernameInput.value = credentials.username;
expect(usernameInput.value).toBe('leongaban');
const passwordInput = loginComponent.find('#input-auth-password');
passwordInput.value = credentials.password;
expect(passwordInput.value).toBe('testpass');
});
});
What do you see wrong here?
Redux recommends exporting the unconnected component for unit tests. See their docs.
In login.js:
// Named export for tests
export class LoginContainer extends React.Component {
}
// Default export
export default connect(null, mapDispatchToProps)(LoginContainer);
And in your test:
// Import the named export, which has not gone through the connect function
import { LoginContainer as Login } from './Login';
You can then specify any props that would have come from the store directly on the component.
You need to pass store as either a prop or context in your test. mount method accepts context as another parameter.
and how do you get store here? You create store the same way you created in app.js
You could use React's contextType or pass propType. You would need to declare it either as a prop or contextType.
Provider.contextTypes = {
Store: React.PropTypes.object.isRequired
};
Provider.propTypes= {
Store: React.PropTypes.object.isRequired
};

Could not find store in either context or props

I am writing code with react and I just started using redux (because I require a container of sorts). However, I have been stuck at one place for a bit now.
I get this error -
Invariant Violation: Could not find "store" in either the context or
props of "Connect(HomePage)". Either wrap the root component in a
, or explicitly pass "store" as a prop to
"Connect(HomePage)".
I tried googling, and according to the troubleshooting section of react-redux, this can be checked using these three things:
1. Make sure you don’t have a duplicate instance of React on the page.
2. Make sure you didn’t forget to wrap your root component in < Provider>.
3. Make sure you’re running the latest versions of React and React Redux.
I have the following code that is the root (which is where the store is defined with the provider) -
import React from 'react';
import { Router, browserHistory } from 'react-router';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import reduxThunk from 'redux-thunk';
import routes from '../Routes';
import reducers from './reducers/reducers';
import actions from './actions/actions';
export default class AppRoutes extends React.Component {
render() {
const store = createStore(reducers, applyMiddleware(reduxThunk));
return (
<Provider store={store}>
<Router history={browserHistory} routes={routes} onUpdate={() => window.scrollTo(0, 0)}/>
</Provider>
);
}
}
And this error only happens on one of the two components I have -
// No error when connected only in this component
import React from 'react';
import { connect } from 'react-redux';
import * as actions from './actions/actions';
class Dashboard extends React.Component {
constructor(props) {
super(props);
}
render() {
return <h1>Hello, {this.props.isAuthenticated.toString()}</h1>;
}
}
function mapStateToProps(state) {
return {
content: state.auth.content,
isAuthenticated: state.auth.authenticated
};
}
export default connect(mapStateToProps, actions)(Dashboard);
// Error thrown when I try to connect this component
import React from 'react';
import LoginPage from './LoginPage';
import Dashboard from './Dashboard';
import Loading from './Loading';
import { connect } from 'react-redux';
import * as actions from './actions/actions';
class HomePage extends React.Component {
constructor(props) {
super(props);
this.setState({
loading: true
});
}
render() {
var inPage = undefined;
if(this.props.isAuthenticated) {
console.log('Logged in');
inPage = <Dashboard user = {HomePage.user}/>;
}
else if (this.state.loading){
console.log('Loading');
inPage = <Loading />;
}
else {
console.log('Login');
inPage = <LoginPage />;
}
return (
<div>
{inPage}
</div>
);
}
}
function mapStateToProps(state) {
this.setState({
loading: false
});
return {
content: state.auth.content,
isAuthenticated: state.auth.authenticated
}
}
export default connect(mapStateToProps, actions)(HomePage);
Not sure if this is where your issue lies or not. So I may be way off base.
But, I would check to be sure you are passing down the children to your components. The way I've done this is in my App class as such:
import React, {PropTypes} from 'react';
import { connect } from 'react-redux';
class App extends React.Component{
render(){
return(
<div className="container-fluid">
{this.props.children}
</div>
);
}
}
App.propTypes={
children: PropTypes.object.isRequired
};
export default connect()(App);

Categories

Resources