The program flows as follows:
Home.js => Add.js => Detail.js => Repeat...
Home.js lists the posts.
Add.js is a page for entering the data required for a post. Move through stack navigation from Home.js
Detail.js will take over the data entered in Add.js as a parameter, show you the information of the post to be completed, and press the Done button to move to Home.js.
Home.js contains posts that have been completed.
If a post is created, eatObject is assigned an object from Detail.js.
Const { eatObject } = this.props.route?params || {};
In Home.js, state has an array of objects toEats.
Add eatObject to the object list
{Object.values(toEats).map(toEat =toEat.id toEat.idToEat key={toEat.id} {...toEat} deleteToEat={this._deleteToEat} />)}
Currently, the refresh button updates the 'Home.js' list whenever a post is added. But I want the list of posts to be updated automatically whenever 'Home.js' is uploaded.
I tried to call '_pushToEat' in 'componentDidMount', but 'eatObject' exists in 'render' so it seems impossible to hand it over to parameters.
I want to automatically call '_pushTooEat' when the components of 'Home.js' are uploaded.
Home.js
import React from "react";
import { View, Text, Button } from "react-native";
import ToEat from "./ToEat"
export default class Home extends React.Component {
state = {
toEats:{}
}
render() {
const { toEats } = this.state;
const { eatObject } = this.props.route?.params || {};
// error
// if (eatObject) {
// () => this._pushToEat(eatObject)
// }
return (
<View>
{Object.values(toEats).map(toEat => <ToEat key={toEat.id} {...toEat} deleteToEat={this._deleteToEat} />)}
<Button title="refresh" onPress={()=>this._pushToEat(eatObject)}/>
<View>
<Button title="Add" onPress={() => this.props.navigation.navigate("Add")} />
</View>
</View>
);
}
_deleteToEat = () => {
}
_pushToEat = (eatObject) => {
console.log("function call!")
console.log(eatObject)
this.setState(prevState => {
const newToEatObject = eatObject
const newState = {
...prevState,
toEats: {
...prevState.toEats,
...newToEatObject
}
}
return { ...newState };
})
}
}
Detail.js
import React from "react";
import { View, Text, Button } from "react-native";
import uuid from 'react-uuid'
import { connect } from 'react-redux';
import { inputId } from '../src/reducers/infoReducer';
class Detail extends React.Component {
render() {
const { name, dosage, note } = this.props.route.params;
return (
<View>
<Text>name: {name}</Text>
<Text>dosage: {dosage}</Text>
<Text>note: {note}</Text>
<Button
title="Done"
onPress={() =>
this.props.navigation.navigate(
"Home", {
eatObject: this._createToEat(uuid(), name, dosage)
})
}
/>
</View>
)
}
_createToEat = (id, name, dosage) => {
const ID = id
inputId(id)
const newToEatObject = {
[ID]: {
id: ID,
name: name,
dosage: dosage,
}
}
return newToEatObject;
}
}
function mapStateToProps(state) {
return {
state: state.infoReducer
};
}
function matchDispatchToProps(dispatch) {
return {
inputId: (id) => {
dispatch(inputId(id));
},
}
}
export default connect(mapStateToProps, matchDispatchToProps)(Detail);
I want your advice.
Thank you
Related
I'm trying to export a function declared in a react class component to use it in another file but that function uses the props of that class.
this is an exemple where i want to export the function handleDeleteAction()
import React, { Component } from 'react';
import { connect } from 'react-redux';
import swal from "sweetalert";
import { delete_user } from '../Actions';
class UserContainer extends Component {
constructor(props) {
super(props);
}
handleDeleteAction = async (event, { id, username }) => { // i want to export this function
await this.props.delete_user(id); //this is an action
};
renderDataTable = () => {
const { loading, data, pagination } = this.state;
return (
<DataTable
id="trace"
data={data ? data[0] : null}
totalRows={data ? data[1] : null}
columns={USER_COLUMNS} //the function is called inside the const USER_COLUMN
loading={loading}
/>
);
};
render() {
return (
<LayoutContainer renderDataTable={this.renderDataTable} title='Gestion des utilisateurs' />
);
}
export default connect(null, { loadUsersData, delete_user })(UserContainer);
and the function needs to be called in this file, it's fired when the user click on the button :
import { Tag, Button, Space } from 'antd';
import { AiFillDelete } from "react-icons/ai";
import { BsPencil } from "react-icons/bs";
import {handleDeleteAction} from '../user/UserContainer'
export const USER_COLUMNS = [
{
title: "Action", dataIndex: "id", key: "id", align: 'center', render: (text, record, index) => {
// console.log("text", text);
// console.log("record", record);
// console.log("index", index);
return (
<Space size={'small'} >
<Button type="primary" icon={<AiFillDelete />} id={record.id} onClick={(e) => handleDeleteAction(e, record)} size='large' danger />
</Space >
);
}
}
];
You can not export that function. That function is created whenever the constructor UserContainer creates an object and is local to the object created. You could create the function outside the class or even as a method of the class which you can export.
export async function handleDeleteAction(event, { id, username }){
return this.props.delete_user(id);
};
class UserContainer extends Component {
constructor(props) {
super(props);
}
handleDeleteAction = handleDeleteAction.bind(this);
renderDataTable = () => {
const { loading, data, pagination } = this.state;
return (
<DataTable
id="trace"
data={data ? data[0] : null}
totalRows={data ? data[1] : null}
columns={USER_COLUMNS} //the function is called inside the const USER_COLUMN
loading={loading}
/>
);
};
render() {
return (
<LayoutContainer renderDataTable={this.renderDataTable} title='Gestion des utilisateurs' />
);
}
}
However this doesn't mean you will get access to the props passed down to any object instantiated by the UserContainer class. The exported function will need to be bound to a component that does have access to that value passed down through that prop. Just like we do in the UserContainer class.
I don't know what is happening with the component im trying to map an array of objects from mongo here but it is no showing nothing to printInventory, but if i call to this.props.inventory and it takes all the data! What is happening?
printInventory = () => {
this.props.inventory.map((inventory) => {
return (
<CardInventario
cardTitle={inventory.name}
quantity={inventory.quantity}
description={inventory.price}
/>
)
})
}
in these function.
Next I going to show the actions and the reducers:
inventoryReducer:
import {TAKE_INVENTORY} from '../../types/inventoryTypes';
const INITIAL_STATE ={
inventory: []
}
function invetoryReducer(state = INITIAL_STATE,action){
switch (action.type) {
case TAKE_INVENTORY:
return {...state, inventory: action.payload}
break;
default:
return state;
break;
}
}
export default invetoryReducer;
and here is the inventoryActions:
import axios from 'axios'
import { TAKE_INVENTORY } from '../../types/inventoryTypes'
export const takeInventory = () => async (dispatch) =>{
const res = await axios.get('http://localhost:3001/inventory')
dispatch({
type: TAKE_INVENTORY,
payload: res.data
})
}
And the full component:
import React, { Component } from 'react'
import { Link } from 'react-router-dom'
import { connect } from 'react-redux'
import axios from 'axios'
/* Components and Styles */
import CardInventario from '../components/CardInventario'
import '../assets/styles/container/Stock.scss'
/* Redux */
import * as inventoryActions from '../actions/inventoryActions'
class Stock extends Component {
componentDidMount() {
this.props.takeInventory();
}
printInventory = () => {
this.props.inventory.map((inventory) => {
return (
<CardInventario
cardTitle={inventory.name}
quantity={inventory.quantity}
description={inventory.price}
/>
)
})
}
render() {
console.log(this.props.inventory)
return (
<>
<div className="container">
<div className="additem">
<Link to="/additem" className="additem__link">
<p className="link-add">
Añadir Item a Stock
</p>
</Link>
</div>
<div className="stock" key="stock">
{this.printInventory()}
</div>
</div>
</>
)
}
}
const mapStateToProps = (reducers) => {
return reducers.inventoryReducer;
}
export default connect(mapStateToProps, inventoryActions)(Stock);
First of all, please use the correct mapping.
inventoryReducer is not the one you're looking to map.
inventory inside that object is the one you want.
const mapStateToProps = (reducers) => {
return reducers.inventoryReducer.inventory;
}
Also if you get data in this.props.inventory, it should be related to duplicated keys
Please try the following
printInventory = () => {
this.props.inventory.map((inventory, index) => {
return (
<CardInventario
key={index}
cardTitle={inventory.name}
quantity={inventory.quantity}
description={inventory.price}
/>
)
})
}
If you don't have id, it is possible to use index instead (not recommended though)
printInventory = () => {
this.props.inventory.map((inventory) => {
return (
<CardInventario
key={inventory.id}
cardTitle={inventory.name}
quantity={inventory.quantity}
description={inventory.price}
/>
)
})
}
Background
I have an events page that uses JWT for authentication. I have a signout function in the header across the whole application. The basic premise, its a event display app with a many to many between users and events. Using Mongo if that matters.
I click on sign out on all pages and it works just fine. However on Landing component it throws this error.
TypeError: Cannot read property 'id' of null
Function.stateToProps [as mapToProps]
Path/client/src/components/EventActionButton.js:69
66 |
67 | const stateToProps = (state) => {
68 | return {
> 69 | userId: state.user.id
70 | }
71 | }
72 |
View compiled
▼ 19 stack frames were expanded.
mapToPropsProxy
Path/client/node_modules/react-redux/es/connect/wrapMapToProps.js:41
handleNewPropsAndNewState
Path/client/node_modules/react-redux/es/connect/selectorFactory.js:30
handleSubsequentCalls
Path/client/node_modules/react-redux/es/connect/selectorFactory.js:56
pureFinalPropsSelector
Path/client/node_modules/react-redux/es/connect/selectorFactory.js:63
runComponentSelector [as run]
Path/client/node_modules/react-redux/es/components/connectAdvanced.js:21
Connect.componentWillReceiveProps
Path/client/node_modules/react-redux/es/components/connectAdvanced.js:152
callComponentWillReceiveProps
Path/client/node_modules/react-dom/cjs/react-dom.development.js:12399
Header.js (where signout btn is)
import React , {Component} from 'react';
import {connect} from 'react-redux';
import {startSignOut} from '../actions/auth';
const Header = class Header extends Component {
handleSignOut = () => {
this.props.startSignOut();
}
render () {
return (
<div>
<span>circle</span>
<span>user</span>
<button onClick={() => this.handleSignOut()}>Sign out</button>
</div>
)
}
}
const mapDispatchToProps = (dispatch) => ({
startSignOut: () => startSignOut(dispatch)
});
export default connect(undefined, mapDispatchToProps)(Header);
Landing.js (Landing page at / URI)
import RequireAuth from './RequireAuth';
import {fetchEvents} from '../actions/event';
import {connect} from 'react-redux';
import EventItem from './EventItem';
import Header from './Header';
const EventDisplay = class EventDisplay extends Component {
componentDidMount = () => {
this.props.fetchEvents();
}
handleAddEvent = () => {
this.props.history.push('/addevent');
}
handleSignOut = () => {
this.props.startSignOut();
}
render() {
return (
<div>
<Header signOut={this.handleSignOut}/>
{
this.props.events.map((event, ind) => {
return <EventItem key={ind} history={this.props.history} index={ind + 1} event={event}/>
})
}
<button onClick={() => this.handleAddEvent()}>+</button>
</div>
)
}
}
const mapDispatchToProps = (dispatch) => ({
fetchEvents: (userId) => dispatch(fetchEvents(userId))
});
const mapStateToProps = (state) => ({
events: state.events
})
const connectedAuth = RequireAuth(connect(mapStateToProps, mapDispatchToProps)(EventDisplay));
export default connectedAuth;
EventItem.js (reusable component for each event)
import EventActionButton from './EventActionButton';
import RequireAuth from './RequireAuth';
const EventItem = (props) => {
const event = props.event;
const handleDetailView = () => {
props.history.push({
pathname: "/eventview",
state: { event: props.event }
});
}
return (
<div>
<div onClick={() => handleDetailView()}>
<span >{event.title}</span>
<span>{event.description}</span>
<span>{event.host.first_name + " " + event.host.last_name}</span>
<span>{event.date}</span>
<span>{event.capacity}</span>
</div>
<EventActionButton displayEvent={event}/>
</div>
)
}
export default RequireAuth(EventItem);
EventActionButton.js (Button in EventItem allowing users to attend or leave events)
import {connect} from 'react-redux';
import {editEventAssociation} from '../actions/event';
import RequireAuth from './RequireAuth';
const EventActionButton = class EventActionButton extends Component {
constructor(props) {
super(props);
this.state ={
edit: false,
joined: false
}
}
onClick = (e) => {
console.log(this.props);
var currentUser = this.props.userId;
if(this.state.edit) {
this.props.history.push('/addevent');
} else {
let userIds = this.props.displayEvent.users.map((user) => {
return user._id;
});
console.log(userIds);
if (this.state.joined) {
const modifiedUsers = userIds.filter((id) => id === this.props.userId);
userIds = modifiedUsers;
console.log(userIds);
} else {
userIds.push(this.props.userId);
console.log(userIds);
}
console.log("Joined " + this.state.joined);
console.log("Edited " + this.state.edit);
this.props.editEventAssociation(this.props.displayEvent._id, userIds, currentUser, this.state.joined);
}
}
componentWillMount = () => {
const event = this.props.displayEvent;
if (event.host._id === this.props.userId) {
this.setState({ edit: true, joined: false });
} else {
event.users.map((user) => {
if (user._id === this.props.userId) {
return this.setState({joined: true, edit: false});
} else {
return this.setState({join: false, edit: false});
}
});
}
}
render() {
const text = this.state.edit ? "Edit" : this.state.joined ? "Leave" : "Join";
return (
<>
<button value={text} onClick={this.onClick}>{text}</button>
</>
)
}
}
const stateToProps = (state) => {
return {
userId: state.user.id
}
}
const mapDispatchToProps = (dispatch) => {
return ({
editEventAssociation: (eventId, users, currentUser, joinedEvent) => dispatch(editEventAssociation(eventId, users, currentUser, joinedEvent))
})
}
const connectedRouterButton = connect(stateToProps, mapDispatchToProps)(EventActionButton);
export default RequireAuth(connectedRouterButton);
and just in case, this is the signout action.
export const startSignOut = (dispatch) => {
localStorage.removeItem('token');
localStorage.removeItem('user');
dispatch(removeUser());
dispatch(signOut());
}
where remove user removes currentUser in redux store and signOut removes auth token from redux store. Somehow Action button rerenders even though sign out redirects to /signin page using an HOC RequireAuth.
I dont understand how EventActionButton's re-render is triggered? Im not redirecting to Landing on /, Im redirecting to /signin
This makes sense because you're logging out so there's technically no "user" in "state.user", so instead of trying to access "state.user.id", assign a variable to "state.user" and in the constructor or wherever you're using the "id", do a check for if "state.user" is NULL and then assign the ID.
I have a simple post React project where I have a list of posts Posts.js and a full post template FullPost.js. In Posts.js, I pass the slug and id so that I can render the url.
For example, 'food' has a list of posts. I click on a post and it passes the 'slug: carrots' and 'id: 2' to 'food/carrots'. This 'id' is passed so it can find the correct pathname to my carrot post in my database i.e. 'mydatabase.com/food/posts/2/'
This works well, however when I refresh the page the 'slug' and 'id' that got passed to the FullPost component earlier disappears. Additionally, I would like to be able to access the URL directly, for example, if I type in 'www.mywebsite.com/food/carrots' it would load my carrot post. Currently my FullPost does not load when I refresh or when I go to the post's URL directly. Is there a way to load the post from these other entry points?
Below is my code:
My database:
posts:
0:
slug: "beans"
id: 1
1:
slug: "milk"
id: 2
2:
slug: "carrots"
id: 3
Posts.js: A list of posts.
import React, { Component } from 'react';
import axios from '../../../axiosPosts';
import Aux from '../../../hoc/Aux/Aux';
import classes from './Posts.css';
import Post from '../../../components/Post/Post';
class Posts extends Component {
state = {
posts: []
}
componentDidMount () {
this.getData(this.props.pathname, this.props.filter);
}
getData(pathname, filter) {
axios.get(pathname + '.json')
.then(response => {
const post = response.data.filter(({category}) => category === filter);
const updatedPosts = post.map(post => {
return {
...post
}
});
this.setState({
posts: updatedPosts
});
})
.catch(error => {
console.log(error);
});
}
postSelectedHandler = ( slug, id ) => {
const URL = `${this.props.match.url}/${slug}`;
this.props.history.push({pathname: URL, id: id });
}
render () {
let posts = <p style={{textAlign: 'center'}}>Whoops! Something went wrong.</p>;
if(!this.state.error) {
posts = this.state.posts.map(post => {
return (
<Post
key={post.id}
title={post.slug}
clicked={() => this.postSelectedHandler( post.slug, post.id )} />
);
});
};
return (
<Aux>
<div className={classes.PostList}>{posts}</div>
</Aux>
)
}
}
export default Posts;
FullPost.js (Actual Post)
import React, { Component } from 'react';
import axios from '../../../axiosPosts';
import classes from './FullPost.css';
class FullPost extends Component {
state = {
loadedPost: null,
}
componentDidMount () {
const { location: {id} } = this.props;
this.loadData(id);
}
loadData(id) {
if ( id ) {
if ( !this.state.loadedPost || (this.state.loadedPost && this.state.loadedPost.id !== +id) ) {
axios.get( '/food/posts/' + (id - 1) + '.json' )
.then( response => {
this.setState( { loadedPost: response.data, locationId: id } );
});
}
}
}
render () {
let post = <p style={{ textAlign: 'center' }}>Please select a Post!</p>;
const { location: {id} } = this.props;
if ( id ) {
post = <p style={{ textAlign: 'center' }}>Loading...!</p>;
}
if ( this.state.loadedPost ) {
post = (
<div className={classes.FullPost}>
<h1 className={classes.Title}>{this.state.loadedPost.title}</h1>
</div>
);
}
return post;
}
}
export default FullPost;
App.js: Main js
import React, { Component } from 'react';
import { Route, Switch } from 'react-router-dom';
import classes from './App.css';
import Layout from './hoc/Layout/Layout';
import Posts from './pages/Posts/Posts';
import FullPost from './pages/FullPost/FullPost';
class App extends Component {
constructor(props) {
super(props);
console.log('[App.js] Inside Constructor', props);
}
render() {
return (
<div className={classes.App}>
<Layout>
<Switch>
<Route path="/food/" exact component={Posts} />
<Route path="/food/:slug" exact component={FullPost} />
<Route render={() => <h1>Whoops! What you're looking for isn't here anymore.</h1>} />
</Switch>
</Layout>
</div>
);
}
}
export default App;
You have to use something like React Router.
In a nutshell your issue is that there is no URL matching logic in your code (everything would work the same even if you didn't change the URL at all).
I am near the end of creating my application.
So it is for banks accounts where they ask you to give the first letter of your password, then for example fourth, etc.
I'm tired of counting on my own so I created this app.
But there is the last bug that I don't know how to fix.
So when I press "1" I get "1 - H", and then when I press "4" I want to get:
"1 - H" (clicked before)
"4 - X" (clicked just now)
but instead, I get:
"4 - X" (clicked just now)
"4 - X" (clicked just now)
So it is caused by the way handleResults() function works inside my Input component, but for now it is my only concept how to approach this...
import React, { Component } from 'react';
import TextField from 'material-ui/TextField';
import './style.css';
import Buttons from '../Buttons';
import Results from '../Results';
class Input extends Component {
constructor(props) {
super(props);
this.state = {
password: 'Hh9Xzke2ayzcEUPHuIfS',
selectedButtons: [],
};
this.handleButtonSelectTwo = this.handleButtonSelectTwo.bind(this);
}
handleInputChange(pass) {
this.setState({ password: pass });
}
handleButtonSelectTwo(selected) {
this.setState({
selectedButtons: [...this.state.selectedButtons, selected],
});
}
handleResults() {
return this.state.selectedButtons.map(el => (
<Results key={el} appState={this.state} />
));
}
render() {
return (
<div>
<div className="Input-textfield">
<TextField
hintText="Paste your password here to begin"
value={this.state.password}
onChange={event => this.handleInputChange(event.target.value)}
/>
</div>
<div>
<Buttons
handleButtonSelectOne={this.handleButtonSelectTwo}
array={this.state.password.length}
/>
{this.handleResults()}
</div>
</div>
);
}
}
export default Input;
and here is Results component code:
import React, { Component } from 'react';
import _ from 'lodash';
import Avatar from 'material-ui/Avatar';
import List from 'material-ui/List/List';
import ListItem from 'material-ui/List/ListItem';
import './style.css';
const style = {
avatarList: {
position: 'relative',
left: -40,
},
avatarSecond: {
position: 'relative',
top: -40,
left: 40,
},
};
class Results extends Component {
resultsEngine(arg) {
const { selectedButtons, password } = this.props.appState;
const passwordArray = password.split('').map(el => el);
const lastSelectedButton = _.last(selectedButtons);
const passwordString = passwordArray[_.last(selectedButtons) - 1];
if (arg === 0) {
return lastSelectedButton;
}
if (arg === 1) {
return passwordString;
}
return null;
}
render() {
if (this.props.appState.selectedButtons.length > 0) {
return (
<div className="test">
<List style={style.avatarList}>
<ListItem
disabled
leftAvatar={<Avatar>{this.resultsEngine(0)}</Avatar>}
/>
<ListItem
style={style.avatarSecond}
disabled
leftAvatar={<Avatar>{this.resultsEngine(1)}</Avatar>}
/>
</List>
</div>
);
}
return <div />;
}
}
export default Results;
Anyone has an idea how should I change my code inside handleResults() function to achieve my goal? Any help with solving that problem will be much appreciated.
Buttons component code:
import React from 'react';
import OneButton from '../OneButton';
const Buttons = props => {
const arrayFromInput = props.array;
const buttonsArray = [];
for (let i = 1; i <= arrayFromInput; i++) {
buttonsArray.push(i);
}
const handleButtonSelectZero = props.handleButtonSelectOne;
const allButtons = buttonsArray.map(el => (
<OneButton key={el} el={el} onClick={handleButtonSelectZero} />
));
if (arrayFromInput > 0) {
return <div>{allButtons}</div>;
}
return <div />;
};
export default Buttons;
And OneButton code:
import React, { Component } from 'react';
import RaisedButton from 'material-ui/RaisedButton';
const style = {
button: {
margin: 2,
padding: 0,
minWidth: 1,
},
};
class OneButton extends Component {
constructor() {
super();
this.state = { disabled: false };
}
handleClick() {
this.setState({ disabled: !this.state.disabled });
this.props.onClick(this.props.el);
}
render() {
return (
<RaisedButton
disabled={this.state.disabled}
key={this.props.el}
label={this.props.el}
style={style.button}
onClick={() => this.handleClick()}
/>
);
}
}
export default OneButton;
In your resultsEngine function in the Results component you are specifying that you always want the _.last(selectedButtons) to be used. This is what it is doing, hence you always see the last button clicked. What you actually want is the index of that iteration to show.
const lastSelectedButton = selectedButtons[this.props.index];
const passwordString = passwordArray[selectedButtons[this.props.index]];
To get an index you have to create and pass one in, so create it when you map over the selected Buttons in the handleResults function in your Input component.
handleResults() {
return this.state.selectedButtons.map((el, index) => (
<Results key={el} appState={this.state} index={index} />
));
}