I wrapped the Profile component in a HOC that is supposed to redirect the user to certain page once he logs out while in this route, like so :
<Route
path="/profile/:username"
render={props=> withAuth(<Profile currentUser={currentUser} {...props} />)}
/>
I get this Error:
Error: Objects are not valid as a React child (found: object with keys {$$typeof, type, compare, WrappedComponent, displayName}). If you meant to render a collection of children, use an array instead.
this is My HOC:
import React, { Component } from "react";
import { connect } from "react-redux";
export default function withAuth(ComponentToBeRendered) {
class Authenticate extends Component {
componentDidMount() {
if (this.props.isAuthenticated === false) {
this.props.history.push("/signin");
}
}
componentWillUpdate(nextProps) {
if (nextProps.isAuthenticated === false) {
this.props.history.push("/signin");
}
}
render() {
return ComponentToBeRendered ;
}
}
function mapStateToProps(state) {
return {
isAuthenticated: state.currentUser.isAuthenticated
}
}
return connect(mapStateToProps)(Authenticate);
}
This is My Profile Component:
import MessageList from "../containers/MessageList";
import UserAside from "./UserAside";
import { connect } from 'react-redux';
import { selectTheUser } from '../store/selectors'
const Profile = props => {
return (
<div className="row">
<MessageList {...props} />
<UserAside
{...props} />
</div>
)
}
const mapStateToProps = (state, props) => ({
userToVisit: selectTheUser(props.match.params.username)(state)
})
export default connect(mapStateToProps)(Profile);
In your HOC
render() {
return <ComponentToBeRendered {...this.props}/> ;
}
In your Router
const ProfileWithAuth = withAuth(Profile)
<Route
path="/profile/:username"
render={props=> (<ProfileWithAuth currentUser={currentUser} {...props}/>)}
/>
Related
I learn react and know, how to create static routes, but can't figure out with dynamic ones. Maybe someone can explain, I'll be very grateful. Let there be two components, one for rendering routes, and another as a template of a route. Maybe something wrong in the code, but hope You understand..
Here is the component to render routes:
import React, { Component } from 'react';
import axios from 'axios';
import Hero from './Hero';
class Heroes extends Component {
constructor(props) {
super(props);
this.state = {
heroes: [],
loading: true,
error: false,
};
}
componentDidMount() {
axios.get('http://localhost:5555/heroes')
.then(res => {
const heroes = res.data;
this.setState({ heroes, loading: false });
})
.catch(err => { // log request error and prevent access to undefined state
this.setState({ loading: false, error: true });
console.error(err);
})
}
render() {
if (this.state.loading) {
return (
<div>
<p> Loading... </p>
</div>
)
}
if (this.state.error || !this.state.heroes) {
return (
<div>
<p> An error occured </p>
</div>
)
}
return (
<div>
<BrowserRouter>
//what should be here?
</BrowserRouter>
</div>
);
}
}
export default Heroes;
The requested JSON looks like this:
const heroes = [
{
"id": 0,
"name": "John Smith",
"speciality": "Wizard"
},
{
"id": 1,
"name": "Crag Hack",
"speciality": "Viking"
},
{
"id": 2,
"name": "Silvio",
"speciality": "Warrior"
}
];
The route component (maybe there should be props, but how to do it in the right way):
import React, { Component } from 'react';
class Hero extends Component {
render() {
return (
<div>
//what should be here?
</div>
);
}
}
export default Hero;
I need something like this in browser, and every route url should be differentiaie by it's id (heroes/1, heroes/2 ...):
John Smith
Crag Hack
Silvio
Each of them:
John Smith.
Wizard.
and so on...
Many thanks for any help!)
Use Link to dynamically generate a list of routes.
Use : to indicate url params, :id in the case
Use the match object passed as props to the rendered route component to access the url params. this.props.match.params.id
<BrowserRouter>
/* Links */
{heroes.map(hero => (<Link to={'heroes/' + hero.id} />)}
/* Component */
<Route path="heroes/:id" component={Hero} />
</BrowserRouter>
class Hero extends Component {
render() {
return (
<div>
{this.props.match.params.id}
</div>
);
}
}
Update so this works for React Router v6:
React Router v6 brought some changes to the general syntax:
Before: <Route path="heroes/:id" component={Hero} />
Now: <Route path="heroes/:id" element={<Hero />} />
You can't access params like with this.props.match anymore:
Before: this.props.match.params.id
Now: import {useParams} from "react-router-dom";
const {id} = useParams();
You can now just use id as any other variable.
To do this you simply add a colon before the url part that should be dynamic. Example:
<BrowserRouter>
{/* Dynamic Component */}
<Route path="heroes/:id" component={Hero} />
</BrowserRouter>
Also you can use the useParams hook from react-router-dom to get the dynamic value for use in the page created dynamically. Example:
import { useParams } from "react-router-dom";
const Hero = () => {
const params = useParams();
// params.id => dynamic value defined as id in route
// e.g '/heroes/1234' -> params.id equals 1234
return (...)
}
import React, { Component } from 'react';
import {
ScrollView,
StyleSheet,
} from 'react-native';
import axios from 'axios';
import CarDetail from '../components/CarDetail';
class CarList extends Component {
state = { cars: [] };
componentDidMount() {
axios.get('https://www.website.com/ajx/home_ajx/lister')
.then(response => this.setState({ cars: response.data.data[0]['S1']
}));
//Add an if statement here to notify the user when the request fails
}
renderCars() {
return this.state.cars.map(model =>
<CarDetail key={model.title} modelprop={model} />
);
}
render() {
console.log(this.state);
return (
<ScrollView>
{this.renderCars()}
</ScrollView>
);
}
}
export default CarList;
And the image path in the JSON file is as below:
"image": "data/models/peugeot-2008-138twf_600X400_.jpg",
Below is where I'm calling the image in another component
const CarDetail = ({ modelprop }) => {
const { image } = modelprop;
return (
<Card>
<CardSection>
<View>
<Image style={imageStyle}
source={{ uri: props.modelprop.image }}
/>
</View>
I believe I need to use some kind of prefix maybe in my Global.js which I couldn't find or figure out.
Any help is highly appreciated.
Mostly like your code has a bug:
const CarDetail = ({ modelprop }) => {
const { image } = modelprop;
return (
<View>
<Image
style={imageStyle}
source={{ uri: image }}
/>
</View>
);
}
If the image is something like data/models/peugeot-2008-138twf_600X400_.jpg, you should use `${Global.imageUrlPrefix}${image}` to concat all.
I have tried to follow the udemy guide to implement react router. the problem i have is when loading the page directly with an id without going through a list page first. it does not seem to load the records. i want to go to page /commsmatrix/approve/121 and load record 121. i am using react router v4. in mapStateToProps, records is undefined
approve.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchCommsmatrix } from '../../actions/commsmatrices';
import { bindActionCreators } from 'redux';
import FontAwesome from 'react-fontawesome';
class Approve extends Component {
constructor(props) {
super(props);
this.meta = { title: 'Comms Matrix Approval', description: 'Sox approval' };
this.runOnce = false;
this.passMetaBack = this.passMetaBack.bind(this);
this.initConfirm = this.initConfirm.bind(this);
}
componentDidMount() {
this.passMetaBack;
const { id } = this.props.match.params.id;
this.props.fetchCommsmatrix(id);
}
passMetaBack = () => {
this.props.passMetaBack(this.meta);
};
initConfirm(){
this.runOnce = true;
/*this.props.fetchCommsmatrix(121)
.then(function(response){
console.log(response);
let data = response.payload.data;
if(data.header.error){
self.setState({
showError: true,
errorMsg: data.header.message
});
}else{
}
});*/
}
render() {
console.log(this);
if(!this.runOnce && this.props.isReady){
this.initConfirm();
}
const { record } = this.props ;
console.log(record);
let message = <div>Confirming...<i className="fa fa-spinner fa-spin"></i></div>;
return (
<div className="container-fluid">
<div className="row-fluid top-buffer">{message}</div>
</div>
);
}
}
function mapStateToProps({ records }, ownProps) {
console.log(records);
console.log(ownProps);
return { record : records[ownProps.match.params.id] };
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(
{ fetchCommsmatrix },
dispatch
);
}
export default connect(mapStateToProps, { fetchCommsmatrix })(Approve);
here is my actions
import axios from 'axios';
export const FETCH_COMMSMATRIX = 'fetch_commsmatrix';
export function fetchCommsmatrix(id) {
const request = axios.get(`/api/user/comms/matrices/id/`+id+`/format/json?quiet=1`);
return {
type: FETCH_COMMSMATRIX,
payload: request
};
}
export const FETCH_COMMSMATRICES_BY_SERVICE = 'fetch_commsmatrices_by_service';
export function fetchCommsmatricesByService(service_id) {
const request = axios.get(`/api/user/comms/matrices/format/json?quiet=1&service_id=`+service_id);
return {
type: FETCH_COMMSMATRICES_BY_SERVICE,
payload: request
};
}
here is my reducer
import { FETCH_COMMSMATRIX, FETCH_COMMSMATRICES_BY_SERVICE } from '../actions/commsmatrices';
export default function(state = {}, action) {
switch (action.type) {
case FETCH_COMMSMATRIX:
return { ...state, [action.payload.data.body.recordset.record[0].id] : action.payload.data.body.recordset.record[0] };
case FETCH_COMMSMATRICES_BY_SERVICE:
return action.payload.data.body.recordset.record;
default:
return state;
}
}
here is index reducer
import { combineReducers } from 'redux';
import { reducer as formReducer } from 'redux-form';
import ActiveUserReducer from './reducer_active_user';
import CommsmatricesReducer from './reducer_commsmatrices';
import ContentReducer from './reducer_content';
import ContentVideListReducer from './reducer_content_video_list';
import SecurityExemptionsReducer from './reducer_security_exemption';
import ReportsWorkerJobs from './reducer_reports_workerjobs';
import ReportsWorkerJobsCount from './reducer_reports_workerjobs_count';
import ReportsFactsandfigures from './reducer_reports_factsandfigures';
import ReportsFactsandfiguresCount from './reducer_reports_factsandfigures_count';
import ServicesReducer from './reducer_services';
import ServicesEditCheckReducer from './reducer_services_edit_check';
import ServicesAddReducer from './reducer_services_add';
import ServicesRenameReducer from './reducer_services_rename';
import ServicesRemoveReducer from './reducer_services_remove';
import TemplatesReducer from './reducer_templates';
const rootReducer = combineReducers({
form: formReducer,
activeUser: ActiveUserReducer,
commsmatrices: CommsmatricesReducer,
content: ContentReducer,
contentVideoList: ContentVideListReducer,
reportsWorkerJobs: ReportsWorkerJobs,
reportsWorkerJobsCount: ReportsWorkerJobsCount,
securityExemptions: SecurityExemptionsReducer,
reportsFactsAndFigures: ReportsFactsandfigures,
reportsFactsAndFiguresCount: ReportsFactsandfiguresCount,
services: ServicesReducer,
servicesEditCheck: ServicesEditCheckReducer,
servicesAdd: ServicesAddReducer,
servicesRename: ServicesRenameReducer,
servicesRemove: ServicesRemoveReducer,
templatesReducer: TemplatesReducer
});
export default rootReducer;
here is app
import React, { Component } from 'react';
import { Switch, Route, withRouter, Redirect } from 'react-router-dom';
import ReactGA from 'react-ga';
import { connect } from 'react-redux';
import { fetchActiveUser } from './actions/index';
import { bindActionCreators } from 'redux';
import { getHttpRequestJSON } from './components/HTTP.js';
import Header from './components/header';
import Logout from './components/logout';
import SideBar from './components/sidebar';
import HomeContent from './containers/home';
import Ldapuser from './components/ldapuser';
import Admin from './components/admin/admin';
import Services from './components/services/index';
import SecurityExemptionsNew from './components/security/security_exemptions_new';
import WorkerJobs from './components/reports/workerjobs';
import FactsAndFigures from './components/reports/factsandfigures';
import Approve from './components/commsmatrix/approve';
import CommsMatrixTemplates from './components/commsmatrix/templates';
import CommsMatrixTemplate from './components/commsmatrix/template';
ReactGA.initialize('UA-101927425-1');
function fireTracking() {
ReactGA.pageview(window.location.pathname + window.location.search);
}
class App extends Component {
constructor(props) {
super(props);
this.state = {
isGuest: false,
isSupp: false,
priv: [],
loading: true,
version: '',
redirect: false,
title: 'Home',
description: '',
isReady: false
};
}
setRedirect = () => {
this.setState({
redirect: true
});
};
renderRedirect = () => {
//if (this.state.redirect) {
return <Redirect to="/SSOLogon/manual_login.jsp" />;
//}
};
initData = () => {
let self = this;
getHttpRequestJSON(
'/api/user/get/user/method/is/guest/format/json?quiet=1'
)
.then(response => {
let isGuest = response.body.recordset.record.isGuest;
if (isGuest) {
/*$(".logo").trigger('click');
//$("#overlay").show();
$('#modalIntro').modal('toggle');
$("#modalIntro").on("hidden.bs.modal", function () {
$(".logo").trigger('click');
});*/
}
//self.props.isGuest = isGuest;
//self.props.loading = false;
//self.props.version = response.header.version;
self.setState({
loading: false,
version: response.header.version,
isGuest: isGuest
});
})
.catch(error => {
console.log('Failed!', error);
//$('#myModalError .modal-body').html(error);
//$('#myModalError').modal('show');
});
getHttpRequestJSON(
'/api/user/get/user/method/is/supp/format/json?quiet=1'
)
.then(response => {
self.setState({
isSupp: response.body.recordset.record.isSupp
});
})
.catch(error => {
console.log('Failed!', error);
//$('#myModalError .modal-body').html(error);
//$('#myModalError').modal('show');
});
getHttpRequestJSON(
'/api/user/get/user/method/priv/format/json?quiet=1'
)
.then(response => {
self.setState({
priv: response.body.recordset.record
});
})
.catch(error => {
console.log('Failed!', error);
//$('#myModalError .modal-body').html(error);
//$('#myModalError').modal('show');
});
};
componentDidMount() {
let self = this;
this.props.fetchActiveUser()
.then(() => {
self.initData();
})
.then(() => {
self.setState({
isReady : true
});
})
if (this.props.activeUser.name == 'AuthError') {
this.setRedirect();
}
}
passMetaBack = (meta) => {
this.setState({
title: meta.title,
description: meta.description
})
}
render() {
if (this.props.activeUser.name == 'AuthError') {
//console.log('redirect');
this.renderRedirect();
}
return (
<div>
<Header
activeUser={this.props.activeUser}
loading={this.state.loading}
version={this.state.version}
title={this.state.title}
description={this.state.description}
/>
<SideBar isReady={this.state.isReady} />
<main>
<Switch>
<Route
path="/commsmatrix/approve/:id"
component={Approve}
/>
</Switch>
</main>
</div>
);
}
}
//export default App;
function mapStateToProps(state) {
if (state.activeUser.id > 0) {
ReactGA.set({ userId: state.activeUser.id });
}
// Whatever is returned will show up as props
// inside of the component
return {
activeUser: state.activeUser
};
}
// Anything returned from this function will end up as props
// on this container
function mapDispatchToProps(dispatch) {
// Whenever getUser is called, the result should be passed
// to all our reducers
return bindActionCreators({ fetchActiveUser }, dispatch);
}
//Promote component to a container - it needs to know
//about this new dispatch method, fetchActiveUser. Make it available
//as a prop
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(App));
index.js
import './scripts/api';
import React, { Component } from 'react'
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware, combineReducers } from 'redux';
import { BrowserRouter, Route, browserHistory } from 'react-router-dom';
import promise from 'redux-promise';
import App from './App'
import reducers from './reducers';
import 'react-quill/dist/quill.snow.css'; // ES6
require("babel-core/register");
require("babel-polyfill");
const createStoreWithMiddleware = applyMiddleware(promise)(createStore);
ReactDOM.render(
<Provider store={createStoreWithMiddleware(reducers)}>
<BrowserRouter history={browserHistory}>
<App/>
</BrowserRouter>
</Provider>
, document.getElementById('root'));
UPDATE
so I have updated records to match reducer name. seems to work but need a way to handle errors when there are no records
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchCommsmatrix } from '../../actions/commsmatrices';
import { bindActionCreators } from 'redux';
import FontAwesome from 'react-fontawesome';
class Approve extends Component {
constructor(props) {
super(props);
this.meta = { title: 'Comms Matrix Approval', description: 'Sox approval' };
this.runOnce = false;
this.passMetaBack = this.passMetaBack.bind(this);
this.initConfirm = this.initConfirm.bind(this);
}
componentDidMount() {
this.passMetaBack;
const id = this.props.match.params.id;
this.props.fetchCommsmatrix(id);
}
passMetaBack = () => {
this.props.passMetaBack(this.meta);
};
initConfirm(){
this.runOnce = true;
}
render() {
let message = <div>Confirming...<i className="fa fa-spinner fa-spin"></i></div>;
const { commsmatrix } = this.props ;
if(!this.runOnce && this.props.isReady && Object.keys(commsmatrix).length > 0 ){
this.initConfirm();
}
return (
<div className="container-fluid">
<div className="row-fluid top-buffer">{message}</div>
</div>
);
}
}
function mapStateToProps({ commsmatrices }, ownProps) {
return { commsmatrix : commsmatrices[ownProps.match.params.id] };
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(
{ fetchCommsmatrix },
dispatch
);
}
export default connect(mapStateToProps, { fetchCommsmatrix })(Approve);
Can I see your root index.js for completeness sake? I can't use comments unfortunately.
Why do you have const { id } = this.props.match.params.id;
Shouldn't it be justconst { id } = this.props.match.params;
Does your state object even have a records property?
I know I can access history.goBack() to go back in the router history.
However, I'd like to create a <Link /> tag that has this functionality and relies on the to property (href) to navigate back rather than an onClick.
Is this possible?
I may have a solution to your problem using the context api.
But I strongly believe that it would be easier to use history.goBack().
First you'll need to wrap the App component inside a router:
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
ReactDOM.render(
<Router>
<App />
</Router>,
document.getElementById('root'),
);
Then in your your App/index.js file you'll need to listen to the location change event and set your state accordingly:
import React, { Component } from 'react';
import { Switch, Route } from 'react-router-dom';
import { withRouter } from 'react-router'
class App extends Component {
state = { prevLocation: '' };
// Use the context api to retrieve the value in your Link
getChildContext = () => (
{
prevLocation: this.state.prevLocation,
}
);
componentWillReceiveProps(nextProps) {
if (nextProps.location !== this.props.location) {
this.setState({ prevLocation: this.props.location.pathname });
}
}
render() {
return (
<div>
<Switch>
// ...
</Switch>
</div>
);
}
}
App.childContextTypes = {
prevLocation: PropTypes.string,
};
export default withRouter(App);
Then in can create a GoBack component and use the context API to retrieve the value the previous path.
import React from 'react';
class GoBack extends React.Component {
render() {
return <Link to={this.context.prevLocation}>click</Link);
}
}
GoBack.contextTypes = {
prevLocation: PropTypes.string,
};
I am looking for a solution in order to still be able to use Link from react-router instead of a when testing href attribute value.
Indeed, I have some components which change of route according to the context. However, when I am testing the href attribute value, the only thing returned is null.
However, when I use an a, it returns me the expected value.
Here is an failing test:
import React from 'react';
import {Link} from 'react-router';
import TestUtils from 'react-addons-test-utils';
import expect from 'must';
const LINK_LOCATION = '/my_route';
class TestComponent extends React.Component {
render() {
return (
<div>
<Link className='link' to={LINK_LOCATION}/>
<a className='a' href={LINK_LOCATION}/>
</div>
);
}
}
describe('Url things', () => {
it('should return me the same href value for both link and a node', () => {
const test_component = TestUtils.renderIntoDocument(<TestComponent/>);
const link = TestUtils.findRenderedDOMComponentWithClass(test_component, 'link');
const a = TestUtils.findRenderedDOMComponentWithClass(test_component, 'a');
expect(link.getAttribute('href')).to.eql(a.getAttribute('href'));
});
});
Output: AssertionError: null must be equivalent to "/my_route"
knowbody from React-router answered to see how they test Link, but they do not have dynamic context which can change value of the href attribute.
So I have done something like that:
class ComponentWrapper extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
set_props(props) {
this.setState({props});
}
render() {
if (this.state.props) {
return <Component {...this.state.props}/>;
}
return null;
}
}
But now, from my component helper:
render_into_document() {
const full_component_props = {
location: this.location,
widget_categories: this.widget_categories
};
node = document.createElement('div');
this.component = render((
<Router history={createHistory('/')}>
<Route path='/' component={ComponentWrapper} />
</Router>
));
this.component.set_props(full_component_props);
return this;
}
I am not able to lay hand on this.component in order to changes props. How could I do that?
I just looked at how react-router tests <Link /> and came up with this for my case:
import test from 'ava'
import React from 'react'
import { render } from 'enzyme'
import { Router, Route } from 'react-router'
import createHistory from 'history/lib/createMemoryHistory'
import SkipToXoom from '../skip-to-xoom'
test('the rendered button redirects to the proper URL when clicked', t => {
const toCountryData = { countryName: 'India', countryCode: 'IN' }
const div = renderToDiv({ toCountryData, disbursementType: 'DEPOSIT', userLang: 'en_us' })
const { attribs: { href } } = div.find('a')[0]
t.true(href.includes(encodeURIComponent('receiveCountryCode=IN')))
t.true(href.includes(encodeURIComponent('disbursementType=DEPOSIT')))
t.true(href.includes(encodeURIComponent('languageCode=en')))
})
/**
* Render the <SkipToXoom /> component to a div with the given props
* We have to do some fancy footwork with the Router component to get
* the Link component in our SkipToXoom component to render out the href
* #param {Object} props - the props to apply to the component
* #returns {Element} - the div that contains the element
*/
function renderToDiv(props = {}) {
return render(
<Router history={createHistory('/')}>
<Route path="/" component={() => <SkipToXoom {...props} userLang="en" />} />
</Router>
)
}
I hope that's helpful!