I am using React useState to create an object in state.
This is updated to an object of data after a successful API call.
I have a form that can change this state, but I also have a cancel button.
How can i restore this state to its initial values (after API call) when cancel is clicked?
Should i create another state variable and store initial state there and then update my state based on that?
const [basePosition, setBasePosition] = useState({});
const [position, setPosition] = useState({
id: '',
title: '',
description: '',
authoredBy: '',
createdDate: '',
lastUpdatedBy: '',
lastUpdateDate: '',
sliderResponses: [],
tileResponses: [{}],
template: {}
});```
const initialState = {
id: '',
title: '',
};
const Test = () => {
const [position, setPosition] = useState(initialState);
return <>
...form
<button onClick={() => setPosition(initialState)}>Reset</button>
</>;
};
Don't create another state variable just to store initial state as it will cause another re render instead when your component is mounted then intialize your initial state object:
let initialState = null;
React.useEffect(() => {
initialState = position;
},[])
When you want to reset to initial state just use:
setPosition(initialState);
You need not to create another State. Just declare an initial state which will not be changed and assign it to the Position state when it is needed to be reset. EX:
import React,{useState} from 'react'
const YourComponent = () =>{
const initialState = {
id: '',
title: '',
description: '',
authoredBy: '',
createdDate: '',
lastUpdatedBy: '',
lastUpdateDate: '',
sliderResponses: [],
tileResponses: [{}],
template: {}
}
const [basePosition, setBasePosition] = useState({});
const [position, setPosition] = useState(initialState);
const resetState = () =>{
setPosition(initialState)
}
}
Answer to your question if you should store initial value is Yes.
That would be the easiest way to maintain your code. So put your initial value in a constant:
const INITIAL_VALUES = {
id: '',
title: '',
description: '',
authoredBy: '',
createdDate: '',
lastUpdatedBy: '',
lastUpdateDate: '',
sliderResponses: [],
tileResponses: [{}],
template: {}
}
Than every time you want to use that initial object, just spread it and all is good (spread to lose reference to constant):
const [basePosition, setBasePosition] = useState({});
const [position, setPosition] = useState({...INITIAL_VALUES});
And later when you reset:
setPosition({...INITIAL_VALUES})
import React, { useState } from 'react'
// counter
function Example3() {
const [initial, setIncrement] = useState(0)
const increment = () => {
setIncrement(initial + 1)
}
const dincrement = () => {
setIncrement(initial - 1)
}
const reset = () => {
setIncrement(0)
}
return (
<div>
<p>{initial}</p>
<button onClick={increment} >+</button>
<button onClick={dincrement} >-</button>
<button onClick={reset}>reset</button>
</div>
)
}
export default Example3;
Related
I am using draftjs to update data from a server call. I am using MongoDB as the database and graphql as the query language. I can convert the data from the API using convertFromRaw(JSON.parse(data)) . I then set it to state, but when I attempt to createWithContent() i get an error Cannot read properties of undefined (reading 'getBlockMap'). My code is below. Been working on this for two days.
const GET_ISP_ENTRY = gql`
query IspListEntry($ispListEntryId: ID!) {
ispListEntry(id: $ispListEntryId) {
_id
displayName
contactFirstName
contactLastName
contactTitle
lastUpdated
onlineService
onlineAttn
address
city
state
zipCode
country
phoneNumber
extension
mobileNumber
faxNumber
email
website
referTo
notes
previous
}
}
`;
const UPDATE_ISP_ENTRY = gql`
mutation UpdateISPEntry($ispListEntryUpdateId: ID!, $input: UpdateISPEntry) {
ispListEntryUpdate(id: $ispListEntryUpdateId, input: $input) {
displayName
}
}
`;
const UpdateISPEntry = () => {
const ispEntryFields = {
displayName: '',
lastUpdated: Date(),
onlineService: '',
contactTitle: '',
contactFirstName: '',
contactLastName: '',
onlineAttn: '',
address: '',
city: '',
state: '',
zipCode: '',
country: '',
phoneNumber: '',
extension: '',
mobileNumber: '',
faxNumber: '',
email: '',
website: '',
referTo: '',
notes: '',
previous: ''
};
const [rawNotesFromDB, setRawNotesFromDB] = useState();
const [urlId, setUrlId] = useState('');
const [getIsp, { data, loading, error }] = useLazyQuery(GET_ISP_ENTRY, {
variables: {
ispListEntryId: urlId
},
onCompleted: () => {
loading
? console.log('Loading....')
: setEditorNotesState(
convertFromRaw(JSON.parse(data.ispListEntry.notes))
);
},
onError: () => {
toast.error(error);
}
});
const [editorNotesState, setEditorNotesState] = useState(() =>
EditorState.createWithContent().getCurrentContent()
);
console.log(editorNotesState);
const [formValues, setFormValues] = useState();
const [previousValue, setPreviousValue] = useState();
const [editorPreviousState, setEditorPreviousState] = useState();
const [
submitValues,
{ data: successful, loading: successLoading, error: loadingError }
] = useMutation(UPDATE_ISP_ENTRY, {
onError: () => {
toast.error(`There was an error ${loadingError}`);
}
});
const params = useLocation();
const path = params.pathname;
const pathSplit = path.split('/')[2];
useEffect(() => {
getIsp();
setFormValues(data && data.ispListEntry);
setUrlId(pathSplit);
}, [data, getIsp, pathSplit, formValues]);
// const convertedState = convertFromRaw(
// JSON.parse(data && data.ispListEntry.notes)
// );
// console.log(convertedState);
// const raw = () => {
// !formValues
// ? console.log('DAMN')
// : setRawNotes(convertFromRaw(JSON.parse(formValues.notes)));
// };
const handleSubmit = () => {};
return (
<Fragment>
<div className='container p-4 parent-container'>
<ISPFormHeader />
<ISPFormHeaderPagename children='Update ISP Entry' />
<ISPForm
initialValues={data && data.ispListEntry}
enableReinitialize={true}
onSubmit={handleSubmit}
/>
<div className='editor-fields'>
<div className='rt-editor'>
<header className='rt-editor-header'>Notes</header>
<EditorComponent
id='notes'
name='notes'
type='text'
editorState={editorNotesState}
onEditorStateChange={setEditorNotesState}
/>
</div>
<div className='rt-editor'>
<header className='rt-editor-header'>Previous</header>
<EditorComponent
name='previous'
id='previous'
type='text'
EditorState={editorPreviousState}
// onEditorStateChange={handleEditorPreviousChange}
/>
</div>
</div>
</div>
</Fragment>
);
};
export default UpdateISPEntry;
How do I clear/delete a grocery item? I need to make a button that clears a grocery item after I click it. TIA
Here's the code this is for a HW assignment:
class App extends Component {
state = {
grocery: grocery,
item: '',
brand: '',
units: Number,
quantity: Number,
isPurchased: Boolean
}
handleChange = (e) => {
this.setState({ [e.target.id]: e.target.value })
}
handleSubmit = (e) => {
e.preventDefault()
const addGrocery = {
item: this.state.item,
brand: this.state.brand,
units: this.state.units,
quantity: this.state.quantity,
}
this.setState({
grocery: [addGrocery, ...this.state.grocery],
item: '',
brand: '',
units: Number,
quantity: Number,
})
const removeGrocery = {
item: this.state.item
}
}
Here is some codes bro. I fixed something that will give you error in future.
const initialValue = {
grocery: grocery,
item: '',
brand: '',
units: 1,
quantity: 2,
isPurchased: false
}
class App extends Component {
state = initialValue;
handleChange = (e) => {
// added a spread iterator
this.setState({...this.state, [e.target.id]: e.target.value })
}
reset = ()=> {this.setState(initialValue)} // set to initialValue is clearing current state to initial state
I'm building a wordle clone. I've structured it so that the keypad and the letter display grid are two separate components, Keypad.js and Row,js respectively. Project structure is as follows:
src
-components
|-Grid.js
|-Keypad.js
|-Row.js
-App.js
-AppContex.js
-index.js
When a user enters a letter on the keypad, initially I want that letter to appear in the first index of the first row, as per the game of wordle. How do I get Row.js to "listen" to changes in Keypad.js, so that when a user enters a letter, it shows up in the corresponding index in the grid row?
My approach so far has been to create global state using the Context API, where I've made an empty grid to share to the entire app:
AppContext.js
import { createContext } from "react";
const guessRows = [
['', '', '', '', ''],
['', '', '', '', ''],
['', '', '', '', ''],
['', '', '', '', ''],
['', '', '', '', ''],
['', '', '', '', '']
]
export const AppContext = createContext()
const AppContextProvider = (props) => {
return(
<AppContext.Provider value = {guessRows}>
{props.children}
</AppContext.Provider>
)
}
export default AppContextProvider
In Keypad.js, the letter the user enters is used to update the context (or at least that's what I think it's doing):
import { useContext,useState } from "react"
import { AppContext } from "../AppContext"
const Keypad = () => {
const guessRows = useContext(AppContext);
let currentRow = 0;
let currentTile = 0;
const letters = [
"Q",
"W",
"E",
"R",
// etc
];
const handleClick = (letter) => {
guessRows[currentRow][currentTile] = letter;
currentTile++;
console.log("guess rows", guessRows);
};
return (
<div className="keyboard-container">
{letters.map((letter, index) => {
return (
<div className="key" key={index} onClick={() => handleClick(letter)}>
{letter}
</div>
);
})}
</div>
);
};
export default Keypad;
...then in Row.js, I'm looping through the context and rendering the rows:
Row.js
import { useContext } from "react";
import { AppContext } from "../AppContext";
const Row = () => {
const rowData = useContext(AppContext)
const currentRow = rowData[0]
return (
<div className="row">
{currentRow.map((letter,index) => {
return(
<div className="tile" id = {index}>{letter}</div>
)
})}
</div>
)
}
export default Row;
Unsurprisingly this isn't working, so any suggestions would be appreciated.
Your guessRows should be put into the context state, so that it can have a state setter passed down - then instead of doing guessRows[currentRow][currentTile] = letter;, call the state setter. Similarly, currentTile++; should be replaced with a state update, since this is React - the view should flow from the state.
const AppContextProvider = (props) => {
const [guessRows, setGuessRows] = useState([
['', '', '', '', ''],
['', '', '', '', ''],
['', '', '', '', ''],
['', '', '', '', ''],
['', '', '', '', ''],
['', '', '', '', '']
]);
return(
<AppContext.Provider value = {{ guessRows, setGuessRows }}>
{props.children}
</AppContext.Provider>
);
};
const { guessRows, setGuessRows } = useContext(AppContext);
const [currentRow, setCurrentRow] = useState(0);
const [currentTile, setCurrentTile] = useState(0);
const handleClick = (letter) => {
setGuessRows(
guessRows.map((row, i) => i !== currentRow ? row : (
row.map((item, j) => j === currentTile ? letter : item)
))
);
setCurrentTile(currentTile + 1);
};
And then when the state setter is called, the components will re-render, including Row, which will show the changes made.
So I deployed the app to demonstrate the problem
https://ecstatic-mayer-aaa530.netlify.app/
When you 'refresh' the page new data is fetched and user name is displayed (along with active state)
BUT when I click random user button, I don't get the same behavior even though I refetch the data. I want to get the same behavior as page refresh. (Name is displayed with the active state)
To better observe the problem you can : Hover on other fields, click on random user btn and then re-enter with your mouse on name (profile icon). It is not updating as you'll see
import './App.css';
import ProfileCard from './components/ProfileCard';
import React, { useEffect, useState } from 'react';
function App() {
const [userData, setUserData] = useState({});
const [triggerFetch, setTriggerFetch] = useState(false);
const fetchUserData = async () => {
const response = await fetch('https://randomuser.me/api/');
const data = await response.json();
setUserData(setFields(data.results[0]));
};
const setFields = (userData) => {
const fName = userData.name.first;
const lName = userData.name.last;
const fullName = fName + ' ' + lName;
return {
image: userData.picture.large,
name: fullName,
email: userData.email,
dob: userData.dob.date,
location: userData.location.street.name,
phone: userData.phone,
password: userData.login.password,
};
};
useEffect(() => {
fetchUserData();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [triggerFetch]);
//this triggers fetch
const handleRandomUserClick = () => {
setTriggerFetch(!triggerFetch);
};
return (
<main>
<div className='grey-bg'></div>
<ProfileCard
userData={userData}
handleRandomUserClick={handleRandomUserClick}
/>
</main>
);
}
export default App;
import React from 'react';
import {
FaUserAlt,
FaPhoneAlt,
FaBirthdayCake,
FaLock,
FaLocationArrow,
FaEnvelope,
} from 'react-icons/fa';
const ProfileCard = ({ userData, handleRandomUserClick }) => {
const { image, name, email, dob, location, phone, password } = userData;
const [randomUserClicked, setrandomUserClicked] = React.useState(false);
const defaultFlag = {
name: 'active',
email: '',
dob: '',
location: '',
phone: '',
password: '',
};
const [flag, setFlag] = React.useState(defaultFlag);
const [title, setTitle] = React.useState('My Name Is');
const [value, setValue] = React.useState(name);
const handleValue = (e) => {
if (e.target.dataset.label === 'name') {
setTitle('My Name is');
setValue(name);
setFlag(defaultFlag);
} else if (e.target.dataset.label === 'email') {
setTitle('My Email is');
setValue(email);
setFlag({
name: '',
email: 'active',
dob: '',
location: '',
phone: '',
password: '',
});
} else if (e.target.dataset.label === 'dob') {
setTitle('My Birthday is');
setValue(new Date(dob).toLocaleDateString());
setFlag({
name: '',
email: '',
dob: 'active',
location: '',
phone: '',
password: '',
});
} else if (e.target.dataset.label === 'location') {
setTitle('I live in');
setValue(location);
setFlag({
name: '',
email: '',
dob: '',
location: 'active',
phone: '',
password: '',
});
} else if (e.target.dataset.label === 'phone') {
setTitle('My phone number is');
setValue(phone);
setFlag({
name: '',
email: '',
dob: '',
location: '',
phone: 'active',
password: '',
});
} else {
setTitle('My password');
setValue(password);
setFlag({
name: '',
email: '',
dob: '',
location: '',
phone: '',
password: 'active',
});
}
if (e.target.dataset.label === 'random') {
handleRandomUserClick();
setrandomUserClicked(!randomUserClicked);
}
};
React.useEffect(() => {
setTitle('My Name is');
setValue(name);
setFlag(defaultFlag);
}, [randomUserClicked]);
return (
<article className='profile-card'>
<img src={image} alt={name} />
<div className='user-details'>
<p className='user-title'>{title}</p>
<h3 className='user-value'>{value || name}</h3>
</div>
<ul className='card-list'>
<li className={flag.name} onMouseEnter={handleValue} data-label='name'>
<FaUserAlt className='icon' />
</li>
<li
data-label='email'
onMouseEnter={handleValue}
className={flag.email}
>
<FaEnvelope className='icon' />
</li>
<li data-label='dob' onMouseEnter={handleValue} className={flag.dob}>
<FaBirthdayCake className='icon' />
</li>
<li
data-label='location'
onMouseEnter={handleValue}
className={flag.location}
>
<FaLocationArrow className='icon' />
</li>
<li
data-label='phone'
onMouseEnter={handleValue}
className={flag.phone}
>
<FaPhoneAlt className='icon' />
</li>
<li
data-label='password'
onMouseEnter={handleValue}
className={flag.password}
>
<FaLock className='icon' />
</li>
</ul>
{/* Also handling this click within handleValue function */}
<button className='btn' onClick={handleValue} data-label='random'>
Random User
</button>
</article>
);
};
export default ProfileCard;
The problem happens, because in ProfileCard component, userData is not listed under useEffects's dependencies array.
By the way, your component design is very prone to bugs. In React functional components should be a simple functions that receive some data (props) and return JSX. Hooks should be used only when you actually have to use them. In your app, you're creating a complicated network of effects, state updates and re-renders, which makes it hard to maintain.
Let's write all hooks you actually need:
one state for the data fetched from an api
one state for keeping which section is currently active (name, email, etc.)
one effect for fetching the data
And that's it! Everything else can be just passed through props.
I am trying to re render a component when a prop changes. My component uses a custom hook which holds the state of a form. In useEffect whenever the prop called refresh changes I want to re render the page. I have tried countless solutions including making a forceUpdate function and calling it when the prop changes, I tried changing state so that the component should re render, but the information in my for does not clear. Below is my code.
Component:
const CustomerInformationForm = (props) => {
const [triggerRefresh, setTriggerRefresh] = useState();
const initialState = {
name: {
value: "",
isValid: false,
},
phone: {
value: "",
isValid: false,
},
address: {
value: "",
isValid: true,
},
};
const initialValidity = false;
useEffect(() => {
console.log("customerinfo refreshing");
setTriggerRefresh(props.refresh);
}, [props.refresh]);
let [formState, inputHandler] = useForm(
initialState,
initialValidity
);
return (
<div>
<h3>Customer Information</h3>
<form className="customer-information_form">
<Input
id="name"
label="Name"
type="text"
element="input"
validators={[VALIDATOR_REQUIRE()]}
errorText="Please enter a valid name."
onInput={inputHandler}
onChange={props.customerInfo(formState)}
/>
<Input
type="text"
element="input"
id="phone"
label="Phone"
validators={[VALIDATOR_MINLENGTH(8)]}
errorText="Please enter a valid phone number."
onInput={inputHandler}
onChange={props.customerInfo(formState)}
/>
<Input
type="text"
element="input"
id="address"
label="Address"
validators={[]}
onInput={inputHandler}
onChange={props.customerInfo(formState)}
/>
</form>
</div>
);
};
export default CustomerInformationForm;
Custom hook:
import { useCallback, useReducer } from "react";
const formReducer = (state, action) => {
switch (action.type) {
case "INPUT_CHANGE":
let formIsValid = true;
for (const inputId in state.inputs) {
if (!state.inputs[inputId]) {
continue;
}
if (inputId === action.inputId) {
formIsValid = formIsValid && action.isValid;
} else {
formIsValid = formIsValid && state.inputs[inputId].isValid;
}
}
return {
...state,
inputs: {
...state.inputs,
[action.inputId]: { value: action.value, isValid: action.isValid },
},
isValid: formIsValid,
};
case "SET_DATA":
return {
inputs: action.inputs,
isValid: action.formIsValid,
};
default:
return state;
}
};
export const useForm = (initialInputs, initialValidity) => {
const [formState, dispatch] = useReducer(formReducer, {
inputs: initialInputs,
isValid: initialValidity,
});
const inputHandler = useCallback((id, value, isValid) => {
dispatch({
type: "INPUT_CHANGE",
value: value,
isValid: isValid,
inputId: id,
});
}, []);
const setFormData = useCallback((inputData, formValidity) => {
dispatch({
type: "SET_DATA",
inputs: inputData,
formIsValid: formValidity,
});
}, []);
return [formState, inputHandler, setFormData,];
};
Any solutions as to what I could do to get the form to re render empty??
I think the best way is to use the setFormData method returned by your hook (you have omitted it in your CustomerInformationForm component).
Then you can call it in the form, when some condition is met in an effect:
const initialState = useRef({
name: {
value: "",
isValid: false,
},
phone: {
value: "",
isValid: false,
},
address: {
value: "",
isValid: true,
},
});
let [formState, inputHandler, setFormData] = useForm(
initialState,
initialValidity
);
useEffect(() => {
console.log("customerinfo refreshing");
setTriggerRefresh(props.refresh);
setFormData(initialState.current);
}, [props.refresh]);
You can also store the initialState value with useRef, to avoid re-running the effect unnecessarily.