Hi could someone tell me why I am getting the list.map is not a function error? I am pretty sure my React code makes list an array but I am a beginner so I may have overlooked something
import React, { useState, useEffect } from "react";
import Task from "./Task";
function Home() {
const [text, setText] = useState("");
const [task, setTask] = useState("");
const [list, setList] = useState([]);
useEffect(() => {
setList(list.push(task));
}, [task]);
const addTask = (e) => {
e.preventDefault();
setTask(text);
setText("");
};
const updateText = (e) => {
setText(e.target.value);
};
return (
<div className="Home">
<h3>Home Page</h3>
<form onSubmit={addTask}>
<input type="text" value={text} onChange={updateText} />
<button type="submit">Add</button>
</form>
<div className="listoftasks">
{list.map((t) => (
<Task
text={t}
/>
))}
</div>
</div>
);
}
export default Home;
Array.push() does not return the updated array. Therefore you should use Array.concat() -
useEffect(() => {
setList(list.concat(task));
}, [task]);
Or use spread operator, for immutability functional approach:
useEffect(() => {
setList([...list, task]);
}, [task]);
When task is updating you're setting the new value of list to the result of push which is the new length of the array.
You should push and then update the state
useEffect(() => {
list.push(task);
setList(list.slice());
}, [task]);
Array.push() returns a number, the new length of the now modified in place array.
useEffect(() => setList(list.push(newThing))) // list is now a number!!!
You've turned list into a number, so now for a 3 item array(that once was 2 items) you'll be calling 3.map() and a 3 doesn't have a map method on it.
Worse yet if you just push and reset the reference you'll not update
useEffect(() => {
list.push(newThing)
setList(list) // won't update! list === list is true, and thus does not trigger render
})
You can go about updating the list a few ways by making new array and passing it with concatenation
useEffect(() => setList(list.concat(newThing)) // works
or spreading
useEffect(() => setList([...list, newThing])) // works
Once this is done, it will fail the check to see if the passed entity is the same value. This should allow the render to be triggered and your new updates to appear.
Related
Sorry in advance if the question is a bit vague, still quite new to JS and react. Anyways, my problem is that in the following code the newFilter state hook is one step behind the event.target.value, which should have been assigned to newFilter at onChange, could anyone enlighten me why the newFilter gets updated one step later?
Output in console from console.log, when input change happens:
The code:
function App() {
const [countries, setCountries] = useState([]);
const [newFilter, setNewFilter] = useState('');
const [allCountries, setAllCountries] = useState([]);
useEffect(() => {
axios.get("https://restcountries.com/v3.1/all").then((response) => {
setAllCountries(response.data);
});
}, []);
const handleFilterChange = (event) => {
setNewFilter(event.target.value);
console.log("this is event.target.value", event.target.value)
console.log("this is the newFilter", newFilter)
if (event.target.value) {
let countriesToShow = allCountries.filter((country) =>
country.name.common.toLowerCase().match(event.target.value.toLowerCase())
);
setCountries(countriesToShow);
}
};
return (
<div>
<strong>
<p>Find countries</p>
</strong>{" "}
<input value={newFilter} onChange={handleFilterChange} />
</div>
);
}
export default App;
React state updates are asynchronous & are not run immediately (kind of like setTimeout(func , 0).
See https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous.
Hence when you update a state property using its previous value, you should use the callback argument for the state setter.
handleFilterChange = (event) => {
this.setState((state)=> {
newFilter: event.target.value,
countries: (event.target.value)?allCountries.filter(...):allCountries
});
}
I need to change the useState without rendering the page.
First is it possible?
const UsersComponent = ({valueProp}) => {
const [users, setUsers] = useState(valueProp);
const [oldUsers, setoldUsers] = useState(value);
const allUsers = useSelector((state) =>
state.users
);
useEffect(() => {
dispatch(getUsersData());
}, [dispatch]);
useEffect(() => {
// assign users to state oldUsers
}, [dispatch]);
const onClickMergeTwoArrayOfUsers = () => {
let oldUsers = collectData(oldUsers);
const filteredUsers = intersectionBy(oldUsers, valueProp, "id");
setUsers(filteredUsers); // most important
console.log("filteredUsers", filteredUsers); // not changed
};
I tried everything nothing helps me.
useEffect(() => {
let oldUsers = collectData(oldUsers);
const filteredUsers = intersectionBy(oldUsers, valueProp, "id");
setUsers(filteredUsers); // most important
}, [users]); // RETURN INFINITIVE LOOP
I am also try ->
useEffect(() => {
let oldUsers = collectData(oldUsers);
const filteredUsers = intersectionBy(oldUsers, valueProp, "id");
setUsers(filteredUsers); // most important
}, []);
Load only one and that doesn't mean anything to me..
I am try with useRef ,but that doesn't help me in this case.
I will try to explain the basis of the problem.
I need to get one get data. After that get on the click of a button, I need to merge oldUsers and users without rendering, change the state. That is problem.
If there is no solution to this problem, tell me what I could do to solve the problem?
I am googling but without succes ... I am also try this solution from interent ->
const [state, setState] = useState({});
setState(prevState => {
// Object.assign would also work
return {...prevState, ...updatedValues};
});
no work.
I am also try with ->
const [state, setState] = useState(() => {
const initialState = someExpensiveComputation(props);
return initialState;
});
Here is problem because I need to asynchronous get only after that can I looping.
Using a ref is probably a better option for whatever it is you're ultimately trying to do.
Yes, it is possible, but it violates one of the core rules of React state: Do Not Modify State Directly.
React compares state values using Object.is equality, so if you simply mutate an object in state instead of replacing it with a new value that is not object-equal, then the state "update" will not cause a re-render (but this is considered a bug in your program!). Anyway, this is how you'd do it:
<div id="root"></div><script src="https://unpkg.com/react#17.0.2/umd/react.development.js"></script><script src="https://unpkg.com/react-dom#17.0.2/umd/react-dom.development.js"></script><script src="https://unpkg.com/#babel/standalone#7.17.1/babel.min.js"></script>
<script type="text/babel" data-type="module" data-presets="env,react">
const {useCallback, useState} = React;
function Example () {
const [state, setState] = useState([1]);
const logState = useCallback(() => console.log(state.join(', ')), [state]);
// Don't actually do this!!!
const mutateState = () => {
setState(arr => {
arr.push(arr.at(-1) + 1);
return arr;
});
};
return (
<>
<div>{state.join(', ')}</div>
<button onClick={mutateState}>Mutate state</button>
<button onClick={logState}>Log state</button>
</>
);
}
ReactDOM.render(<Example />, document.getElementById('root'));
</script>
This is probably a beginner React mistake but I want to call "addMessage" twice using "add2Messages", however it only registers once. I'm guessing this has something to do with how hooks work in React, how can I make this work?
export default function MyFunction() {
const [messages, setMessages] = React.useState([]);
const addMessage = (message) => {
setMessages(messages.concat(message));
};
const add2Messages = () => {
addMessage("Message1");
addMessage("Message2");
};
return (
<div>
{messages.map((message, index) => (
<div key={index}>{message}</div>
))}
<button onClick={() => add2Messages()}>Add 2 messages</button>
</div>
);
}
I'm using React 17.0.2
When a normal form of state update is used, React will batch the multiple setState calls into a single update and trigger one render to improve the performance.
Using a functional state update will solve this:
const addMessage = (message) => {
setMessages(prevMessages => [...prevMessages, message]);
};
const add2Messages = () => {
addMessage('Message1');
addMessage('Message2');
};
More about functional state update:
Functional state update is an alternative way to update the state. This works by passing a callback function that returns the updated state to setState.
React will call this callback function with the previous state.
A functional state update when you just want to increment the previous state by 1 looks like this:
setState((previousState) => previousState + 1)
The advantages are:
You get access to the previous state as a parameter. So when the new state depends on the previous state, the parameter is helpful as it solves the problem of stale state (something that you can encounter when you use normal state update to determine the next state as the state is updated asynchronously)
State updates will not get skipped.
Better memoization of handlers when using useCallback as the dependencies can be empty most of the time:
const addMessage = useCallback((message) => {
setMessages(prevMessages => [...prevMessages, message]);
}, []);
import React from "react";
export default function MyFunction() {
const [messages, setMessages] = React.useState([]);
const addMessage = (message) => {
setMessages(messages => [...messages, message]);
};
const add2Messages = () => {
addMessage("Message1");
addMessage("Message2");
};
return (
<div>
{messages.map((message, index) => (
<div key={index}>{message}</div>
))}
<button onClick={() => add2Messages()}>Add 2 messages</button>
</div>
);
}
This is because messages still refers to the original array. It will get the new array at the next re-render, which will occur after the execution of add2Messages.
Here are 2 solutions to solve your problem :
Use a function when calling setMessages
export default function MyFunction() {
const [messages, setMessages] = React.useState([]);
const addMessage = (message) => {
setMessages(prevMessages => prevMessages.concat(message));
};
const add2Messages = () => {
addMessage("Message1");
addMessage("Message2");
};
return (
<div>
{messages.map((message, index) => (
<div key={index}>{message}</div>
))}
<button onClick={() => add2Messages()}>Add 2 messages</button>
</div>
);
}
Modify addMessage to handle multiple messages
export default function MyFunction() {
const [messages, setMessages] = React.useState([]);
const addMessage = (...messagesToAdd) => {
setMessages(prevMessages => prevMessages.concat(messagesToAdd));
// setMessages(messages.concat(messagesToAdd)); should also work
};
return (
<div>
{messages.map((message, index) => (
<div key={index}>{message}</div>
))}
<button onClick={() => addMessage("Message1", "Message2")}>
Add 2 messages
</button>
</div>
);
}
Changing addMessage function as below will make your code work as expected
const addMessage = (message) => {
setMessages(messages => messages.concat(message));
};
Your code didn't work because in case of synchronous event handlers(add2Messages) react will do only one batch update of state instead of updating state after every setState calls. Which is why when second addMessage was called here, the messages state variable will have [] only.
const addMessage = (message) => {
setMessages(messages.concat(message));
};
const add2Messages = () => {
addMessage('Message1'); // -> [].concat("Message1") = Message1
addMessage('Message2'); // -> [].concat("Message2") = Message2
};
So if you want to alter the state value based on previous state value(especially before re-rendering), you can make use of functional updates.
I'm using useState hook but after changing the state, the component is not rending itself. I don't know what thing I'm missing.
import React, {useState} from 'react'
import List from '#material-ui/core/List'
import ListTile from "./components/ListTile/ListTile"
import style from './App.module.css'
import InputField from "./components/inputField/InputField";
const App = () => {
const [list, setList] = useState([])
const onFormSubmitHandler = (data) => {
list.push(data)
setList(list)
}
return (
<div className={style.outerDiv}>
<h1 className={style.center}>CLister</h1>
<InputField onSubmit={onFormSubmitHandler}/>
<List component="nav">
{list.map((data, index) =>
<ListTile index={index} body={data}/>
)}
</List>
</div>
);
}
export default App
As your list an array a reference type in js. If you modify the list using push
like list.push() it will also modify the original list in your state ,as a result there will be no change in your state.
Example
let list = [1, 2, 3, 4];
let list2 = list;
// if I modify list2 now
list2.push(5);
console.log(list); // list also gets modified as ,they are reference type
So what you can do
const onFormSubmitHandler = (data) => {
let list2=[...list]; // creating a new variable from existing one
list2.push(data)
setList(list2);
}
or
const onFormSubmitHandler = (data) => {
setList(prev=>([...prev,data]));
}
Remember that your state cant be modificate with push, because the way to modificate it is with the method set
Use this code in the method onFormSubmitHandler
const onFormSubmitHandler = (data) => {
setList(list => ([...list, data]))
}
Lastly remember if your form will be submit you need to break it with e.prevent.default()
const onFormSubmitHandler = (data) => {
list.push(data);
setList([...list]);
}
You should try something like this
import List from '#material-ui/core/List'
import ListTile from "./components/ListTile/ListTile"
import style from './App.module.css'
import InputField from "./components/inputField/InputField";
const App = () => {
const [list, setList] = useState([])
const onFormSubmitHandler = (data) => {
list.push(data)
setList(list)
}
return (
<div className={style.outerDiv}>
<h1 className={style.center}>CLister</h1>
<InputField onSubmit={(e) => onFormSubmitHandler(e.target.value)}/>
<List component="nav">
{list.map((data, index) =>
<ListTile index={index} body={data}/>
)}
</List>
</div>
);
}
export default App
You are editing it the wrong way, you should directly give the new values to the setList function and not try to update the list variable. Thats why you have the function, so that you do not update the original value. What you have to do here is use the previous state within the function and the spread operator since its an array and u just want to add an item:
const onFormSubmitHandler = (data) => {
setList(prevList => [...prevList, data])
}
You should look at the list variable as a read-only variable and not attempt to modify it, you modify it through the setList function.
If you want to do some other modifications instead of just adding item:
const onFormSubmitHandler = (data) => {
let listCopy = [...list];
// do something with listCopy
setList(listCopy);
}
In addition, it seems like you are not sending data at all to the function, the way to send data with your function call is to do it with anonymous function in the component:
<Component onSubmit={(e) => { onFormSubmitHandler(e.target.value) }} />
i am a bit puzzled with the logic when reading the below code, although the code is working but not exactly as i would like it to behave.
3 queries i have if some one can please clarify.
1- As i understand useEffect is used to invoke the function after render, but in the below code, once the form is sumbitted (onSubmit={credentialVerify}) it will call the credentialVerify() function as below, so i dont think we need useEffect here, but still the code doesnt call the API unless i use the useEffect statement.
2- Also doesnt wait for me to enter my credentails first and as soon as i go to the Signin page it will fetch the API’s (when using useEffect ) and shows the result in the windows, but i try to design in a way that when i click button then it will fetch the API
3- when in the form onsubmit call the credentialVerify function, i have console.log(e) but it is showing as undefined, but as i understand onsubmit will call the function and through the event argument by default.
Below is the snippet of my code.
Any help Appreciated.
import React, { useState, useEffect } from "react";
import "../App.css";
import { Link } from "react-router-dom";
function Signin() {
const [name, setName] = useState("");
const [password, setPassword] = useState("");
const updateName = (e) => {
setName(e.target.value);
};
const updatePassword = (e) => {
setPassword(e.target.value);
};
const [items, setItems] = useState([]);
useEffect(() => { //Point-1 useEffect- API not call atall without this statement
credentialVerify();
}, []);
const credentialVerify = async (e) => {
console.log(e); //Point-3 this is coming as undefined
const data1 = await fetch("http://localhost:5000/api/customers");
const incomingdata = await data1.json();
console.log(data1);
console.log(incomingdata);
console.log(name, password);
setItems(incomingdata);
};
return (
<div>
<div>
{
<form className="formstyle" onSubmit={credentialVerify}>
<input
type="text"
placeholder="Username"
name="username"
value={name}
onChange={updateName}
/>
<input
type="text"
placeholder="Password"
name="password"
value={password}
onChange={updatePassword}
/>
<button type="submit">Submit</button>
</form>
}
</div>
<div>
{items.map((entry) => {
let key = entry.email;
let valuefirst = entry.firstName;
let valuelast = entry.created_at;
return (
<p key={key}>
{key}: {valuefirst}bb {valuelast}
</p>
);
})}
</div>
</div>
);
}
export default Signin;
For your first question, you are correct - it doesn't make sense to call credentialVerify when your component renders for the first time since that seems to be the handler for when your form gets submitted. Unless you're fetching data prior to displaying your form, you can drop the useEffect hook entirely.
This is also takes care of your second question because the hook will run once when your component renders for the first time, which is indicated by the empty array [] used as a dependency array of the useEffect hook. This is equivalent to componentDidMount in a class-based component, but again, it doesn't make sense to call credentialVerify at this point.
As for your third question, you should probably do something like the following:
const credentialVerify = event => {
event.preventDefault();
(async () => {
const data = await fetch("http://localhost:5000/api/customers")
.then(res => res.json());
.catch(e => e);
console.log(incomingData);
// ...
})();
}
Since you're passing an asynchronous function as your event handler, you might have issues accessing the SyntheticEvent object due to the reasons stated in React docs:
The SyntheticEvent is pooled. This means that the SyntheticEvent object will be reused and all properties will be nullified after the event callback has been invoked. This is for performance reasons. As such, you cannot access the event in an asynchronous way.
reactjs.org/docs/events.html#event-pooling
Your final component should look like the following:
function Signin() {
const [name, setName] = useState("");
const [password, setPassword] = useState("");
const [items, setItems] = useState([]);
const updateName = e => {
setName(e.target.value);
};
const updatePassword = e => {
setPassword(e.target.value);
};
const credentialVerify = event => {
event.preventDefault();
(async () => {
const incomingdata = await fetch("http://localhost:5000/api/customers")
.then(res => res.json())
.catch(e => e);
console.log(incomingdata);
console.log(name, password);
setItems(incomingdata);
})();
};
return (
<div>...</div>
);
}