I am trying to render a component with already existing data from state (provided from redux-persist), the data in is state.login.user (i can see it in console.log in the mapStateToProps function that is being called and returns the dataObject : state.login.user but the dataObject is not being updated and because of that componentWillReceiveProps is not being called.
Can you point me to what im doing wrong?
import React from 'react'
import { connect } from 'react-redux'
import { ScrollView, AppRegistry, Component, Text, Image, View, Button, Modal, TouchableOpacity } from 'react-native'
import { GiftedForm, GiftedFormManager } from 'react-native-gifted-form'
// Styles
import styles from './Styles/MyProfileScreenStyles'
class MyProfileScreen extends React.Component {
constructor (props, context) {
const dataObject = {
profile: {
last_name : undefined,
}
}
super(props, context)
this.state = {
form: {
lastName: dataObject.profile.last_name,
tos: false
}
}
}
handleValueChange (values) {
this.setState({form: values})
}
componentWillReceiveProps (newProps) {
console.tron.log("componend will receive")
console.tron.log(newProps)
if (newProps.dataObject) {
this.setState({
dataObject: newProps.dataObject
})
}
}
render () {
const {lastName, tos, gender} = this.state.form
console.log('render', this.state.form)
return (
<View style={styles.container}>
<GiftedForm
formName='signupForm'
openModal={(route) => { this.props.navigator.push(route) }}
onValueChange={this.handleValueChange.bind(this)}
>
<GiftedForm.TextInputWidget
name='lastName'
title='Last name'
placeholder='Last name'
clearButtonMode='while-editing'
value={lastName}
/>
<GiftedForm.HiddenWidget name='tos' value={tos}/>
</GiftedForm>
</View>
)
}
}
const mapStateToProps = (state) => {
if( state.login.user !== null){
console.tron.log("test map state to props")
return {
dataObject: state.login.user
}
}
return {}
}
export default connect(mapStateToProps)(MyProfileScreen)
componentWillReceiveProps is only called when the props are updated after the component has rendered, before the component is re-rendered. You'll want to set the state inside your constructor as the props should already be there.
Related
I am having an issue where I'm trying to pass a function(updateEvents) via props from my App.js file to a NumberOfEvents.js file. I passed the same function to another component with no issues. However, when I try on the NumberOfEvents file, I get the following error:
Error image
Please help!!!
Here is the Parent:
import React, { Component } from 'react';
import EventList from './EventList';
import CitySearch from './CitySearch';
import NumberOfEvents from './NumberOfEvents';
import { extractLocations, getEvents } from './api';
import './nprogress.css';
import './App.css';
class App extends Component {
state = {
events: [],
locations: [],
numberOfEvents: 32
}
componentDidMount() {
this.mounted = true;
getEvents().then((events) => {
if (this.mounted) {
this.setState({
events: events.slice(0, this.state.numberOfEvents),
locations: extractLocations(events)
});
}
});
}
componentWillUnmount() {
this.mounted = false;
}
updateEvents = (location, eventCount) => {
this.mounted = true;
getEvents().then((events) => {
const locationEvents = (location === 'all')
? events
: events.filter((event) => event.location === location);
this.setState({
events: locationEvents,
numberOfEvents: eventCount,
});
});
};
render() {
return (
<div className="App">
<CitySearch
locations={this.state.locations} updateEvents={this.updateEvents} />
<EventList
events={this.state.events} />
<NumberOfEvents
numberOfEvents={this.state.numberOfEvents}
updateEvents={this.updateEvents} />
</div>
);
}
}
export default App;
And here is the Child:
import React, { Component } from 'react';
class NumberOfEvents extends Component {
state = {
numberOfEvents: 32
}
handleChange = (event) => {
const value = event.target.value;
this.setState({
numberOfEvents: value,
});
this.props.updateEvents('', value);
};
render() {
return (
<input
className="number"
value={this.state.numberOfEvents}
onChange={this.handleChange} />
)
}
}
export default NumberOfEvents;
Im not sure this will help ...In Your Parent Component , inside return statement when passing the updateEvents Prop, try passing it as arrow function like this ....
updateEvents={ () => this.updateEvents() } />
try adding a constructor to the child component
constructor(props) {
super(props);
this.state = {
numberOfEvents: 32
}
}
I am building an app with react / redux for managing Collection of Electronic equipment (=donations). I have several routes that their functionality - is similiar - fetching entity (it could be volunteer, donor etc) data and show it in a table.
the volunteer route:
import React, {Component} from 'react';
import { connect } from 'react-redux';
import { requestVolunteerData } from '../actions/entitiesAction';
import { volenteerColumns as columns } from '../utils/entitiesColumns/volenteerColumns';
import '../container/App.css';
import Table from '../components/Table/Table';
import Loading from '../components/Loading/Loading';
const mapStateToProps = state => {
return {
entities: state.requestEntitiesReducer.entities,
isPending: state.requestEntitiesReducer.isPending,
error: state.requestEntitiesReducer.error
}
}
const mapDispatchToProps = dispatch => {
return {
onRequestEntities: () => dispatch(requestVolunteerData())
}
}
class Volenteer extends Component{
componentDidMount () {
this.props.onRequestEntities();
}
render () {
const { entities, isPending} = this.props;
return isPending ?
<Loading />
:
(
<div className='tc'>
<h1 className='f2'>רשימת מתנדבים</h1>
<Table data={ entities } columns={ columns } />
</div>
);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Volenteer);
and a consumer route look like this:
import React, {Component} from 'react';
import { connect } from 'react-redux';
import { requestConsumerData } from '../actions/entitiesAction';
import { consumerColumns as columns } from '../utils/entitiesColumns/consumerColumns';
import '../container/App.css';
import Table from '../components/Table/Table';
import Loading from '../components/Loading/Loading';
const mapStateToProps = state => {
return {
entities: state.requestEntitiesReducer.entities,
isPending: state.requestEntitiesReducer.isPending,
error: state.requestEntitiesReducer.error
}
}
const mapDispatchToProps = dispatch => {
return {
onRequestEntities: () => dispatch(requestConsumerData())
}
}
class Consumer extends Component{
componentDidMount () {
this.props.onRequestEntities();
}
render () {
const { entities, isPending} = this.props;
return isPending ?
<Loading />
:
(
<div className='tc'>
<h1 className='f2'>רשימת נזקקים</h1>
<Table data={ entities } columns={ columns }/>
</div>
);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Consumer);
As you can see, they both have the same logic and the differences are:
the action
the Entity name for the h1 tag
the columns object
the data of course
so I tried to implement an HOC which look like this:
import React, {Component} from 'react';
import { connect } from 'react-redux';
import '../container/App.css';
import Table from '../Table/Table';
import Loading from '../Loading/Loading';
export default function WithEntity (EntityComponent, action, columns, name) {
const mapStateToProps = state => {
return {
isPending: state.requestEntitiesReducer.isPending,
entities: state.requestEntitiesReducer.entities,
error: state.requestEntitiesReducer.error
}
}
const mapDispatchToProps = dispatch => {
return {
onRequestEntities: () => dispatch(action)
}
}
class extends Component {
componentDidMount () {
this.props.onRequestEntities();
}
render() {
return (
<EntityComponent {...this.props} />
)
}
}
return connect(mapStateToProps, mapDispatchToProps)(EntityComponent);
}
and the volunteer should look like:
const volunteerHoc = WithEntity (volunteer, action, columns, name);
const consumerHoc = WithEntity (consumer, action, columns, name)
but I did not understand how to inject the Loading and Table components, and wht the name of the class inside the HOC should be-
should I use another HOC - something like WithLoader that receive the data from the first one and render the Loading and Table components with the proper data? just to mention that connect is HOC itself so I need to return the EntityComponent to the redux store :
return connect(mapStateToProps, mapDispatchToProps)(EntityComponent);
I Would appreciate any help
OK, I made it, the HOC takes a basic component, Expands the functionality (by adding methods and managing state for ex) and return a new (henanced) comp with this props.
lets create a simple volunteer comp:
import React, {Component} from 'react';
import { requestVolunteerData } from '../actions/entitiesAction';
import { volenteerColumns as columns } from '../utils/entitiesColumns/volenteerColumns';
import '../container/App.css';
import WithEntity from '../components/HOC/WithEntity.jsx';
import Table from '../components/Table/Table';
import Loading from '../components/Loading/Loading';
class Volenteer extends Component {
render() {
const { entities, isPending} = this.props;
return isPending ?
<Loading />
:
(
<div className='tc'>
<h1 className='f2'>רשימת מתנדבים</h1>
<Table data={ entities } columns={ columns } />
</div>
);
}
}
const VolenteerHOC = WithEntity(Volenteer, requestVolunteerData() );
export default VolenteerHOC;
now lets create the HOC WithEntity that managing the state and return the new cmop to redux state by connect:
import React, {Component} from 'react';
import { connect } from 'react-redux';
const WithEntity = (EntityComponent, action) => {
const mapStateToProps = state => {
return {
isPending: state.requestEntitiesReducer.isPending,
entities: state.requestEntitiesReducer.entities,
error: state.requestEntitiesReducer.error
}
}
const mapDispatchToProps = dispatch => {
return {
onRequestEntities: () => dispatch(action)
}
}
class NewCmoponent extends Component {
componentDidMount () {
this.props.onRequestEntities();
}
render() {
const { entities, isPending} = this.props;
return (
<EntityComponent {...this.props} />
)
}
}
return connect(mapStateToProps, mapDispatchToProps)(NewCmoponent );
}
export default WithEntity;
Now same route can be simply generated via this HOC.
check out this video:
https://www.youtube.com/watch?v=rsBQj6X7UK8
I have a web app that is suppose to show a list of notes made by the user on the dashboard if said list exist (that is if the user wrote any note at all). I wrote the reducer, the actions and I connected state and dispatch in order for it to work. But for some reason the notes created don't appear once in the dashboard when I write them, I already made sure that the ADD_NOTE action gets fired and that the reducer updates the data in redux, but in the dashboard component that data disappears.
This is my reducer.
export default (state = [], action) => {
switch (action.type) {
case "ADD_NOTE":
return [
...state,
action.note
];
case "REMOVE_NOTE":
return state.filter(({ id }) => id !== action.id);
default:
return state;
}
}
And those are my actions
import { v4 as uuidv4 } from 'uuid';
export const addNote = ({ title = "", body = ""} = {}) => ({
type: "ADD_NOTE",
note : {
title,
body,
id : uuidv4()
}
});
export const removeNote = ({ id } = {}) => ({
type: "REMOVE_NOTE",
id
});
This is the component that holds the create note form.
import React, { Component } from 'react';
class CreateNote extends React.Component{
constructor(props){
super(props);
this.onTitleChange = this.onTitleChange.bind(this);
this.onBodyChange = this.onBodyChange.bind(this);
this.onSubmit = this.onSubmit.bind(this);
this.state = {
title: "",
body: "",
error: ""
}
}
onTitleChange(e){
const title = e.target.value;
this.setState({ title });
}
onBodyChange(e){
const body = e.target.value;
this.setState({ body });
}
onSubmit(e){
e.preventDefault();
if(!this.state.title || !this.state.body){
this.setState({ error : "Please fill in all gaps"});
} else {
this.setState({ error: ""});
const data = { title: this.state.title, body: this.state.body}
this.props.onChange(data);
}
}
render(){
return(
<div>
{this.state.error && <p>{this.state.error}</p>}
<form onSubmit = {this.onSubmit}>
<label>Put a title for your note</label>
<input
placeholder="Title"
type="text"
value={this.state.title}
autoFocus
onChange = {this.onTitleChange}
/>
<label>Write your note</label>
<textarea
placeholder="Note"
value={this.state.body}
autoFocus
onChange = {this.onBodyChange}
/>
<input type="submit" value="Submit"/>
</form>
</div>
);
}
}
export default CreateNote;
And this is the component that fires the ADD_NOTE action
import React, { Component } from 'react';
import CreateNote from "./actions/CreateNote";
import Header from "./Header";
import { addNote } from "../actions/noteActions"
import { connect } from 'react-redux';
class Create extends React.Component{
constructor(props){
super(props);
this.eventHandler = this.eventHandler.bind(this);
}
eventHandler(data){
this.props.addNote(data);
this.props.history.push("/");
}
render(){
return (
<div>
<Header />
<CreateNote onChange = {this.eventHandler}/>
</div>
)
}
}
const mapDispatchToProps = (dispatch) => ({
addNote: (note) => dispatch(addNote(note))
});
export default connect(null, mapDispatchToProps)(Create);
And finally this is the dashboard component that renders the notes if they exist
import React from "react";
import ListItem from "./actions/ListItem";
import { connect } from 'react-redux';
const ListGroup = (props) => (
<div>
{
props.notes.length === 0 ? <h1>Write a note!</h1> :
(
props.notes.map((note) => {
return <ListItem key={note.id} {...note} />;
})
)
}
</div>
)
// The mapStateToProps does not connect with the local state, the action ADD_NOTE fires whenever
// the Create form is submited and the reducer updates the redux storage. So the problem lies here ?
// It could be that state.note is not definded but I don't know where should I define it if I have to,
// and apparently I don't have to ???????????????
const mapStateToProps = (state) => {
return {
notes: state.note
};
};
export default connect(mapStateToProps)(ListGroup);
When I try to run this it fires an error:
ListGroup.js?11a1:5 Uncaught TypeError: Cannot read property 'length' of undefined
at ListGroup (ListGroup.js?11a1:5)
Showing that the data that gets passed to the props is undefined. I'm thinking that it could be that state.note is not defined and I have to define it somewhere but I don't know if that's the case.
Use Hooks in functional components
connect() is only valid for class based components. For functional components you need to use hooks. Specifically the useSelector hook for reading redux state and useReducer to emit actions. You can find more instructions on redux hooks here https://react-redux.js.org/api/hooks#useselector
I am working on a simple react-redux project that gets information about movies from the OMDB api based on search term provided by the user. I am currently having trouble trying to get text typed into the searchbar to update the store value corresponding to the title of the film to search for. I'm fairly new to react and completely new to redux I've only finished one other redux project before and I set up my actions and reducers in the exact same way as last time but this time I'm running into "Uncaught TypeError: dispatch is not a function". This was not a problem I encountered in the previous project and my google searching has not been very helpful thus far.
I've searched this problem on google and only found a few results and none of them seem to be having the exact same issue as me, they involve using mapDispatchToProps which I'm not using inside of my connect function. Supposedly when you write a mapStateToProps like I have, dispatch should just be passed down as a prop to the connected component but whenever I try to access it I get the aforementioned "Uncaught TypeError: dispatch is not a function" error.
here is the index.js for my component
import { connect } from 'react-redux';
import MovieSearch from './MovieSearchContainer';
import {
updateSearchTerm,
getMovies
} from './movieSearchActions';
function mapStateToProps(state){
return {
title: state.movieSearch.title,
year: state.movieSearch.year,
plot: state.movieSearch.plot,
released: state.movieSearch.released,
runtime: state.movieSearch.runtime,
genre: state.movieSearch.genre,
plot: state.movieSearch.plot,
ratings: {
IMDB: state.movieSearch.ratings.IMDB,
Metascore: state.movieSearch.ratings.Metascore
},
posterUrl: state.movieSearch.posterUrl,
cachedMovies: state.movieSearch.cachedMovies
};
}
export default connect(mapStateToProps)(MovieSearch);
here is my action
export function updateSearchTerm(searchTerm){
return {
type: "UPDATE_SEARCH_TERM",
payload: { searchTerm }
}
}
here is my jsx component
import React from 'react';
import PropTypes from 'prop-types';
import {
updateSearchTerm,
getMovies
} from './movieSearchActions';
export default class MovieSearchContainer extends React.Component
{
constructor(props) {
super(props);
this.handleUpdateSearchTerm =
this.handleUpdateSearchTerm.bind(this);
}
handleUpdateSearchTerm(event){
const { dispatch } = this.props;
const { value } = event.target;
dispatch(updateSearchTerm(value));
}
render() {
return (
<div>
<h1 className='text-center'>Movie Finder</h1>
<input type='text' className='col-sm-11' id='searchBar'
onChange={ this.handleUpdateSearchTerm }/>
<button type='button' id='getMovies' className='col-sm-
1'>Go!</button>
</div>
)
}
}
MovieSearchContainer.propTypes = {
store: PropTypes.object
}
here is the reducer
export default function movieSearchReducer(state = defaultState,
action) {
const { type, payload } = action;
switch(type){
case 'UPDATE_SEARCH_TERM': {
return {
...state,
title: payload.title
}
}
default: {
return state;
}
}
}
I expect changes in the searchbar on the component on the page to be reflected in the redux store, but instead I just get this error
The dispatch prop is only available when you are directly interacting with the redux-store. When you define something like mapDispatchToProps() and pass it as the 2nd argument to connect(), dispatch, gets passed to mapDispatchToProps().
const mapDispatchToProps = (dispatch) => {
return{
actionCreator: (arg) => {
dispatch(actionCreator(arg))
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Component)
If you dont want to define mapDispatchToProps(), you can effectively bind your action-creators by passing in an object to connect() as the 2nd argument. This implicitly binds dispatch to the action-creators:
import React from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { updateSearchTerm, getMovies } from "./movieSearchActions";
class MovieSearchContainer extends React.Component {
constructor(props) {
super(props);
this.handleUpdateSearchTerm = this.handleUpdateSearchTerm.bind(this);
}
handleUpdateSearchTerm(event) {
const { value } = event.target;
this.props.updateSearchTerm(value);
}
render() {
console.log(this.props.movies);
return (
<div>
<h1 className="text-center">Movie Finder</h1>
<input
type="text"
className="col-sm-11"
id="searchBar"
onChange={this.handleUpdateSearchTerm}
/>
<button
type="button"
id="getMovies"
className="col-sm-
1"
>
Go!
</button>
</div>
);
}
}
MovieSearchContainer.propTypes = {
store: PropTypes.object
};
const mapStateToProps = state => {
return {
title: state.movieSearch.title,
year: state.movieSearch.year,
plot: state.movieSearch.plot,
released: state.movieSearch.released,
runtime: state.movieSearch.runtime,
genre: state.movieSearch.genre,
plot: state.movieSearch.plot,
ratings: {
IMDB: state.movieSearch.ratings.IMDB,
Metascore: state.movieSearch.ratings.Metascore
},
posterUrl: state.movieSearch.posterUrl,
cachedMovies: state.movieSearch.cachedMovies
};
};
export default connect(
mapStateToProps,
{
updateSearchTerm,
getMovies
}
)(MovieSearchContainer);
With that, you do not need to explicitly call dispatch to use your action-creator. Simply use this.props.nameOfActionCreator()
See sandbox for example: https://codesandbox.io/s/simple-redux-7s1c0
I think you should connect your component inside your jsx file. Then you can access with this.props.yourFunctionToDispatch
import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import {
updateSearchTerm,
getMovies
} from './movieSearchActions';
class MovieSearchContainer extends React.Component
{
constructor(props) {
super(props);
this.handleUpdateSearchTerm =
this.handleUpdateSearchTerm.bind(this);
}
handleUpdateSearchTerm(event){
const { dispatch } = this.props;
const { value } = event.target;
dispatch(updateSearchTerm(value));
}
render() {
return (
<div>
<h1 className='text-center'>Movie Finder</h1>
<input type='text' className='col-sm-11' id='searchBar'
onChange={ this.handleUpdateSearchTerm }/>
<button type='button' id='getMovies' className='col-sm-
1'>Go!</button>
</div>
)
}
}
const mapStateToProps = state => {
return {
title: state.movieSearch.title,
year: state.movieSearch.year,
plot: state.movieSearch.plot,
released: state.movieSearch.released,
runtime: state.movieSearch.runtime,
genre: state.movieSearch.genre,
plot: state.movieSearch.plot,
ratings: {
IMDB: state.movieSearch.ratings.IMDB,
Metascore: state.movieSearch.ratings.Metascore
},
posterUrl: state.movieSearch.posterUrl,
cachedMovies: state.movieSearch.cachedMovies
};
}
MovieSearchContainer.propTypes = {
store: PropTypes.object
}
export default connect(mapStateToProps, {yourFunctionToDispatch})(MovieSearchContainer);
Is there any way to send data from the component's state to HoC?
My component
import React, { Component } from 'react';
import withHandleError from './withHandleError';
class SendScreen extends Component {
contructor() {
super();
this.state = {
error: true
}
}
render() {
return (
<div> Test </div>
)
}
};
export default withHandleError(SendScreen)
My HoC component:
import React, { Component } from 'react';
import { ErrorScreen } from '../../ErrorScreen';
import { View } from 'react-native';
export default Cmp => {
return class extends Component {
render() {
const { ...rest } = this.props;
console.log(this.state.error) //// Cannot read property 'error' of null
if (error) {
return <ErrorScreen />
}
return <Cmp { ...rest } />
}
}
}
Is there any way to do this?
Is the only option is to provide props that must come to the SendScreen component from outside??
A parent isn't aware of child's state. While it can get an instance of a child with a ref and access state, it can't watch on state updates, the necessity to do this indicates design problem.
This is the case for lifting up the state. A parent needs to be notified that there was an error:
export default Cmp => {
return class extends Component {
this.state = {
error: false
}
onError() = () => this.setState({ error: true });
render() {
if (error) {
return <ErrorScreen />
}
return <Cmp onError={this.onError} { ...this.props } />
}
}
}
export default withHandleError(data)(SendScreen)
In data you can send the value you want to pass to HOC, and can access as prop.
I know I answer late, but my answer can help other people
It is very easy to do.
WrappedComponent
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import HocComponent from './HocComponent';
const propTypes = {
passToHOC: PropTypes.func,
};
class WrappedComponent extends Component {
constructor(props) {
super(props);
this.state = {
error: true,
};
}
componentDidMount() {
const {passToHOC} = this.props;
const {error} = this.state;
passToHOC(error); // <--- pass the <<error>> to the HOC component
}
render() {
return <div> Test </div>;
}
}
WrappedComponent.propTypes = propTypes;
export default HocComponent(WrappedComponent);
HOC Component
import React, {Component} from 'react';
export default WrappedComponent => {
return class extends Component {
constructor() {
super();
this.state = {
error: false,
};
}
doAnything = error => {
console.log(error); //<-- <<error === true>> from child component
this.setState({error});
};
render() {
const {error} = this.state;
if (error) {
return <div> ***error*** passed successfully</div>;
}
return <WrappedComponent {...this.props} passToHOC={this.doAnything} />;
}
};
};
React docs: https://reactjs.org/docs/lifting-state-up.html
import React, { Component } from 'react';
import withHandleError from './withHandleError';
class SendScreen extends Component {
contructor() {
super();
this.state = {
error: true
}
}
render() {
return (
<div state={...this.state}> Test </div>
)
}
};
export default withHandleError(SendScreen)
You can pass the state as a prop in your component.