I have the code below.
How can I reset the stocksHelper, instatiating again when the component render on stocks's useState change?
I need this class to instantiate again to reset the variables inside the class instance, because when the stocks change a calculation needs to be done to render the stocks again. And if I get the instance of the last render with the old values this calculation will bug my entire aplication
export default function Heatmap() {
const [stocks, setStocks] = useState<IStock[]>([]);
const stocksHelper: StocksHelper = new StocksHelper(stocks);
return (
<main className={styles.main}>
<RegisterForm stocks={stocks} setStocks={setStocks} />
</main>
);
}
RegisterForm component below:
export default function RegisterForm(props: Props) {
const { stocks, setStocks } = props;
const [name, setName] = useState<string>('');
const [value, setValue] = useState<number>(0);
const [volume, setVolume] = useState<number>(0);
function storeStock(): void {
axios.post('any url', {
name: name,
value: value,
volume: volume
})
.then((res) => {
setStocks([...stocks, res.data]);
})
.catch((res) => console.log(res));
}
return (
<form className={styles.form} onSubmit={() => storeStock()}>
<fieldset>
<legend className={styles.title}>Cadastro</legend>
<input type="text" onChange={e => setName(e.target.value)} placeholder="Nome" />
<input type="number" onChange={e => setValue(parseFloat(e.target.value))} placeholder="Porcentagem" />
<input type="number" onChange={e => setVolume(parseInt(e.target.value))} placeholder="Volume" />
<button type='submit'>Cadastrar</button>
</fieldset>
</form>
);
}
#AmitMaraj's answer is perfectly fine but for a shorter and more concise method you should use useMemo:
const stocksHelper = useMemo(() => new StocksHelper(stocks), [stocks]);
Now a new StocksHelper will only be created when stocks changes.
Pass a “create” function and an array of dependencies. useMemo will only recompute the memoized value when one of the dependencies has changed. This optimization helps to avoid expensive calculations on every render.
Link to documentation
If I'm understanding correctly, you might be able to achieve this with useEffect! See below for an example:
export default function Heatmap() {
const [stocks, setStocks] = useState<IStock[]>([]);
const [stocksHelper, setStockHelper] = useState<StocksHelper>(new StocksHelper(stocks));
useEffect(() => {
setStockHelper(new StocksHelper(stocks))
}, [stocks])
return (
<main className={styles.main}>
<RegisterForm stocks={stocks} setStocks={setStocks} />
</main>
);
}
Related
I have a webpage with multiple forms. Here's a bare minimum example of the structure:
export default function Example() {
const [previousFormFetchedFromWeb, setPreviousFormFetchedFromWeb] = useState(
{}
);
const [formA, setFormA] = useState({});
const [formB, setFormB] = useState({});
const router = useRouter();
useEffect(() => {
fetchFormFromWeb(router.query.id).then((previousForm) => {
console.log("fetched info from web");
setPreviousFormFetchedFromWeb(previousForm);
setFormA(previousForm.formA);
setFormB(previousForm.formB);
});
},[router.isReady]);
return (
<>
<FormA form={formA} />
<FormB form={formB} />
</>
);
}
function FormA({ form }) {
return (
<input
type="text"
name="field1"
id="field1"
value={formA.field1}
onChange={(e) => {
setFormA(e.target.value);
}}
/>
);
}
function FormB({ form }) {
return (
<input
type="text"
name="field2"
id="field2"
value={formB.field2}
onChange={(e) => {
setFormB(e.target.value);
}}
/>
);
}
I think this should be the encapsulated logic of my form page. The problem is that when the onChange event is called for a field of any form, all forms get re-rendered. I assumed that setState should re-render only the components with the affected dependency change. Am I missing something?
Any state change in Example component will trigger re render to its child components (FormA, FormB). If you want to avoid that. wrap FormA and FormB in React.memo. That way you can prevent unwanted re-renders
export default function Example() {
const [previousFormFetchedFromWeb, setPreviousFormFetchedFromWeb] = useState(
{}
);
const [formA, setFormA] = useState({});
const [formB, setFormB] = useState({});
const router = useRouter();
useEffect(() => {
fetchFormFromWeb(router.query.id).then((previousForm) => {
console.log("fetched info from web");
setPreviousFormFetchedFromWeb(previousForm);
setFormA(previousForm.formA);
setFormB(previousForm.formB);
});
},[router.isReady]);
return (
<>
<MemFormA form={formA} />
<MemFormB form={formB} />
</>
);
}
const MemFormA = React.memo(function FormA({ form }) {
return (
<input
type="text"
name="field1"
id="field1"
value={formA.field1}
onChange={(e) => {
setFormA(e.target.value);
}}
/>
);
})
const MemFormB = React.memo(function FormB({ form }) {
return (
<input
type="text"
name="field2"
id="field2"
value={formB.field2}
onChange={(e) => {
setFormB(e.target.value);
}}
/>
);
})
Anytime state of <Example /> is updated, <Example /> re-renders, which in turn also re-renders <FormA /> and <FormB />. This is expected.
You should look into using React.memo() for FormA and FormB if you want them to only re-render when the props passed to them is changed.
I'm trying to set a form field value with useState.
The settings.values.apiKey variable has a value, but the textarea element is empty. What's wrong with my useState?
I tried to change value={apiKey} to value={settings.values.apiKey} and then the value is displayed, but then I can't change the value of the field. When I try to enter something, it always shows the original value.
App.js
const App = () => {
const [apiKey, setApiKey] = useState(settings.values.apiKey)
useEffect(() => {
const getSettings = async () => {
const settingsFromServer = await fetchSettings()
setSettings(settingsFromServer)
}
getSettings()
}, [])
const fetchSettings = async () => {
const res = await fetch('http://127.0.0.1/react-server/get.php')
return await res.json()
}
const saveSettings = async (settings) => {
}
return (
<div className="container">
<Header />
<Settings
settings={settings}
saveSettings={saveSettings}
/>
<Footer />
</div>
);
}
export default App;
Settings.js:
import { useState } from 'react';
const Settings = ({ settings, saveSettings }) => {
const [apiKey, setApiKey] = useState(settings.values.apiKey)
const onSubmit = (e) => {
e.preventDefault()
saveSettings({ apiKey})
}
return (
<div>
<form className='add-form' onSubmit={onSubmit}>
<div className='form-control'>
<label>Api key</label>
<textarea
value={apiKey}
onChange={(e) => setApiKey(e.target.value)}
/>
</div>
<input type='submit' value='Save settings' className='mt15' />
</form>
</div>
)
}
export default Settings
It looks like by mistake you have used apiKey in App.js file as your state variable. It should be replaced by settings.
const [settings, setSettings] = React.useState();
The above code would make value={apiKey} work properly for textarea in Settings.js file.
And, then onChange will also start working properly.
UPDATE
In addition to the above mentioned error, in case settings props is undefined in Settings.js, this might cause your code to break at useState. So, instead put a check for settings values in useEffect and then set the value. The code would look like this or you can check the codesandbox link here for working demo.
Settings.js
import { useEffect, useState } from "react";
const Settings = ({ settings, saveSettings }) => {
const [apiKey, setApiKey] = useState();
useEffect(() => {
if (settings?.values?.apiKey) {
setApiKey(settings.values.apiKey);
}
}, [settings]);
const onSubmit = (e) => {
e.preventDefault();
saveSettings({ apiKey });
};
return (
<div>
<form className="add-form" onSubmit={onSubmit}>
<div className="form-control">
<label>Api key</label>
<textarea
value={apiKey}
onChange={(e) => setApiKey(e.target.value)}
/>
</div>
<input type="submit" value="Save settings" className="mt15" />
</form>
</div>
);
};
export default Settings;
App.js
const [settings, setSettings] = useState()
const saveSettings = async (settings) => {
setSettings(settings);
}
I have a simple react app in which there is a FruitsList component for showing the fruits in the list, a FruitForm component to add a fruit, and both are contained inside a Fruits component. I am using useContext and useReducer to manage the state of the fruits. I have created a FruitContext for same. I want to stop the re-rendering of FruitForm since it is only using dispatch function and re-rendering it is useless every time new fruit is added. Plz suggest any solution for same.
Form Component
const Form = () => {
const { dispatch } = useContext(FruitsContext);
const { setLoading } = useContext(LoaderContext);
let formRef = null;
const fruit = {};
const formSubmitHandler = async (event) => {
event.preventDefault();
setLoading(true);
await fetch('https://fruit-basket-74269.firebaseio.com/fruits.json', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(fruit)
});
dispatch({type: 'ADD', fruit: fruit});
// formRef.reset();
setLoading(false);
}
return (
<Card>
{console.log('[Form]')}
<form ref={ref => formRef = ref} onSubmit={formSubmitHandler} className={style.form} autoComplete="off">
<div className={style.formGroup}>
<input onChange={event => fruit.item = event.target.value} className={style.input} type="text" id="name" placeholder="Enter fruit name" />
<label className={style.label} htmlFor="name">Name</label>
</div>
<div className={style.formGroup}>
<input onChange={event => fruit.qty = event.target.value} className={style.input} type="number" min="0" id="qty" placeholder="Enter quantity" />
<label className={style.label} htmlFor="qty">Quantity</label>
</div>
<Button>Add Fruit</Button>
</form>
</Card>
)
}
export default React.memo(Form);
FruitList
const FruitList = () => {
const { fruits } = useContext(FruitsContext);
console.log('[FruitList]:', fruits);
return useMemo(() => {
return (
<div className={style.fruitList}>
<h2 className={style.heading}>Fruits</h2>
<hr />
<div className={style.list}>
<FruitCard name={'Apple'} qty={15} />
<FruitCard name={'Orange'} qty={10} />
<FruitCard name={'Grapes'} qty={20} />
</div>
</div>
);
}, []);
}
export default FruitList;
Fruits
const Fruits = () => {
console.log('[Fruits Parent]');
// const { loading } = useContext(LoaderContext);
return (
<div className={style.fruits}>
{/* {loading && <Loader />} */}
<Form />
<br />
<Filter />
<br />
<FruitList />
</div>
)
}
export default Fruits
FruitContext
export const FruitsContext = createContext();
const FruitsProvider = ({children}) => {
const [fruits, dispatch] = useReducer(reducer, []);
const value = ({
fruits, dispatch
});
return (
<FruitsContext.Provider value={value}>
{ children }
</FruitsContext.Provider>
);
}
export default FruitsProvider;
FruitReducer
export default (state, action) => {
switch(action.type) {
case 'LOAD':
return action.fruits
case 'ADD':
console.log('[Pre-Action]', state);
const newList = [...state];
newList.push(action.fruit);
console.log('[Post-Action]', newList);
return newList;
case 'DELETE':
return state.filter(fruit => fruit.id !== action.id);
default: return state;
}
}
Components that consume a context will always rerender if anything in the providers value changed. Regardless of whether you actually use that value or not (In this case for example even if you only pull the dispatch function).
Usually you don't need to optimize something like that for most react applications, react is already quite fast and a few additional rerenders don't hurt. Any performance issues can be solved when and where they happen.
If you want to optimize from the start you can split your reducers state and dispatch into two different contexts. They both can be put into the same ProviderComponent but there have to be two different Context.Provider components. One will use the state as value and the other will use the dispatch function as value.
If you then consume the dispatch context, it won't cause the component to rerender if the dispatch action changes the state.
// Update
As an example:
const FruitsProvider = ({children}) => {
const [fruits, dispatch] = useReducer(reducer, []);
return (
<FruitsStateContext.Provider value={fruits}>
<FruitsDispatchContext.Provider value={dispatch}>
{ children }
</FruitsDispatchContext.Provider>
</FruitsStateContext.Provider>
);
}
I would also recommend to not export the context directly but instead export hooks that expose the state or the dispatch.
e.g.
export const useFruits = () => {
const fruitsState = React.useContext(FruitsStateContext);
if (!fruitsState) {
throw new Error('you cant use the useFruits hook outside the FruitsStateContext');
}
return fruitsState;
}
[Mycode] (https://codesandbox.io/s/romantic-kowalevski-fp00l?file=/src/App.js)
I'm practicing React by making todo-list app.
I want my input empty when i hit Enter. but it didn't work.
here is my whole code :
const Todo = ({ text }) => {
return (
<div>
<span>{text}</span>
</div>
);
};
const InputText = ({ addTodo }) => {
const [txt, setTxt] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
if (!txt) return;
addTodo(txt);
setTxt("");
};
return (
<form onSubmit={handleSubmit}>
<input type="text" onChange={(e) => setTxt(e.target.value)}></input>
</form>
);
};
function App() {
const [todos, setTodos] = useState([]);
const addTodo = (text) => {
const newTodos = [...todos, text];
setTodos(newTodos);
};
return (
<>
<div className="todo-list">
{todos.map((val, idx) => {
return <Todo key={val + idx} text={val} />;
})}
<InputText addTodo={addTodo} />
</div>
</>
);
}
line 17 on the link, setTxt(""); doesn't change state of txt.
how can i fix it?
That is not a "controlled" component since you are not using the value property on the input.
Try
<input type="text" onChange={e => setTxt(e.target.value)} value={txt} />
https://reactjs.org/docs/forms.html
You actually need to set the input value to your state.
Try something like
<Input type="text" onChange={(e) => setTxt(e.target.value)} value={txt}/>
I hope it helps.
I am making a React app that tracks your expenses. Right now in the ExpenseInput component I want to be able to extract the values of the inputs and alert the values that were entered. Later on I will want to render a list of expenses similar to a ToDo App. I have some stuff setup already. I know that the expense state variable should store all you expenses and all the other state variables are set to its respective inputs. I just am not sure what the next step should be.
import React, { useState } from "react";
export default function ExpenseInputs() {
const [expense, setExpense] = useState([]);
const [expenseName, setExpenseName] = useState("");
const [expenseAmt, setExpenseAmt] = useState();
const [expenseType, setExpenseType] = useState("");
const expenseTypes = ["Food", "Gas", "Entertainment", "Rent"];
const updateExpenseName = e => {
e.preventDefault();
setExpenseName(e.target.value);
console.log(e.target.value);
};
const updateExpenseAmt = e => {
e.preventDefault();
setExpenseAmt(e.target.value);
};
const addExpenses = () => {
};
return (
<>
<div className="field">
<label className="label">Expense</label>
<input
value={expenseName}
onChange={updateExpenseName}
className="expense-input"
type="text"
placeholder="Expense"
/>
</div>
<div className="field">
<label className="label">Amount</label>
<input
value={expenseAmt}
onChange={updateExpenseAmt}
className="expense-amount"
type="text"
placeholder="Expense amount"
/>
</div>
<div className="field">
<label className="label">Expense Type</label>
<select>
{expenseTypes.map(e => {
return <option>{e}</option>;
})}
</select>
</div>
<div className="field">
<button onClick={addExpenses} className="button">
Add
</button>
</div>
</>
);
}
You have your values in the state of each input (expenseAmt, expenseName, etc).
const [expenseName, setExpenseName] = useState("");
const [expenseAmt, setExpenseAmt] = useState();
const [expenseType, setExpenseType] = useState("");
For your purposes to add new expense you can use the state which may be array of objects, where each object represents your expense (title, type, amount).
const [expense, setExpense] = useState([]);
So you need to create handler to add new object to your array of expenses.
const addExpenses = () => {
};
And just map it. Then you pass object of expense to each expense component as props.
{expense.map((expense, index) => (
<ListItem key={index} expense={expense} />
))}
For removing you need to create remove handler and pass it to expense component as props and fire it there.