nested component can't get props through connect - reactjs

I am using a connected component as a props passed to a parent component, but I can't get any props from connect() or even from the function definition inside the component.
I have tried to defined the props function in render() but still not work
import React from 'react';
import PropTypes from 'prop-types';
import {Col,Row} from 'antd'
import injectSheet from 'react-jss';
class SubComponent extends React.Component{
constructor(props){
super(props);
}
picClicked=()=>{
console.log("cause state!");
console.log(this.props.picClicked);// output undefined
this.props.picClicked;
}
render(){
const {picClicked,classes} = this.props;// classes is from react-jss
console.log(this.props);// only classes is shown from the console log
return(
<div>
<div onClick={this.picClicked} className={classes.headBlank}>
</div>
</div>
)
}
SubComponent.propTypes={
picClicked:PropTypes.func.isRequired
};
export default injectSheet(logoStyle)(SubComponent)
then the container looks like following:
const mdtp = (dispatch)=>{
return{
picClicked:()=>{console.log("picClicked!");}
};
};
export default connect(null,mdtp)(SubComponent);
The SubComponent is then used as a props in another parent component like this:
ReactDOM.render(
<Provider store={storeMainContents}>
<ParentComponent contentsShow={<SubComponent />} />
</Provider>
,document.getElementById("parentComponent")
);
The parent component looks like this:
render(){
const {contentsShow,classes}=this.props;
return(
<div>
<div>
{this.props.contentsShow}
</div>
</div>
);
}
What I expected is that the SubComponent can access the props like picClicked as a action that can be dispatched or at least get accessed. Now it is simply undefined.

Related

Reactjs - how to pass props to Route?

I’m learning React Navigation using React-Router-Dom. I have created a simple app to illustrate the problem:
Inside App.js I have a Route, that points to the url “/” and loads the functional Component DataSource.js.
Inside DataSource.js I have a state with the variable name:”John”. There is also a buttonwith the onclick pointing to a class method that’s supposed to load a stateless component named ShowData.js using Route.
ShowData.js receives props.name.
What I want to do is: when the button in DataSource.js is clicked, the url changes to “/showdata”, the ShowData.js is loaded and displays the props.name received by DataSource.js, and DataSource.js goes away.
App.js
import './App.css';
import {Route} from 'react-router-dom'
import DataSource from './containers/DataSource'
function App() {
return (
<div className="App">
<Route path='/' component={DataSource}/>
</div>
);
}
export default App;
DataSource.js
import React, { Component } from 'react';
import ShowData from '../components/ShowData'
import {Route} from 'react-router-dom'
class DataSource extends Component{
state={
name:' John',
}
showDataHandler = ()=>{
<Route path='/showdata' render={()=><ShowData name={this.state.name}/>}/>
}
render(){
return(
<div>
<button onClick={this.showDataHandler}>Go!</button>
</div>
)
}
}
export default DataSource;
ShowData.js
import React from 'react';
const showData = props =>{
return (
<div>
<p>{props.name}</p>
</div>
)
}
export default showData;
I have tried the following, but, even though the url does change to '/showdata', the DataSource component is the only thing being rendered to the screen:
DataSource.js
showDataHandler = ()=>{
this.props.history.push('/showdata')
}
render(){
return(
<div>
<button onClick={this.showDataHandler}>Go!</button>
<Route path='/showdata' render={()=>{<ShowData name={this.state.name}/>}}/>
</div>
)
}
I also tried the following but nothing changes when the button is clicked:
DataSource.js
showDataHandler = ()=>{
<Route path='/showdata' render={()=>{<ShowData name={this.state.name}/>}}/>
}
render(){
return(
<div>
<button onClick={this.showDataHandler}>Go!</button>
</div>
)
}
How can I use a nested Route inside DataSource.js to pass a prop to another component?
Thanks.
EDIT: As user Sadequs Haque so kindly pointed out, it is possible to retrieve the props when you pass that prop through the url, like '/showdata/John', but that's not what I'd like to do: I'd like that the url was just '/showdata/'.
He also points out that it is possible to render either DataSource or ShowData conditionally, but that will not change the url from '/' to '/showdata'.
There were multiple issues to solve and this solution worked as you wanted.
App.js should have all the routes. I used Route params to pass the props to ShowData. So, /showdata/value would pass value as params to ShowData and render ShowData. And then wrapped the Routes with BrowserRouter. And then used exact route to point / to DataSource because otherwise DataSource would still get rendered as /showdata/:name has /
DataSource.js will simply Link the button to the appropriate Route. You would populate DataSourceValue with the appropriate value.
ShowData.js would read and display value from the router prop. I figured out the object structure of the router params from a console.log() of the props object. It ended up being props.match.params
App.js
import { BrowserRouter as Router, Route } from "react-router-dom";
import DataSource from "./DataSource";
import ShowData from "./ShowData";
function App() {
return (
<div className="App">
<Router>
<Route exact path="/" component={DataSource} />
<Route path="/showdata/:name" component={ShowData} />
</Router>
</div>
);
}
export default App;
DataSource.js
import React, { Component } from "react";
import ShowData from "./ShowData";
class DataSource extends Component {
state = {
name: " John",
clicked: false
};
render() {
if (!this.state.clicked)
return (
<button
onClick={() => {
this.setState({ name: "John", clicked: true });
console.log(this.state.clicked);
}}
>
Go!
</button>
);
else {
return <ShowData name={this.state.name} />;
}
}
}
export default DataSource;
ShowData.js
import React from "react";
const ShowData = (props) => {
console.log(props);
return (
<div>
<p>{props.name}</p>
</div>
);
};
export default ShowData;
Here is my scripts on CodeSandbox. https://codesandbox.io/s/zen-hodgkin-yfjs6?fontsize=14&hidenavigation=1&theme=dark
I figured it out. At least, one way of doing it, anyway.
First, I added a route to the ShowData component inside App.js, so that ShowData could get access to the router props. I also included exact to DataSource route, so it wouldn't be displayed when ShowData is rendered.
App.js
import './App.css';
import {Route} from 'react-router-dom'
import DataSource from './containers/DataSource'
import ShowData from './components/ShowData'
function App() {
return (
<div className="App">
<Route exact path='/' component={DataSource}/>
{/* 1. add Route to ShowData */}
<Route path='/showdata' component={ShowData}/>
</div>
);
}
export default App;
Inside DataSource, I modified the showDataHandler method to push the url I wanted, AND added a query param to it.
DataSource.js
import React, { Component } from 'react';
class DataSource extends Component{
state={
name:' John',
}
showDataHandler = ()=>{
this.props.history.push({
pathname:'/showdata',
query:this.state.name
})
}
render(){
return(
<div>
<button onClick={this.showDataHandler}>Go!</button>
</div>
)
}
}
export default DataSource;
And, finally, I modified ShowData to be a Class, so I could use state and have access to ComponentDidMount (I guess is also possible to use hooks here, if you don't want to change it to a Class).
Inside ComponentDidMount, I get the query param and update the state.
ShowData.js
import React, { Component } from 'react';
class ShowData extends Component{
state={
name:null
}
componentDidMount(){
this.setState({name:this.props.location.query})
}
render(){
return (
<div>
<p>{this.state.name}</p>
</div>
)
}
}
export default ShowData;
Now, when I click the button, the url changes to '/showdata' (and only '/showdata') and the prop name is displayed.
Hope this helps someone. Thanks.

React Adding two parents for a child component

State is the smart component which store all the states of child components
import React from 'react';
import TextArea from './Components/TextArea';
class State extends React.Component {
constructor(props) {
super(props);
this.state = {
name: ''
}
this.myChangeHandler = this.myChangeHandler.bind(this)
}
myChangeHandler = (event) => {
event.preventDefault();
this.setState({
[event.target.name]:event.target.value
});
}
render() {
return (
<div>
{/* Change code below this line */}
<TextArea name = {this.state.name}
myChangeHandler = {this.myChangeHandler}/>
{/* Change code above this line */}
</div>
);
}
};
export default State;
Now TextArea is the child component which share the input value to state.
import React from 'react';
import '../style.css';
class TextArea extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<input type="text" onChange= {this.props.myChangeHandler} name="name" value={this.props.name}></input>
<h1>Hello, my name is:{this.props.name} </h1>
</div>
);
}
};
export default TextArea;
There are several child components that send the data to state component.
so the app component is used to order the child component.
I need two parents for a child component. please see the image enter image description here
import React from 'react';
import { BrowserRouter, Route } from 'react-router-dom'
import './App.css';
import TextArea from './Components/TextArea'
function App() {
return (
<div className="App">
<BrowserRouter>
<Route path = "/new" component= {TextArea} />
</BrowserRouter>
</div>
);
}
export default App;
You can't have two "direct" parent for a single component but you can have multiple indirect parents.
Example:
App is the parent of State
State is the parent of TextArea
App is also a parent of TextArea but not a direct one.
This means that you can pass props (data and functions) from the top parent to all of his children.
If you need to pass a function from App to TextArea you need to pass it by State.
Here a tutorial on how to do it.
You can also use the Context and here's a tutorial on how to do it.

React Props not displaying data on UI

I am learning React
While working on Props, I have created a component and using that component in my index.jsx. But the values passed through props are not displayed on the UI.
usingprops.jsx
import React from 'react';
class UsingProps extends React.Component {
render() {
return (
<div>
<p>{this.props.headerProp}</p>
<p>{this.props.contentProp}</p>
</div>
);
}
}
export default UsingProps;
index.jsx
import React from 'react';
import UsingProps from './Props/UsingProps.jsx';
class App extends React.Component {
render() {
return (
<div>
<UsingProps />
</div>
);
}
}
const myElement = <App headerProp="Header from props!!!" contentProp="Content from props!!!" />;
ReactDOM.render(myElement, document.getElementById('root'));
export default App;
You are putting the headerProp on the App component, not the UsingProps component, which is where you are trying to access it. You need to revise it to this:
class App extends React.Component {
render() {
return (
<div>
<UsingProps headerProp="Header from props!!!" contentProp="Content from props!!!" />
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));

Passing an event method between siblings component in ReactJS

I am practicing in ReactJS and I have a trouble in passing a method between 2 sibling component. I have created React app which has 3 component: MainPage is the parent, FirstPage and SecondPage are two children. In FirstPage component there is a header with some text and SecondPage component has a button. My main goal is to pass the change-header method I defined in FirstPage, through MainPage component, to SecondPage component, so that when I click on the button that event method is fired.
I follow this tutorial https://medium.com/#ruthmpardee/passing-data-between-react-components-103ad82ebd17 to build my app. I also use react-router-dom in MainPage to display two page: one for FirstPage, another for SecondPage
Here is my FirstPage component:
import React from 'react'
class FirstPage extends React.Component{
constructor(props){
super(props)
this.state = {
msg: 'First page'
}
}
componentDidMount(){
this.props.callBack(this.changeText.bind(this))
}
render(){
return(
<div className = "first">
<h2>{this.state.msg}</h2>
</div>
)
}
changeText(){
{/* event method I defined to change the header text*/}
this.setState({msg: 'Text changed !!'})
this.props.history.push('/first')
}
}
export default FirstPage
and MainPage component:
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'
import React from 'react'
import FirstPage from '../component/FirstPage'
import SecondPage from '../component/SecondPage'
class MainPage extends React.Component{
constructor(props){
super(props);
this.state = {
func : null
}
}
myCallBack(callFunc){
this.setState({func: callFunc})
}
render(){
return(
<div className = "main">
<Router>
<Switch>
<Route path = "/first" render = {(props) => <FirstPage {...props} callBack = {this.myCallBack.bind(this)} />} />
<Route path = "/second" render = {(props) => <SecondPage {...props} myFunc = {this.state.func}/>} />
</Switch>
</Router>
</div>
)
}
}
export default MainPage
Follow the tutorial, I defined the property func inside MainPage state to store the event method from FirstPage. The myCallBack method is used to change the property of state. And I pass that method to the FirstPage by using callBack = {this.myCallBack.bind(this)}. So in the FirstPage, when the this.props.callBack(this.changeText.bind(this)) called, the event method will be stored into MainPage state
And finally my SecondPage commponent:
import React from 'react'
class SecondPage extends React.Component{
render(){
return(
<div className = "second">
<h2>Second page</h2>
<button onClick = {this.props.myFunc}> Click here to change</button>
</div>
)
}
}
export default SecondPage
App.js :
import React from 'react'
import MainPage from './component/MainPage'
function App() {
return (
<div className = "App">
<MainPage/>
</div>
);
}
export default App;
I simply pass the this.state.func that store my event to SecondPage through props. And I think this should be work: when I click the button, React will redirect to the 'FirstPage' and change the header field. But in fact when I clicked, nothing happen. Can anyone show me which part I did wrong ?
Hi Quang.
In order to do this, that is a possible approach you can follow:
For any shared data between two siblings of a parent component, we basically put that shared data in the closest parent, which in this case MainPage
Further:
The content you want show in FirstPage and change by SecondPage should exist in the state of the parent component MainPage
Pass the content to the FirstPage as a prop, such like content={this.state.content}
The function that changes the content changeText should be inside MainPage because it will be changing the specific state that is sent as a prop to the FirstPage
Function, when invoked by SecondPage should be changing the state of the MainPage, which is passed to the FirstPage as the content of the header.
Solution:
- FirstPage:
// Re-write as a functional component, because we won't be using lifecycle methods, thus, no need for it to be class component.
import React from 'react'
const FirstPage = (props) => (
<div className = "first">
<h2>{props.msg}</h2>
</div>
);
export default FirstPage
- SecondPage:
// Re-write as a functional component, because we won't be using lifecycle methods, thus, no need for it to be class component.
import React from 'react'
const SecondPage = (props) => (
<div className = "second">
<h2>Second page</h2>
<button onClick = {props.changeMsg}> Click here to change</button>
</div>
)
export default SecondPage
- MainPage:
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import React from "react";
import FirstPage from "../component/FirstPage";
import SecondPage from "../component/SecondPage";
class MainPage extends React.Component {
constructor(props) {
super(props);
this.state = {
msg: '', //added msg that is shown in FirstPage
};
}
// Added the function changeMsg to modify msg value in the state by the button onClick event from SecondPage.
changeMsg(){
{/* event method I defined to change the header text*/}
this.setState({ msg: 'Text changed !!' })
this.props.history.push('/first')
}
// Cleared some un-used functions.
// passed msg for FirstPage as a Prop
// passed changeMsg to SecondPage as a Prop
render() {
return (
<div className="main">
<Router>
<Switch>
<Route
path="/first"
render={props => (
<FirstPage {...props} msg={this.state.msg} />
)}
/>
<Route
path="/second"
render={props => (
<SecondPage {...props} changeMsg={this.changeMsg.bind(this)} />
)}
/>
</Switch>
</Router>
</div>
);
}
}
export default MainPage;

My mapStateToProps is not passing props to component

I am really newbie in Redux development. Just started two days ago
Before, I used props - state pattern but I am going to change some parts of state - props pattern to Redux.
First, I will show my codes. index.js:
import React from 'react';
import ReactDOM from 'react-dom';
import ItemList from './itemList';
import SearchList from './searchList';
import {Provider,connect} from 'react-redux';
import {store} from './storeReducer';
import {backToMain} from './reduxAction';
class App extends React.Component{
// not important some codes
render(){
return(
<div>
<section id="Search">
<form action="//localhost:80/ReactStudy/travelReduxApp/public/server/itemList.php" id="Search" className="search" method="GET" onSubmit={this.submitForm}>
<input ref={'search'} placeholder="search country, attraction names" type="text" name="search"/>
<button type="submit">SEARCH</button>
</form>
<p className={this.state.validateError.display}>
{this.state.validateError.type=='invalid value'?
'Only english letters are available in this input'
: (this.state.validateError.type=='not found')?
'Nothing was found with this keyword. Try again'
:'Empty text field'
}
</p>
</section>
<ItemContainer initializeSearch={this.initializeSearch} searchList={this.state.searchItem}/>
</div>
)
}
}
class ItemContainer extends React.Component{
state={
viewMain:true
}
//some codes
showMain=()=>{
this.setState({
viewMain:true
})
this.props.initializeSearch();
store.dispatch(backToMain());
}
render(){
console.log('Item container');
console.log(this.props);
return(
<section id="ItemContainer">
{
this.props.searchList!=''?
<SearchList searchList={this.props.searchList} mainVisible={this.state.viewMain}/>
:
<ItemList toggleView={this.toggleView} mainVisible={this.state.viewMain}/>
}
<button onClick={this.showMain}>Back to main</button>
</section>
)
}
}
const mapStateToProps =(state)=>{
console.log('working'); //it doesn't show it.
return{
visible:state.visible
}
};
const wrappedSearchList = connect(mapStateToProps,null)(ItemContainer);
const Root = () => (
<Provider store={store}>
<App/>
</Provider>
);
ReactDOM.render(
<Root/>,
document.getElementById('App')
)
reduxAction.js
export function backToMain(){
return{
type:'BACK_TO_MAIN'
}
}
storeReducer.js
import {createStore} from 'redux';
import {backToMain} from './reduxAction';
export const initialState = {
visible:true
}
export const store = createStore(viewMainReducer,initialState);
export function viewMainReducer(state=initialState,action){
switch(action.type){
case 'BACK_TO_MAIN':
console.log('Back..');
return{
...state,
visible:true
}
break;
default: return state;
}
}
I am really newbie in Redux so maybe I did not understand official document perfectly but I know mapStateToProps must pass state to the component as props. In my case, the component should be ItemContainer component.
Problem is when ItemContainer component is rendered.
If I check with
console.log(this.props)
in ItemContainer's render method , in console I see only initializeSearch and searchList are passed to the component. I cannot figure out why my visible property of initialState object is not passed to the component. What could be the reason? What should I do to pass visible property to ItemContainer component?
React / Redux: mapStateToProps not actually mapping state to props
read this thread but I just did not understand ...
Try using wrappedSearchList instead of ItemContainer
<wrappedSearchList initializeSearch={this.initializeSearch} searchList={this.state.searchItem}/>**strong text**

Resources