I am building an isomorphic React app. The workflow I am currently working through is :
User navigates to the /questions route which makes the API call server side and loads the data on the page. This calls the renderData() function like it should and loads all the questions for the user to see.
User clicks add button to add new question and a modal pops up for a user to enter in the form fields and create a new question.
With every change in the modal, the renderData() function is getting called (which it shouldn't). When the user clicks the Create Question button, the renderData() function is also getting called is throwing an error because the state changes.
I can't pinpoint why the renderData() function is getting called every single time anything happens in the modal. Any ideas as to why this is happening and how to avoid it?
Main component :
import React, { Component, PropTypes } from 'react';
import withStyles from 'isomorphic-style-loader/lib/withStyles';
import s from './QuestionsPage.scss';
import QuestionStore from '../../stores/QuestionStore';
import QuestionActions from '../../actions/QuestionActions';
import Loader from 'react-loader';
import QuestionItem from '../../components/QuestionsPage/QuestionItem';
import FloatButton from '../../components/UI/FloatButton';
import AddQuestionModal from '../../components/QuestionsPage/AddQuestionModal';
const title = 'Questions';
class QuestionsPage extends Component {
constructor(props) {
super(props);
this.state = QuestionStore.getState();
this.onChange = this.onChange.bind(this);
this.openModal = this.openModal.bind(this);
this.closeMOdal = this.closeModal.bind(this);
}
static contextTypes = {
onSetTitle: PropTypes.func.isRequired,
};
componentWillMount() {
this.context.onSetTitle(title);
QuestionStore.listen(this.onChange);
}
componentWillUnmount() {
QuestionStore.unlisten(this.onChange);
}
onChange(state) {
this.setState(state);
}
openModal = () => {
this.setState({ modalIsOpen: true});
}
closeModal = () => {
this.setState({ modalIsOpen: false});
}
createQuestion = () => {
const date = new Date();
const q = this.state.question;
q.createdAt = date;
this.setState({ question : q });
QuestionStore.createQuestion(this.state.question);
}
textChange = (val) => {
const q = this.state.question;
q.text = val;
this.setState({ question : q });
}
answerChange = (val) => {
const q = this.state.question;
q.answer = val;
this.setState({ question : q });
}
tagChange = (val) => {
const q = this.state.question;
q.tag = val;
this.setState({ question : q });
}
companyChange = (val) => {
const q = this.state.question;
q.company = val;
this.setState({ question : q });
}
renderData() {
return this.state.data.map((data) => {
return (
<QuestionItem key={data.id} data={data} />
)
})
}
render() {
return (
<div className={s.root}>
<div className={s.container}>
<h1>{title}</h1>
<div>
<Loader loaded={this.state.loaded} />
<FloatButton openModal={this.openModal}/>
<AddQuestionModal
open = {this.state.modalIsOpen}
close = {this.closeModal}
createQuestion = {this.createQuestion}
changeText = {this.textChange}
changeAnswer = {this.answerChange}
changeTag = {this.tagChange}
changeCompany = {this.companyChange}
/>
{ this.renderData() }
</div>
</div>
</div>
);
}
}
export default withStyles(QuestionsPage, s);
Modal Component :
import React, { Component, PropTypes } from 'react';
import QuestionStore from '../../stores/QuestionStore';
import QuestionActions from '../../actions/QuestionActions';
import Modal from 'react-modal';
import TextInput from '../UI/TextInput';
import Button from '../UI/Button';
class AddQuestionModal extends Component {
createQuestion = () => {
this.props.createQuestion();
}
closeModal = () => {
this.props.close();
}
changeText = (val) => {
this.props.changeText(val);
}
changeAnswer = (val) => {
this.props.changeAnswer(val);
}
changeTag = (val) => {
this.props.changeTag(val);
}
changeCompany = (val) => {
this.props.changeCompany(val);
}
render() {
return (
<Modal
isOpen={this.props.open}
onRequestClose={this.closeModal} >
<TextInput
hintText="Question"
change={this.changeText} />
<TextInput
hintText="Answer"
change={this.changeAnswer} />
<TextInput
hintText="Tag"
change={this.changeTag} />
<TextInput
hintText="Company"
change={this.changeCompany} />
<Button label="Create Question" onSubmit={this.createQuestion} disabled={false}/>
<Button label="Cancel" onSubmit={this.closeModal} disabled={false}/>
</Modal>
);
}
}
export default AddQuestionModal;
On click of
It's happening because every change causes you to call the setState method and change the state of the main component. React will call the render function for a component every time it detects its state changing.
The onChange event on your inputs are bound to methods on your main component
Each method calls setState
This triggers a call to render
This triggers a call to renderData
React allows you to change this by overriding a shouldComponentUpdate function. By default, this function always returns true, which will cause the render method to be called. You can change it so that only certain changes to the state trigger a redirect by comparing the new state with the old state.
Related
I have no idea How to store the react js state into localstorage.
import React, { Component } from 'react'
import './App.css';
import { auth,createUserProfileDocument } from './firebase/firebase.utils'
import { TodoForm } from './components/TodoForm/TodoForm.component'
import {TodoList} from './components/TodoList/TodoList.component'
import {Footer} from './components/footer/footer.component'
import Header from '../src/components/header/header.component'
import {Redirect} from 'react-router-dom'
import {connect} from 'react-redux'
import {setCurrentUser} from './redux/user/user.actions'
export class App extends Component {
constructor(props) {
super(props)
this.input=React.createRef()
this.state = {
todos:[
{id:0, content:'Welcome Sir!',isCompleted:null},
]
}
}
todoDelete = (id) =>{
const todos = this.state.todos.filter(todo => {
return todo.id !== id
})
this.setState({
todos
})
}
toDoComplete = (id,isCompleted) =>{
console.log(isCompleted)
var todos = [...this.state.todos];
var index = todos.findIndex(obj => obj.id === id);
todos[index].isCompleted = !isCompleted;
this.setState({todos});
console.log(isCompleted)
}
addTODO = (todo) =>{
todo.id = Math.random()
todo.isCompleted = true
let todos = [...this.state.todos, todo]
this.setState({
todos
})
}
unsubscribeFromAuth = null;
componentDidMount() {
const { setCurrentUser } = this.props;
this.unsubscribeFromAuth = auth.onAuthStateChanged(async userAuth => {
if (userAuth) {
const userRef = await createUserProfileDocument(userAuth);
userRef.onSnapshot(snapShot => {
setCurrentUser({
id: snapShot.id,
...snapShot.data()
});
});
}
setCurrentUser(userAuth);
});
}
componentWillUnmount() {
this.unsubscribeFromAuth();
}
render() {
return (
<div className='App'>
<Header />
<TodoForm addTODO={this.addTODO} />
<TodoList
todos={this.state.todos}
todoDelete={ this.todoDelete}
toDoComplete={ this.toDoComplete}
/>
<Footer/>
</div>
)
}
}
const mapStateToProps = ({ user }) => ({
currentUser: user.currentUser
});
const mapDispatchToProps = dispatch => ({
setCurrentUser: user => dispatch(setCurrentUser(user))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);
in my input Form
import './TodoForm.style.css'
export class TodoForm extends Component {
constructor(props) {
super(props)
this.state = {
content : ''
}
}
handleChange = (e) =>{
this.setState({
content: e.target.value
})
}
handleSubmit =(e) =>{
e.preventDefault();
this.props.addTODO(this.state);
this.setState({
content: ''
})
}
render() {
return (
<div className='inputTask'>
<form onSubmit={ this.handleSubmit}>
<input
className="textBox"
type='text'
onChange={ this.handleChange}
value={this.state.content}
placeholder='what you want to do ...'
/>
</form>
</div>
)
}
}
export default TodoForm
I have no idea How to store the react js state into localstorage.
i searched on internet but unable to find the exact solution all the codes that i think is necessary post.
You can use reactLocalStorage to save any data in local storage
import {reactLocalStorage} from 'reactjs-localstorage';
reactLocalStorage.set('var', true);
reactLocalStorage.get('var', true);
reactLocalStorage.setObject('var', {'test': 'test'});
reactLocalStorage.getObject('var');
reactLocalStorage.remove('var');
reactLocalStorage.clear();
Read out the localStorage item in the componentDidMount callback. Simply read the item you want to get, check if it exists and parse it to a usable object, array or datatype that need. Then set the state with the results gotten from the storage.
And to store it, simply handle it in an event handler or helper method to update both the state and the localStorage item.
class ExampleComponent extends Component {
constructor() {
super();
this.state = {
something: {
foo: 'bar'
}
}
}
componentDidMount() {
const storedState = localStorage.getItem('state');
if (storedState !== null) {
const parsedState = JSON.parse(storedState);
this.setState({ something: parsedState });
}
}
clickHandler = (event) => {
const value = event.target.value;
const stringifiedValue = JSON.stringify(value);
localStorage.setItem('state', stringifiedValue);
this.setState({ something: value });
}
render() {
return (
<button onClick={clickHandler} value={this.state.something}>Click me</button>
);
}
}
Set data in localStorage
key-value pair :
localStorage.setItem('key_name',"value");
object
localStorage.setItem('key_name', JSON.stringify(object));
Remove data from localStorage
localStorage.removeItem('key_name');
Get data from localStorage
let data = localStorage.getItem('key_name');
object :
let data = JSON.parse(localStorage.getItem('key_name'));
clear localStorage (delete all data)
localStorage.clear();
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
I am quite new to React and in this circumstance I feel like I should be using a lifecycle hook, but I'm not sure which. In the code below I am making a call to the API and retrieving the list of users object which contains another list of things they own called members. Like so:
When clicking the button 'Unlink' I am then storing the reference in state and posting that back up to the API to unlink the member:
import React, { Fragment } from 'react';
import FormScene from 'nwiadmin/scenes/form';
import Button from 'nwiadmin/components/button';
import { formatDate } from 'nwiadmin/utility/formatters';
import Modal from 'nwiadmin/components/modal';
import ModalActions from 'nwiadmin/components/modal/modalactions';
import SingleBusinesses from 'app/components/businesses';
let businessRef = '';
class UserBusinessSingleScene extends FormScene {
/* eslint-disable class-methods-use-this */
setInitialState(props) {
const pendingData = {
...props.data,
};
const reference = businessRef;
const isVisible = false;
this.setReferenceTarget = this.setReferenceTarget.bind(this);
return {
pendingData,
reference,
isVisible,
};
}
shouldComponentUpdate(nextProps, nextState) {
console.log('should cmp update', nextProps, nextState);
return true;
}
UNSAFE_componentWillUpdate(nextProps, nextState) {
console.log('cmp will update', nextProps, nextState);
}
componentDidUpdate(prevProps, prevState) {
console.log('cmp did update', prevProps, prevState);
}
setLabel(data) {
return data.name;
}
setMethod() {
return 'post';
}
setRemote() {
return `businesses/unclaim?reference=${this.state.reference}`;
}
modalToggleHander() {
this.setState(prevState => ({
isVisible: !prevState.isVisible,
}));
}
setReferenceTarget() {
this.setState({ reference: businessRef });
}
render() {
console.log(this.state.reference);
return (
<Fragment>
{this.state.pendingData.members.map(business => (
<SingleBusinesses
key={business.reference}
name={business.name}
reference={business.reference}
claimed={`Claimed on: ${formatDate(business.created_at)}`}
unlink={
<Button
onClick={() => {
businessRef = business.reference;
this.setReferenceTarget();
this.modalToggleHander();
}}
>
Unlink User
</Button>
}
/>
))}
<Modal isOpen={this.state.isVisible} title="Are you sure you want to unlink the user?">
<ModalActions
submit={() => this.submit()}
submitText="Unlink User"
cancel={() => this.modalToggleHander()}
/>
</Modal>
</Fragment>
);
}
}
export default UserBusinessSingleScene;
Once unlinked I get a Uncaught TypeError: Cannot read property 'map' of undefined error. I'm not 100% sure why but I feel like it's something to do with the state and that I should be setting the state after unlinking? Any help is appreciated.
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 React app like:
Main.js-
import React, { Component } from 'react';
import _ from 'underscore';
import ApplicationsButtons from '../components/ApplicationsButtons';
let applications_url = 'http://127.0.0.1:8889/api/applications'
export default class Main extends Component {
constructor(props) {
super(props);
this.state = {applications: [], selected_app: 1};
this.updateSelectedApp = this.updateSelectedApp.bind(this);
}
componentDidMount() {
let self = this;
$.ajax({
url: applications_url,
method: 'GET',
success: function(data) {
console.log(data);
let objects = data.objects;
let apps = objects.map(function(object) {
return {name: object.name, id: object.id};
});
console.log(apps);
self.setState({applications: apps});
}
});
}
updateSelectedApp(id) {
this.setState({selected_app: id});
}
render() {
return (
<div>
{this.state.selected_app}
<ApplicationsButtons apps={this.state.applications} />
</div>
);
}
}
ApplicationsButtons.js-
import React, { Component } from 'react';
export default class ApplicationsButtons extends Component {
render() {
var buttons = null;
let apps = this.props.apps;
let clickHandler = this.props.clickHandler;
if (apps.length > 0) {
buttons = apps.map(function(app) {
return (<button key={app.id}>{app.name} - {app.id}</button>);
// return (<button onClick={clickHandler.apply(null, app.id)} key={app.id}>{app.name} - {app.id}</button>);
});
}
return (
<div>
{buttons}
</div>
);
}
}
I want to pass an onClick to the buttons that will change the currently selected app. Somehow, I just got my first infinite loop in React ("setState has just ran 20000 times"). Apparently, when I tried to pass the event handler to be called on click, I told it to keep calling it.
The onClick function should change state.selected_app for the Main component, based on the id for the button that was clicked.
You are not passing the handler as prop.
Here's what you should do:
render() {
return (
<div>
{this.state.selected_app}
<ApplicationsButtons
apps={this.state.applications}
handleClick={this.updateSelectedApp}
/>
</div>
);
}
And in ApplicationButtons:
render() {
var buttons = null;
let apps = this.props.apps;
let clickHandler = this.props.handleClick;
if (apps.length > 0) {
buttons = apps.map(app =>
<button key={app.id} onClick={() => clickHandler(app.id)}>{app.name} - {app.id}</button>);
);
}
return (
<div>
{buttons}
</div>
);
}