When Reloading by Browser[F5] then,a state always will be undefined - javascript

state.firebase.profile always is undefined when I reload by browser.
Somehow, it goes well except for F5 as far as I can see.
I check by using console.log("TEST HERE"+ JSON.stringify(this.props.profile.name));.
Where should I modify it...
class ReagtTagSample extends Component {
constructor(props) {
super(props);
this.state = {
porco:""
tags: [{ id: 'Yugoslavia', text: 'Yugoslavia' }, { id: 'India', text: 'India' }],
suggestions: [
{ id: "England", text: "England" },
{ id: "Mexico", text: "Mexico" },
],
};
componentDidMount=()=>{
console.log("TEST HERE"+ JSON.stringify(this.props.profile.name));
}
handleAddition(tag) {
this.setState((state) => ({ tags: [...state.tags, tag] }));
}
handleDrag(tag, currPos, newPos) {
const tags = [...this.state.tags];
const newTags = tags.slice();
newTags.splice(currPos, 1);
newTags.splice(newPos, 0, tag);
this.setState({ tags: newTags });
}
//ommit
render() {
const { auth, authError, profile } = this.props;
return (
//ommit
const mapStateToProps = (state) => {
return {
auth: state.firebase.auth,
authError: state.auth.authError,
profile: state.firebase.profile,
};
};
const mapDispatchToProps = (dispatch) => {
return {
profileUpdate: (user) => dispatch(Update(user)),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Update);
Update= (user) => {
return (dispatch, getState, { getFirebase, getFirestore }) => {
const firestore = getFirestore(); 
const firebase = getFirebase();
const profile = getState().firebase.profile;
const authorId = getState().firebase.auth.uid;
firestore.collection('users').doc(authorId).set({
name: user.userName,
tags:user.tags,
}).then(() => {
dispatch({ type: 'PROFILE_UPDATE_SUCCESS' })
}).catch(err => {
dispatch({ type: 'PROFILE_UPDATE_ERROR', err })
})
}
}
I would like to use profile.name as default input name...
<div className="input-field">
<label htmlFor="userName">DisplayName</label>
<input
type="text"
id="userName"
value={this.state.userName}
onChange={this.handleChange}
/>

React state and props will be reset to their initial values when we reload the web app in browser using F5 or refresh button (because the app restarts as fresh).
The console log in componentDidMount prints undefined:
componentDidMount = () => {
console.log("TEST HERE" + JSON.stringify(this.props.profile.name));
// side node: you do not really need an arrow function here as it is a special
// lifecycle method. It will the `this` automatically binded with component instance
}
because, probably you are getting this.props.profile data through an API call. Hence, this.props.profile will receive its values asynchronously. You can see it on console log in componentDidUpdate lifecycle method.
Solution:
But if you want to set the default value of below input from this.props.profile.name, you can use either of these options:
Option 1: Using key and defaultValue. It will work because React components or elements re-render when their key is changed. And due to re-render it will read new defaultValue.
<input
key={this.props.profile.name}
defaultValue={this.props.profile.name}
type="text"
id="userName"
value={this.state.userName}
onChange={this.handleChange}
/>
Option 2: Set the userName in state when data is available in props:
componentDidUpdate(prevProps, prevState) {
if (this.props.profile.name !== prevProps.profile.name) {
this.setState({
userName: this.props.profile.name,
})
}
}
...
<input
type="text"
id="userName"
value={this.state.userName}
onChange={this.handleChange}
/>

Related

Cannot Read Properties Of Undefined Reading handleCategory

I'm trying to handle simple click action , I'm using class based components and I binded the method and used "this" as per documentation but an error occurs sayin cannot read properties of handleCategory , here's my code
import React from 'react';
import classes from '../css/Landing.module.css';
import mainClasses from '../../MainCss/MainClasses.module.css';
import { LOAD_CATEGORIES } from '../../graphql/queries';
export default class Landing extends React.Component {
constructor(props) {
super(props);
this.state = {
categories: [],
loading: true,
category: null,
};
this.handleCategory = this.handleCategory.bind(this);
}
async componentDidMount() {
const { client } = this.props;
const { data } = await client.query({ query: LOAD_CATEGORIES });
const { categories } = data;
this.setState((prevState) => ({ ...prevState, categories: categories, loading: false, category: categories[0]?.name }));
}
handleCategory(e) {
this.setState((prevState) => ({ ...prevState, category: e.target.value }));
}
renderCategory(category) {
return (
<option onClick={this.handleCategory} key={category.name} value={category.name}>
{category.name}
</option>
);
}
render() {
return (
<div className={`${classes.root} ${mainClasses.container} ${mainClasses.column}`}>
<div className={`${mainClasses.container} ${mainClasses.row}`}>
<label className={classes.categorylabel}>Choose Category</label>
{this.state.loading ? 'loading' : <select className={classes.categorymenu}>{this.state.categories.map(this.renderCategory)}</select>}
</div>
<div>{this.state.category}</div>
</div>
);
}
}
Convert the handleCategory method to an arrow function:
handleCategory = (e) => {
this.setState((prevState) => ({ ...prevState, category: e.target.value }));
}
By doing this you can remove the explicit bind from the constructor.
For more info read here.

Updating redux state by a local state of checkbox items

there are similiar questions in stackoverflow but I I did not find what I was looking for.
I have a donorDonationForm which is a class componenet that connected to the redux state. The porpuse of that componenet is to collect inormation about a person that want to donate electronics items. At this point, I want to save those items in an array (maybe with an object in the future).
my redux state save the donor info and the reducer looks like this:
import {CHANGE_INPUT_FIELD} from '../utils/constants';
const initialStateInputs = {
// update the state
donorFields: {
name: '',
phone: '',
area: '',
yeshuv: '',
address: ''
// dateOfOffer: ''
},
donationFields: {
// donorID: '',
// vulonteerID: '',
type: [],
quantity: 1,
status: 'NOT_HANDLED',
comments: ''
// lastDateHandled: ''
}
// }, items: [ //need to add quantity
// {id: 1, name: "LAPTOP", isChecked: false, label: 'מחשב'},
// {id: 2, name: "HEADPHONES", isChecked: false, label: 'אוזניות'},
// {id: 3, name: "OTHER", isChecked: false, label: 'אחר'},
// ]
}
export const donorDonationInputsReducer = ( state = initialStateInputs, action={} ) => {
switch(action.type) {
case CHANGE_INPUT_FIELD:
return Object.assign( {}, state,
{
donorFields : {...state.donorFields,...action.payload},
donationFields: {...state.donationFields,...action.payload},
// items : {...state.items,...action.payload},
// isChecked: action.payload
})
default:
return state;
}
}
As you can see the items is commented by now, and I am managing the state of the item in a local state, and that how the comp looks like:
import React, {Component} from 'react';
import { connect } from 'react-redux';
import { setInputField } from '../actions/formAction';
import CheckBox from '../components/CheckBox/CheckBox';
import FormInput from '../components/FormInput/FormInput';
import {selectAreasOptions_2} from '../utils/constants';
import "./form.css";
const mapStateToProps = (state) => {
return {
donorFields: state.donorDonationInputsReducer.donorFields,
donationFields: state.donorDonationInputsReducer.donationFields
}
}
const mapDispatchToProps = dispatch => {
return {
onInputChange: event => {
const {name, value} = event.target;
dispatch(setInputField( { [name]:value} ) )
}
}
}
class donorDonationForm extends Component {
constructor() {
super();
this.state = {
items: [
{id: 1, name: "LAPTOP", isChecked: false, label: 'מחשב'},
{id: 2, name: "HEADPHONES", isChecked: false, label: 'אוזניות'},
{id: 3, name: "OTHER", isChecked: false, label: 'אחר'},
]
,
type: []
}
}
handleCheckChieldElement = (event) => {
let {items, type} = this.state;
let arr = [];
items.forEach(item => {
if (item.name === event.target.value) {
item.isChecked = event.target.checked;
// console.log(`item.name :${item.name }`);
// console.log(`event.target.value :${event.target.value}`);
// console.log(`event.target.checked :${event.target.checked}`);
}
})
items.map(item => item.isChecked ? arr.push(item.name) : null)
this.setState({items: [...items], type: [...arr]});
}
onButtonSubmit = (event) => {
console.log(this.props.donorFields);
event.preventDefault();
fetch('http://localhost:8000/api/donor', {
method: 'post',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
...this.props.donorFields
})
})
.then(response => response.json())
.then(resp => console.log(resp))
.catch( err => console.log(err) )
}
// componentDidUpdate(prevProps, prevState) {
// const {items, type} = this.state;
// // const type = [];
// if (prevState.items !== items) {
// console.log('items state has changed');
// items.map (item => item.isChecked ?
// this.setState({type: [...type,item.name]}) : null)
// // if (item.isChecked) { type.push(item.name) } ;
// console.log(type);
// }
// }
render() {
console.log(this.state.items);
console.log(this.state.type);
const { onInputChange } = this.props;
return (
<div>
<h1 className="pt4"> פרטי תורם</h1>
<form className=" black-80 pt2" >
<section className=" grid-container">
<FormInput
id="name"
name="name"
type="text"
onInputChange={onInputChange}
label="שם "
required
/>
<FormInput
id="phone"
name="phone"
type="tel"
onInputChange={onInputChange}
label="מספר טלפון "
required
/>
<FormInput
id="address"
name="address"
type="text"
onInputChange={onInputChange}
label="כתובת "
required
/>
<FormInput
id="yeshuv"
name="yeshuv"
type="text"
onInputChange={onInputChange}
label="עיר "
required
/>
<FormInput
id="comments"
name="comments"
onInputChange={onInputChange}
label="הערות "
required
/>
<FormInput
id="area"
name="area"
onInputChange={onInputChange}
label="איזור "
select={selectAreasOptions_2}
/>
{/* type */}
<div className="measure-narrow">
<label htmlFor="type" className="f5 b db mb2">מעוניין לתרום
<span className="normal black-60"> *</span>
</label>
{
this.state.items.map( (item, i) => {
return (
<CheckBox
key={i}
onChange={this.handleCheckChieldElement}
checked={ item.isChecked }
value= {item.name}
label = {item.label}
/>
);
})
}
</div>
</section>
<input type="submit" value="שלח"
className="b bg-light-blue pa2 hover pointer"
onClick={this.onButtonSubmit}
/>
</form>
</div>
);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(donorDonationForm);
My main goal is that the type array - the final donation, will update the redux state before submitting this form. I tried with componentDidUpdate but didn't make it. What is the best way for tracking the checked items, updating the array and then update the type array which is the final donation in the redux state? should I do that in the onButtonSubmit method - before sending the data to the server (and thats way saving the looping over the items array for searching the checked elements) ?
Better approach would be do inside onButtonSubmit
Let me briefly explain the tasks:
inputChangeHandler to update this.state.items
Go with the final this.state.items value Array of items inside onButtonSubmit
After getting API response update the application level Redux state with Array of items.
Note: Dispatch the action. Reducer will update the Redux state. Following code will do this:
// Action
export const setItems = (data) => (dispatch) => {
dispatch({type: 'SET_ITEMS', payload: data})
}
// mapDispatchToProps
const mapDispatchToProps = (dispatch) =>
bindActionCreators(
{
setItems,
...others
},
dispatch
)
// onSubmitButton
onButtonSubmit = (event) => {
console.log(this.props.donorFields);
event.preventDefault();
fetch('http://localhost:8000/api/donor', {
method: 'post',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
...this.props.donorFields
})
})
.then(response => this.props.setItems(response.json())) // will update the state.
.then(resp => console.log(resp))
.catch( err => console.log(err) )
}
// Reducer
export const donorDonationInputsReducer = ( state = initialStateInputs, action={} ) => {
switch(action.type) {
case CHANGE_INPUT_FIELD:
return Object.assign( {}, state,
{
donorFields : {...state.donorFields,...action.payload},
donationFields: {...state.donationFields,...action.payload},
// items : {...state.items,...action.payload},
// isChecked: action.payload
})
case SET_ITEMS:
return {
...state,
items: action.payload
}
default:
return state;
}
}
That's it.
Happy Coding :)

Set initialVariables in Formik from state if it is in edit mode

I'm using Formik for validating some data. It works fine when it should create new entity, but there are problems when I want to edit an entity.
The edit mode must be activated from the state (this.state.edit === true), also the data of the entity is stored on the state, for example this.state.name has a string value there.
I put a console log in render, the problem is that the log is printed several times, the first time with empty string on this.sate.name and the value of this.state.edit is false. The next prints it is correct, this edit on true and name containing a value.
Here is the code:
import React from 'react';
import { Redirect } from 'react-router-dom';
import { Formik, Form, Field } from 'formik';
import { Input, Button, Label, Grid } from 'semantic-ui-react';
import { connect } from 'react-redux';
import * as Yup from 'yup';
import { Creators } from '../../../actions';
class CreateCompanyForm extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
name: '',
redirectCreate: false,
redirectEdit: false,
edit: false,
};
}
componentDidMount() {
const {
getCompany,
getCompanies,
location: { pathname },
} = this.props;
getCompanies({
name: '',
});
if (pathname.substring(11) !== 'create') {
getCompany(pathname.substring(16));
this.setState({
edit: true,
});
this.setState({
name: this.props.company.name,
});
}
}
handleSubmitCreate = e => {
e.preventDefault();
const { createCompany, getCompanies } = this.props;
createCompany(this.state);
this.setState({ redirectCreate: true });
getCompanies(this.props.query);
};
handleSubmit = values => {
const { createCompany, getCompanies } = this.props;
createCompany(values);
this.setState({ redirectCreate: true });
getCompanies(this.props.query);
};
handleSubmitEdit = e => {
e.preventDefault();
const { name } = this.state;
const { updateCompany } = this.props;
updateCompany(this.props.company._id, {
name,
});
this.setState({ redirectEdit: true });
};
render() {
let title = 'Create company';
let buttonName = 'Create';
let submit = this.handleSubmitCreate;
const { redirectCreate, redirectEdit } = this.state;
if (redirectCreate) {
return <Redirect to="/companies" />;
}
if (redirectEdit) {
return <Redirect to={`/companies/${this.props.company._id}`} />;
}
if (this.state.edit) {
title = 'Edit company';
buttonName = 'Edit';
submit = this.handleSubmitEdit;
}
console.log('state: ', this.state); // first time it is empty, next times it has data
let initialValues = {};
if (this.state.edit) {
initialValues = {
name: this.state.name,
};
} else {
initialValues = {
name: '',
};
}
const validationSchema = Yup.object({
name: Yup.string().required('This field is required'),
});
return (
<>
<Button type="submit" form="amazing">
create company
</Button>
<Formik
htmlFor="amazing"
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={values => this.handleSubmit(values)}>
{({ values, errors, touched, setValues, setFieldValue }) => (
<Form id="amazing">
<Grid>
<Grid.Column>
<Label>Company name</Label>
<Field name="name" as={Input} placeholder="write a name" />
<div>{touched.name && errors.name ? errors.name : null}</div>
</Grid.Column>
</Grid>
<Button type="submit" floated="right" form="amazing">
{buttonName} company
</Button>
</Form>
)}
</Formik>
</>
);
}
}
const mapStateToProps = state => ({
companies: state.companies.companies,
company: state.companies.selectedCompany,
query: state.companies.query,
});
const mapDispatchToProps = {
getCompanies: Creators.getCompaniesRequest,
createCompany: Creators.createCompanyRequest,
getCompany: Creators.getCompanyRequest,
updateCompany: Creators.updateCompanyRequest,
};
export default connect(mapStateToProps, mapDispatchToProps)(CreateCompanyForm);
I put the whole file here to have more context. Is it a way to set the initialValue of name with the value from this.state.name and put it inside the input field?
By default Formik does not re-render if the initial values change. You can pass enableReinitialize prop to Formik component to allow it.
As you said in the comment, first time it renders, it has no data, hence it does initialise Formik with empty values. With that prop, it should re-render if the initial values change.
https://formik.org/docs/api/formik#enablereinitialize-boolean

How to do a search in the state (react)

I am able to store a data array containing the login and pass objects. I created an input field in which I write what I want to find in the state. How can I filter the state and display only matching items?
Constructor
class List extends Component {
constructor(props) {
super(props);
this.state = {
data: [
{ login: "login", pass: "pass" },
{ login: "login2", pass: "pass2" }
],
login: "",
pass: "",
find: ""
};
Adding and displaying data
add(e) {
e.preventDefault();
this.setState({
[e.target.name]: e.target.value
});
}
show(e) {
e.preventDefault();
if (!this.state.login.length || !this.state.pass.length) {
return;
} else {
const newUser = {
login: this.state.login,
pass: this.state.pass
};
this.setState(state => ({
data: state.data.concat(newUser)
}));
}
}
Search
filterUsers(e) {
let { data } = this.state;
//console.log(this.temp.login);
this.setState({
find: e.currentTarget.value
});
}
Render
<input onInput={this.filterUsers.bind(this)} />
<div>{this.state.find}</div>
{this.state.data.map((val, index) => (
<>
<td>{val.login}</td>
<td>{val.pass}</td>
<br />
<div />
</>
))}
What property are you filtering on? Login?
I recommend creating a filtered data array in your state so you don't modify the original.
filterUsers(event)
{
let filteredArray = this.state.data.filter((user) =>
{
user.login === event.currentTarget.value;
//or if you want partial matches
//user.login.includes(event.currentTarget.value)
})
this.setState({
filteredList: filteredArray
});
}
If you want to have your state.data remained untouched you can filter out your search-term in the render method.
filterUsers(e) {
this.setState({
find: e.currentTarget.value
});
}
<input onInput={this.filterUsers.bind(this)} />
<div>{this.state.find}</div>
{this.state.data.filter(x => x.login.includes(this.state.find)).map((val, index) => (
<>
<td>{val.login}</td>
<td>{val.pass}</td>
<br />
<div />
</>
))}

Testing Apollo React Hooks with Jest & React Testing Library

I have a form that uses an Apollo mutation hook:
import React from 'react'
import { useFormik } from 'formik'
import { useLoginMutation } from '../generated'
const LoginContainer = () => {
const [loginMutation, { data, loading, error }] = useLoginMutation()
const formik = useFormik({
initialValues: {
email: '',
password: '',
},
onSubmit: values => {
loginMutation({
variables: {
input: {
email: String(values.email).trim(),
password: values.password,
},
},
})
},
})
return (
<form
onSubmit={event => {
event.preventDefault()
formik.handleSubmit(event)
}}
>
<input
data-testid="login-email-input"
name="email"
placeholder="Email address"
// label="Email"
required
type="email"
value={formik.values.email}
onChange={formik.handleChange}
/>
<input
data-testid="login-password-input"
name="password"
placeholder="password"
// label="Password"
required
type="password"
value={formik.values.password}
onChange={formik.handleChange}
/>
<button data-testid="login-submit-input" type="submit">
LOGIN
</button>
</form>
)
}
export default LoginContainer
I am trying to make sure the login mutation is called when the user fills in the form and clicks the submit button.
The test runs successfully sometimes and fails other times. I suspect that the loginMutation promise is not being resolved before the expect block is run.
The console also has the following warning:
Warning: An update to LoginContainer inside a test was not wrapped in act(...).
When testing, code that causes React state updates should be wrapped into act(...):
act(() => {
/* fire events that update state */
});
/* assert on the output */
Here is the test:
describe('login container', () => {
let loginMutationCalled = false
const variables = {
input: {
email: 'test#example.com',
password: '123',
},
}
const result = () => {
loginMutationCalled = true
return {
data: {
Login: {
accountId: '123',
},
},
}
}
const mocks = [
{
request: {
query: LOGIN_MUTATION,
variables,
},
result,
},
]
it('should call the login mutation', async () => {
await act(async () => {
const { findByTestId } = render(
<MockedProvider mocks={mocks} addTypename={false}>
<LoginContainer />
</MockedProvider>,
)
fireEvent.change(await findByTestId('login-email-input'), {
target: {
value: 'test#example.com',
},
})
fireEvent.change(await findByTestId('login-password-input'), {
target: {
value: '123',
},
})
await wait(async () =>
fireEvent.click(await findByTestId('login-submit-input')),
)
})
expect(loginMutationCalled).toBe(true)
})
})
How do I make sure the loginMutation promise has been resolved before running the assertions?
Please check out my GitHub https://github.com/Arit143/mytube-ui/blob/master/src/pages/List.spec.tsx for async act wait. Usually, I wrap the act and wait in a function and then just wait for it to fulfill. If you feel the login resolve is taking much time, you can increase the wait amount. Just have a look at the GitHub URL as an example and let me know in case you still face an issue.

Categories

Resources