Changing Application State based on Router - reactjs

I'm creating a application where I have a component which shows data based on the router location
for example
localhost:8080/us should show "USA flag and some text related to United States"
localhost:8080/in should show "India flag and some text related to India"
I have two reducers one for India and other for US and I have a Root reducer as shown bellow
import {combineReducers} from 'redux';
import IndiaData from './IndiaData';
import USData from './UsData';
const rootReducer = combineReducers({
IndiaData,
USData,
ActiveData
});
export default rootReducer;
My Sample reducer IndiaData is as bellow
export default function() {
return [
{
"StateName": "AP",
"StateImg": "../static/image1.jpg",
},
{
"StateName": "TS",
"StateImg": "../static/image2.jpg",
},
{
"StateName": "TN",
"StateImg": "../static/image3.jpg",
}
]
}
I'm using mapStateToProps in my react component, please find the
code below
import React, {Component} from 'react';
import {connect} from 'react-redux';
import styles from '../styles/custom.css'
class CountryData extends React.Component {
Country(){
return this.props.usdata.map((Data)=>{
return(
<div className="col-md-4">
<img className="panel" src={Data.StateImg}/>
<p>{Data.StateName}</p>
</div>
);
})
}
render(){
return(
<div>
<div className="container margin-top jumbotron">
{this.Country()}
</div>
</div>
);
}
}
function mapStateToProps (state){
return {
indiadata: state.IndiaData,
usdata: state.USData,
};
}
export default connect(mapStateToProps)(CountryData);
and my router configuration is as below
import React from 'react';
import {Route, IndexRoute} from 'react-router';
import App from './components/app';
import CountryData from './components/CountryData';
export default (
<Route path ="/" component={App}>
<Route path="in" component={CountryData} />
<Route path="us" component={CountryData} />
</Route>
);
So Finally I should be able to read what is present in the url path and then show US data or India data dynamically, can some one please help me with this?
Thank you for your support in advance!!

You can make a route like so:
And then access that country param in the container via this.props.params.country.
In your mapStateToProps function, you could use this constant to render the correct country data.
return this.props.data.map((Data)=>{
return(
<div className="col-md-4">
<img className="panel" src={Data.StateImg}/>
<p>{Data.StateName}</p>
</div>
);
})
...
// other code here
...
function mapStateToProps (state, props){
return {
data: props.params.country === 'in'
? state.IndiaData
: state.USData
};
}
Another way would be to export two different connected components.
return this.props.data.map((Data)=>{
return(
<div className="col-md-4">
<img className="panel" src={Data.StateImg}/>
<p>{Data.StateName}</p>
</div>
);
})
...
// other code here
...
function IndiaData (state){
return {
data: state.IndiaData
};
}
function USData (state){
return {
data: state.USData
};
}
export const India = connect(IndiaData)(CountryData);
export const US = connect(USData)(CountryData);
then in your routes:
import React from 'react';
import {Route, IndexRoute} from 'react-router';
import App from './components/app';
import { India, US } from './components/CountryData';
export default (
<Route path ="/" component={App}>
<Route path="in" component={India} />
<Route path="us" component={US} />
</Route>
);

Related

How to use react-router-dom with Context API V6?

I am changing the value in PC component but it is not reflected in the BR1 component. If I don't use react-router-dom, everything works fine, but I need the routes.
App.js code
import React, { createContext, useState } from 'react';
import './App.css';
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import BR1 from './Components/BR1';
import PC from './Components/P_c1'
import BR from './Components/BR';
export const BRcontext = createContext();
function App() {
const [value, setValue] = useState(false)
return (
<div>
<BRcontext.Provider value={{value, setValue}}>
<Router>
<Routes>
<Route path='/PC' element={<PC/>} />
<Route path='/BR1' element={<BR1/>} />
<Route path='/BR' element={<BR/>} />
</Routes>
</Router>
</BRcontext.Provider>
</div>
);
}
export default App;
PC code
import React, { useContext } from 'react'
import './Profile.css';
import { BRcontext } from '../App';
export default function Profile() {
const {value, setValue} = useContext(BRcontext);
return (
<div>
<div className='container mt-5'>
<div className='row'>
<div>
<h3 className='mt-5'>Send Request</h3>
<button className='btn btn-success mt-3 ps-3 pe-3' onClick={()=>{setValue(true)}}>Request</button>
</div>
</div>
</div>
</div>
)
}
BR1 code
import React, { useContext } from 'react'
import BR from './BR'
import { BRcontext } from '../App'
import { Link } from 'react-router-dom';
export default function BR1() {
const {value} = useContext(BRcontext);
// let navigate = useNavigate();
return (
<div>
{console.log(value)} //this remains false
{value ? <Link to="/BR"/>: console.log('hello there!')}
</div>
)
}
In BR1 code, I want the value to become true when a button in the PC component is clicked
Link - https://codesandbox.io/s/great-star-bzhuvw?file=/src/App.js
It seems there's no way to navigate from /PC to /BR1 unless changing the browser URL directly, and by doing this, you lose the current context value because it's in memory. If you intend to keep this behaviour, you should consider persisting the context value every time you change it and initialize it with the previously persisted one.
An example using the browser's local storage:
// Helper function to read the storaged value if it exists
function getPersistedValue() {
const serializedValue = localStorage.getItem('value')
try {
if (!serializedValue) {
throw new Error('No previously persisted value found')
}
return JSON.parse(serializedValue)
} catch {
return false
}
}
// Using the helper function to initialize the state
const [value, setValue] = useState(getPersistedValue())
// Synchronizing the persisted value on local storage with the in-memory one
useEffect(() => {
localStorage.setItem('value', JSON.stringify(value))
}, [value])
If you want, I forked your Code Sandbox and applied these changes: https://codesandbox.io/s/router-context-forked-uqhzye.

Problem with the id of the items in shopping cart application in react

I am developing a shopping cart application . I am using redux and routing . There are mainly 3 pages Home,shop and About. I am adding authentication to the shop page and after successful authentication the user can enter into the shop page. In the shop page there are items which we can add to cart . totally i have 3 items in my shop page.whats my problem is when i am clicking add to cart for 1 st item it is displaying 3 items. I know the problem is with the id's of the items. But I am struggling from past one hour to resolve it.
Thanks in advance.
//App.js
import React ,{Component} from 'react';
import './App.css';
import Navigation from './Step1/Navbar'
import Home from './Step1/Home'
import Shop from './Step1/Shop'
import About from './Step1/About'
import Login from './LoginAuthentication/Loginform'
import {BrowserRouter as Router,Route} from 'react-router-dom'
import {connect} from 'react-redux'
const mapStateToProps=(state)=>{
return{
isLogin:state.isLogin
}
}
class App extends Component {
render(){
return (
<Router>
<div className="App">
<Navigation/>
<Route path="/" exact component={Home}/>
<Route path="/about" component={About}/>
<Route path="/shop"
render={() =>(
this.props.isLogin ? <Shop/> : <Login/>
) }
/>
</div>
</Router>
);
}
}
export default connect(mapStateToProps,null)(App);
//shop template.js
import React from 'react'
//import logo from '../cricket bat.jpg'
import Displaylist from '../Components/DisplayList'
const Shop_template=(props)=> {
return (
<div className="container">
<div className="row">
<div className="col-sm-6">
<div className="card-body">
<h4 className="card-title">{props.cardtitle}</h4>
<p className="card-text">{props.description}</p>
<h3>Price :{props.currency}{props.price}</h3>
<button type="button" onClick={props.cartHandler} className="btn btn-primary">Add to cart</button>
</div>
</div>
<div className="col-sm-6">
<Displaylist/>
</div>
</div>
</div>
)
}
export default Shop_template
//shop.js --> i am updating the state in shop.js to redux state
import React, { Component } from 'react'
import ShopTemplate from './Shop_template'
import {connect} from 'react-redux'
import {action2} from '../Actions/action1'
const mapDispatchToProps=(dispatch)=>({
CartHandler:(details)=>dispatch(action2(details))
})
class Shop extends Component {
state={
items:[
{id:1,cardtitle:'SSS Bat',description:'A stroke to score',currency:'$',price:100},
{id:2,cardtitle:'One upon a wall street',description:'A way to investment',currency:'$',price:50},
{id:3,cardtitle:'mi powerbank 10000mah',description:'Keep charged always',currency:'$',price:200}
]
}
cartHandler=()=>{
this.props.CartHandler(this.state.items)
}
render() {
const info=this.state.items.map((detail)=>{
return <ShopTemplate
cardtitle={detail.cardtitle}
description={detail.description}
currency={detail.currency}
price={detail.price}
key={detail.id}
cartHandler={this.cartHandler}
/>
})
return (
<div>
{info}
</div>
)
}
}
export default connect(null,mapDispatchToProps)(Shop)
/
/reducer.js
import {LOGINCHECK} from '../Constants/actiontypes'
import {ADDTOCART} from '../Constants/actiontypes'
const initialState={
isLogin:false,
items:[]
}
const reducer1=(state=initialState,action)=>{
//console.log(state)
if(action.type===LOGINCHECK){
return Object.assign({},state,{isLogin:true})
}
if(action.type===ADDTOCART){
return Object.assign({},state,{items:state.items.concat(action.payload)})
}
return state
}
export default reducer1
//DisplayList.js
import React from 'react'
import Display from './Display'
import {connect} from 'react-redux'
const mapStateToProps=(state)=>{
return{
items:state.items
}
}
const DisplayList=({items})=>{
console.log(items.body)
return(
<div>
{items.map(it=>{
return(
<Display iteminfo={it.body} key={it.body.id}/>
)
})
}
</div>
)
}
export default connect(mapStateToProps,null)(DisplayList)
//Display.js
import React from 'react'
const Display=({iteminfo:{id,cardtitle, description,currency,price}}) =>{
return (
<div>
<h4>{cardtitle}</h4>
<p>{description}</p>
<h3>{currency}{price}</h3>
<button type="button" className="btn btn-danger">Remove From cart</button>
</div>
)
}
export default Display
I can see too many problems in your source code,
first of all, namings can be better now it's confusing.
your shop items are in Shop component state but it has to be inside your redux module
initialState = {
items: ["your items should be here"]
}
of course, its because you are hardcoding your shop items. you may want to Get shop items from an API.
when you click on add to cart button you have to pass itemId to action. (right now you don't know which item is going to add to cart ).
and then inside reducer action.payload.itemId will be itemId that is added to cart then you can do something like this
const foundItem = state.items.find(it => it.id === action.payload.itemId);
now you found item in your products(items) array,
you can add this item to another array called basket or cart that represents items user added.
for the next step you want to add an inventory and quantity property to see how many items the user wants and how many do you have in your inventory
if you want a more detailed description don't hesitate to ask

Show Detail Component with React-Router

I am learning react-router and trying to display a list of courses and course detail. But now, the CourseDetail2 component page does not display. Help!
App.js
`
import React, { Component } from 'react';
import axios from 'axios';
import CourseList2 from './components/CourseList2'
//campus data
const campusData = [
{ id: 1, value:'A',name: 'A' },
{ id: 2, value:'B',name: 'B' },
{ id: 3, value:'C',name: 'C' }
]
class App extends Component {
state={campus:null,
Courses:[]}
componentDidMount(){
//api call
setState={Courses:response.data}
}
//event handler
handleCampusChkChange()=>{
//code
}
render() {
return (
<div className="App">
<Campus key={item.id} {...item} onChange={this.handleCampusChkChange} />
<CourseList2 courses={this.state.Courses}/>
</div>
);
}
}
export default App;
`
CourseList2.js
import React from 'react';
import CourseDetail2 from './CourseDetail2';
import {BrowserRouter as Router, Route, Link,Redirect} from 'react-router-dom';
import './CourseItem.css';
import App from './App';
const CourseList2=({Courses})=>{
console.log("coruses="+Courses);
const renderedList= Courses.map(course=>{
return (<div className="item" >
<div class="content">
<div class="header">
<h4>
{course.SUBJECT} {course.CATALOG} {course.DESCR}
</h4> </div>
<Link to={{ pathname: 'course/'+course.ID}}
key={course.ID}>
View More
</Link>
</div>
</div>
)
});
return (
<Router><div className="List ui relaxed divided list">
{renderedList}
<Route path="course/:course.ID" component={CourseDetail2} />
</div></Router>);
}
export default CourseList2
CourseDetail2.js
import React, { Component } from 'react';
class CourseDetail2 extends Component {
render(){
return (
<div>
Course Detail: CLASS ID {this.props.match.params.ID}
</div>
);
}
};
export default CourseDetail2;
Adding as answer instead of comment.
Probably want to pass this.state.Courses to CourseList2, and wrap CourseDetails2 with withRouter HOC from react-router-dom so it can access the route match prop.
Also, the path in the route in CourseList2 should probably be path="course/:ID" since that is how you access it on the details.
location, match and history objects can only be accessed when you wrap the component with the higher order component withRouter.
Right now you don't have access to this.props.match in CourseDetail2 component.
import React, { Component } from 'react';
import {withRouter} from 'react-router';
class CourseDetail2 extends Component {
render(){
return (
<div>
Course Detail: CLASS ID {this.props.match.params.courseID}
</div>
);
}
};
export default withRouter(CourseDetail2);
Also the string after : doesn't have match with the code. It can be anything.
<Route path="course/:courseID" component={CourseDetail2} />
And you access using that string name in your code.

Using pagination in a React component, without re-rendering the entire component

I've set up pagination(using a 3rd party component) in one of my React components. Each time a page button is clicked, this function is executed:
handlePageChange= (page)=>{
history.push(`/duplicates?page=${page}`)
}
I'm using "Router" from "react-router-dom", and "createBrowserHistory" from JS history library:
When the component mounts, i simply extract the "page" query parameter, and dispatch a Redux action, that fetches all relevant data, and puts its in the Redux state:
componentDidMount(){
this.props.fetch(this.state.activePage)
}
The "activePage" is taken from the component state:
state={
activePage: this.queryParams.page || 1
}
Everything works nicely, with one very fundamental "flaw": Being that i use history.push on every pagination action, the entire component re-mounts. Sure, i can navigate back and forth in my "pages", and even bookmark them, but the fact that the entire component needs to re-render, seems to undermine one of the main purposes of React: Being very efficient, when it comes to DOM manipulations.
Is there any way to setup pagination, without having to choose between history and efficiency?
EDIT: this is the component:
import React from 'react';
import _ from "lodash";
import { connect } from 'react-redux';
import { searchAction, fetchDuplicates } from '../../actions/products';
import DuplicateProduct from './DuplicateProduct';
import Pagination from 'react-js-pagination'
import queryString from 'query-string';
import {history} from '../../routers/AppRouter';
class Duplicates extends React.Component{
queryParams= queryString.parse(this.props.location.search);
state={
activePage: this.queryParams.page || 1
}
handlePageChange= (page)=>{
history.push(`/duplicates?page=${page}`)
}
componentDidMount(){
this.props.fetch(this.state.activePage)
}
render(){
console.log(this.queryParams.page)
return(
<div>
<h1>Duplicate Titles</h1>
<p>number of pages: {this.props.numberOfPages}</p>
<Pagination
activePage={parseInt(this.state.activePage)}
itemsCountPerPage={150}
totalItemsCount={this.props.numberOfProducts}
pageRangeDisplayed={10}
onChange={this.handlePageChange}
className="pagination"
/>
<br/>
{this.props.duplicates.length>0 &&(
this.props.duplicates[0].map((duplicate_group)=>{
return (
<div key={duplicate_group[0].id}>
<DuplicateProduct duplicate_group={duplicate_group}/>
<hr/>
</div>
)
})
)
}
</div>
);
};
}
const mapStateToProps= (state)=>({
duplicates: state.products.duplicates,
numberOfPages: state.products.numberOfPages,
numberOfProducts: state.products.numberOfProducts
})
const mapDispatchToProps =(dispatch,props)=>({
fetch: (page)=> dispatch(fetchDuplicates(page))
})
export default connect(mapStateToProps, mapDispatchToProps)(Duplicates);
This is the PrivateRoute:
import React from 'react';
import { connect } from 'react-redux';
import { Route, Redirect } from 'react-router-dom';
import Header from '../components/Header';
export const PrivateRoute = ({
isAuthenticated,
component: Component,
...rest
}) => (
<Route {...rest} component={(props) => (
isAuthenticated ? (
<div>
<Header />
<Component {...props} />
</div>
) : (
<Redirect to="/" />
)
)} />
);
const mapStateToProps = (state) => ({
isAuthenticated: state.auth.isLoggedIn
});
export default connect(mapStateToProps)(PrivateRoute);

React with Redux - unable to bind action to parent

I am new to the Redux pattern i'm having some trouble linking an action in a separate JS file to it's parent component. Here is the component:
import React, {Component} from 'react';
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import playSample from './sampleActions/clickToPlay';
class SamplesInnerLrg extends Component {
render() {
return <div>
{
this.props.samples.map((sample) => {
return (
<div key={sample.id} className="sample-comp-lge">
<div className="sample-comp-lge-header">
<span className="sample-comp-lge-Name">{sample.sampleName}</span>
<span className="sample-comp-lge-id">{sample.sampleFamily}</span>
</div>
<div className="sample-comp-lge-audio" ref={sample.id} onClick={() => this.bind.playSample(sample)}>
<audio preload="auto" id="myAudio">
<source src={sample.soundSource} type="audio/wav" />
</audio>
</div>
<div className="sample-comp-lge-owner">{sample.uploader}</div>
</div>
)
})
}
</div>
}
}
function mapStateToProps(state) {
return {
samples:state.samples
};
}
function matchDispatchToProps(dispatch) {
return bindActionCreators({playSample:playSample},dispatch)
}
export default connect(mapStateToProps,matchDispatchToProps)(SamplesInnerLrg);
Specifically I am trying to have an onClick action on this line that will call a function in an imported file (clickToPlay.js):
<div className="sample-comp-lge-audio" ref={sample.id} onClick={() => this.bind.playSample(sample)}>
The clickToPlay file looks like so:
import $ from 'jquery';
export const playSample = (sample) => {
console.log(sample);
return {
type:"Play_Sample_clicked",
payload:sample
}
};
the error i'm getting on click is Cannot read property 'playSample' of undefined. I'm guessing I have bound the action to the component correcly but I can't tell why?
EDIT:
Here is my index.js file as requested:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import {Provider} from 'react-redux';
import { createStore,compose } from 'redux';
import allReducers from './reducers';
const store = createStore(allReducers,compose(
window.devToolsExtension ? window.devToolsExtension() : f => f
));
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>
,
document.getElementById('root')
);
You aren't exporting 'playSample' as the default export, you have two ways to reslove this:
You can do:
import { playSample } from './sampleActions/clickToPlay';
or
you can change export const playSample to const playSample Then add export default playSample at the end of your file.
Another note I want to mention about this line:
return bindActionCreators({playSample:playSample},dispatch)
I don't see why you are doing {playSample:playSample} just change it to playSample. ES6 allows you to eliminate key if it's the same as value, this is called object literal property value shorthand.

Resources