I've been stuck on this all day, and I'd appreciate some help. Thanks in advance.
At first I was writing it like this, but I'd get a type error: todos.map is not a function.
function toggleState() {
setTodos(state => ({ ...state, isComplete: !state.isComplete }))
}
Finally I realized that error was because it was returning todos as an object, so I tried this:
function toggleState() {
setKeywords(state => [{ ...state, isUsed: !state.isUsed }])
}
Now I'm not getting the type error, but it's still not working as expected. Here's the state before toggleState:
[
{
"name": "State",
"value": [
{
"todo": "Learn React",
"id": "91bad41d-1561-425a-9e77-960f731d058a",
"isComplete": false
}
]
and here's state after:
[
{
"name": "State",
"value": [
{
"0": {
"todo": "Learn React",
"id": "91bad41d-1561-425a-9e77-960f731d058a",
"isComplete": false
},
"isComplete": true
}
]
Here's the rest of my code:
import React, { useState, useEffect } from 'react'
import { uuid } from 'uuidv4'
import { Form, FormGroup, Input, Button } from 'reactstrap'
function Example(props) {
const [todos, setTodos] = useState([])
// Run when component first renders
useEffect(() => {
console.log('useEffect component first rendered')
if (localStorage.getItem('todoData')) {
setTodos(JSON.parse(localStorage.getItem('todoData')))
}
}, [])
// Run when todos state changes
useEffect(() => {
console.log('useEffect todos changed')
localStorage.setItem('todoData', JSON.stringify(todos))
}, [todos])
const [formInput, setFormInput] = useState()
function handleChange(e) {
setFormInput(e.target.value)
}
function handleSubmit(e) {
e.preventDefault()
setTodos(prev => prev.concat({ todo: formInput, id: uuid(), isComplete: false }))
setFormInput('')
}
function toggleState() {
setTodos(state => [{ ...state, isComplete: !state.isComplete }])
}
return (
<div className='text-center'>
<div className='mb-2 border text-center' style={{ height: '300px', overflowY: 'scroll' }}>
{todos.map(todo => (
<p className={todo.isUsed ? 'text-success my-1' : 'text-danger my-1'} key={todo.id}>
{todo.todo}
</p>
))}
</div>
<Form onSubmit={handleSubmit}>
<FormGroup>
<Input onChange={handleChange} type='text' name='text' id='todoForm' placeholder='Enter a todo' value={formInput || ''} />
<Button>Set Todo</Button>
</FormGroup>
</Form>
<Button onClick={toggleState}>Toggle isComplete</Button>
</div>
)
}
export default Example
The method that I end up using, and I've seen other developers do, is to copy the object or state first, do modifications to it, and then set the new state with the modified state.
I also noticed you need to provide an index for the todos to be able to toggle them, so I added that functionality.
Take a look at a working example, click "Run code snippet" below.
// main.js
// IGNORE THIS BECAUSE THIS IS JUST TO USE REACT IN STACK OVERFLOW
const { useEffect, useState } = React;
// ---- CODE STARTS HERE -----
const Example = (props) => {
const [todos, setTodos] = useState([]);
const [formInput, setFormInput] = useState('');
// Run when component first renders
useEffect(() => {
/*
// Uncomment - Just doesn't work in Stack Overflow
if (localStorage && localStorage.getItem('todoData')) {
setTodos(JSON.parse(localStorage.getItem('todoData')));
}
*/
}, []);
// Hooks
const handleChange = event => {
setFormInput(event.target.value);
};
const handleSubmit = event => {
const newTodosState = [...todos ]; // make copy
newTodosState.push({ todo: formInput, isComplete: false });
setTodos(newTodosState);
// Add functionality to update localStorage
// ex:
// localStorage.setItem('todoData', newTodosState);
// Reset form
setFormInput('');
event.preventDefault();
};
const toggleTodoState = index => event => {
const newTodosState = [...todos ]; // make copy
newTodosState[index].isComplete = !newTodosState[index].isComplete;
setTodos(newTodosState);
// Add functionality to update localStorage
};
const handleDelete = index => event => {
const newTodosState = [...todos.slice(0, index), ...todos.slice(index + 1) ];
setTodos(newTodosState);
// Add functionality to update localStorage
}
// Render
return (<div>
<h3>Todos</h3>
<ul>
{todos.map((item, index) => <li key={`todo-${index}`}>{item.todo} - <input type="checkbox" checked={item.isComplete} onClick={toggleTodoState(index)} /> - <button onClick={handleDelete(index)}>Delete</button></li>)}
</ul>
<hr />
<form onSubmit={handleSubmit}>
<input type="text" value={formInput} onChange={handleChange} placeholder="Enter todo name" />
<button type="submit">Add</button>
</form>
</div>);
};
ReactDOM.render(<Example />, document.querySelector('#root'));
<body>
<div id="root"></div>
<script src="https://unpkg.com/babel-standalone#6/babel.min.js"></script>
<script src="https://unpkg.com/react#16/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.production.min.js"></script>
<script type="text/babel" src="main.js"></script>
</body>
So in your specific case, where you just want to toggle the isComplete on the first item, could be achieved like this:
function toggleState() {
setTodos(([firstItem, ...remainder]) => {
return [
{
...firstItem,
isComplete: !firstItem.isComplete
},
...remainder
];
});
}
Where we use Destructuring assignment to get the FirstItem and manipulate that, and spread the reminder back into the state.
Related
I am trying to refactor my code into components. So right now I want to refactor the AddName component. This AddName component is to be pass to an event handler <form>(below the header Add Info). The problem right now is that now after refactoring the code, exporting and importing it and passing it to the event handler, I got an error in my VS code saying setPersons is assigned but never used. This is not something that I expect it to be since the other props in the AddName component have no problems but only this setPersons. I only know that to pass prop in between components, usually one will do it like this,
<AddName setPersons={setPersons}/>
In this situation it is not appropiate to use something like this right ? How can I fix this ? Below are my code,
App.js
import { useState } from 'react'
import AddName from './components/AddName'
const App = () => {
const [persons, setPersons] = useState([
{ name: 'Arto Hellas', number: '012-3456789', id: 1 },
{ name: 'Ada Lovelace', number: '013-4567890', id: 2 },
{ name: 'Dan Abramov', number: '011-1234567', id: 3 },
{ name: 'Mary Poppendieck', number: '016-5556789', id: 4 }
])
const [newName, setNewName] = useState('')
const [newNumber, setNewNumber] = useState('')
const [searchTerm, setSearchTerm] = useState("")
const handleNameChange = (event) => {
setNewName(event.target.value)
}
const handleNumberChange = (event) => {
setNewNumber(event.target.value)
}
return (
<div>
<h2> Phonebook </h2>
<input type="text" placeholder="Search..." onChange= {(event) => setSearchTerm(event.target.value)} />
<h2> Add Info </h2>
<form onSubmit={AddName}>
<div>
name: <input value={newName} onChange={handleNameChange} />
</div>
<div>
phonenumber: <input value={newNumber} onChange={handleNumberChange} />
</div>
<div>
<button type="submit"> add </button>
</div>
</form>
<h2> Numbers </h2>
<ul>
{persons.filter((person) => {
if (searchTerm === "") {
return person
} else if (person.name.toLowerCase().includes(searchTerm.toLowerCase())) {
return person
}
}).map((person) => {
return (
<li key={person.name}> {person.name} {person.number} </li>
);
})
}
</ul>
</div>
)
}
export default App
AddName.js
const AddName = (props, event) => {
event.preventDefault()
const nameObject = {
name: props.newName,
number: props.newNumber2
}
const isNameExist = (value) => props.persons.some(person => person.name.includes(value))
if (isNameExist(nameObject.name)) {
alert("name already exist")
} else {
props.setPersons(props.persons.concat(nameObject))
props.setNewName('')
props.setNewNumber('')
}
}
export default AddName
Why whould you pass a component to an onSubmit handler? Do you expect anything to render? I would have a simple function in the App component like this:
const addName = (event) => {
event.preventDefault()
const nameObject = {
name: newName,
number: newNumber2
}
const isNameExist = (value) => persons.some(person => person.name.includes(value))
if (isNameExist(nameObject.name)) {
alert("name already exist")
} else {
setPersons(persons.concat(nameObject))
setNewName('')
setNewNumber('')
}
}
...
...
<form onSubmit={addName}>
...
I would also extract the list in another component otherwise also the list would rerender at all the state changes (i.e. at all input change)
So I have a component TaskList:
const [incompleteTasks, setIncompleteTasks] = useState<any[]>([]);
useMemo(() => {
const filteredTasks = TaskStore.tasks.filter(
(task: any) => !task.completed
);
setIncompleteTasks(filteredTasks);
}, [TaskStore.tasks]);
and the observable state is passed as a dependancy from TaskStore:
public tasks: Task[] = [];
constructor() {
makeAutoObservable(this);
}
#action setCompleted = (task: Task, completed: number) => {
const index = this.tasks.indexOf(task);
if (index !== -1) {
this.tasks[index].completed = !!completed;
}
};
I thought the way useMemo() works is that it caches the calculation in the first parameter(so the filtered array), and then the second parameter is the dependancy which triggers a useMemo() to return another calculation of the first parameter if it changes. Is my understanding wrong? Or am I not doing it correctly?
You've said you believe TaskStore.tasks to be observable because you're using makeAutoObservable, so in theory that shouldn't be the problem. My Mobx is very rusty (I haven't used it in years, though I like the idea of it), but since you're using Mobx, I suspect you want a computed, not useMemo, for what you're doing. See the end of the answer for an example using Mobx where incompleteTasks is a computed.
Using useMemo
But if you want to use useMemo:
Using useMemo's callback to call a state setter is incorrect. Instead, incompleteTasks should be the result of useMemo, not a state member, because it's derived state, not primary state:
const incompleteTasks = useMemo(
() => TaskStore.tasks.filter((task: any) => !task.completed),
[TaskStore.tasks]
);
useMemo will only call your callback if TaskStore.tasks changes;¹ otherwise, when called, it will return the previously-returned value instead. So your code only filters when necessary.
Here's a really simple non-Mobx example of derived state:
const { useState, useCallback, useMemo, useEffect } = React;
const initialTasks = [
{ id: 1, text: "first task", completed: false },
{ id: 2, text: "second task", completed: false },
{ id: 3, text: "third task", completed: false },
{ id: 4, text: "fourth task", completed: false },
{ id: 5, text: "fifth task", completed: false },
{ id: 6, text: "sixth task", completed: false },
];
const Task = ({ task, onChange }) => {
const handleChange = ({ currentTarget: { checked } }) => onChange(task, checked);
return (
<label>
<input type="checkbox" checked={task.completed} onChange={handleChange} /> {task.text}
</label>
);
};
const Example = () => {
const [counter, setCounter] = useState(0);
const [tasks, setTasks] = useState(initialTasks);
const incompleteTasks = useMemo(() => {
console.log(`Updating incompleteTasks.`);
return tasks.filter((task) => !task.completed);
}, [tasks]);
const updateTask = useCallback((task, completed) => {
setTasks((tasks) => tasks.map((t) => (t === task ? { ...t, completed } : t)));
}, []);
// A pointless counter just to show that `incompleteTasks` isn't recreated
// on every render
useEffect(() => {
const timer = setInterval(() => {
setCounter((c) => c + 1);
}, 1000);
return () => {
clearInterval(timer);
};
}, []);
// ...code to add/remove tasks not shown for brevity...
return (
<div>
<div>
Counter: {counter}
</div>
<h3>All tasks:</h3>
<ul>
{tasks.map((task) => (
<li key={task.id}>
<Task task={task} onChange={updateTask} />
</li>
))}
</ul>
<h3>Incomplete tasks:</h3>
<ul>
{incompleteTasks.map((task) => (
<li key={task.id}>
<Task task={task} onChange={updateTask} />
</li>
))}
</ul>
</div>
);
};
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Example />);
<div id="root"></div>
<div style="height: 50rem"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
Notice how even though the component is re-rendered every time counter changes, incompleteTasks is only re-calculated when tasks changes (for instance, because one of the tasks is marked completed/uncompleted).
Using Mobx's computed
But I suspect you don't want to use useMemo at all. Instead, you probably want to make incompleteTasks a Mobx computed. I haven't used Mobx in several years, but here's an example I put together using their documentation, particular this, this, and this:
const { useState, useCallback, useEffect } = React;
const { makeAutoObservable, action } = mobx;
const { observer, computed } = mobxReactLite;
class TaskStore {
tasks = [];
constructor() {
makeAutoObservable(this);
}
get incompleteTasks() {
console.log("Computing incompleteTasks...");
return this.tasks.filter((task) => !task.completed);
}
}
const taskStore = new TaskStore();
taskStore.tasks.push(
{ id: 1, text: "first task", completed: false },
{ id: 2, text: "second task", completed: false },
{ id: 3, text: "third task", completed: false },
{ id: 4, text: "fourth task", completed: false },
{ id: 5, text: "fifth task", completed: false },
{ id: 6, text: "sixth task", completed: false }
);
const Task = ({ task, onChange }) => {
const handleChange = ({ currentTarget: { checked } }) => onChange(task, checked);
return (
<label>
<input type="checkbox" checked={task.completed} onChange={handleChange} /> {task.text}
</label>
);
};
const Example = observer(({ taskStore }) => {
const [counter, setCounter] = useState(0);
const updateTask = useCallback(action((task, completed) => {
task.completed = !task.completed;
}), []);
// A pointless counter just to show that `incompleteTasks` isn't recreated
// on every render
useEffect(() => {
const timer = setInterval(() => {
setCounter((c) => c + 1);
}, 1000);
return () => {
clearInterval(timer);
};
}, []);
// ...code to add/remove tasks not shown for brevity...
return (
<div>
<div>Counter: {counter}</div>
<h3>All tasks:</h3>
<ul>
{taskStore.tasks.map((task) => (
<li key={task.id}>
<Task task={task} onChange={updateTask} />
</li>
))}
</ul>
<h3>Incomplete tasks:</h3>
<ul>
{taskStore.incompleteTasks.map((task) => (
<li key={task.id}>
<Task task={task} onChange={updateTask} />
</li>
))}
</ul>
</div>
);
});
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Example taskStore={taskStore} />);
<div id="root"></div>
<div style="height: 50rem"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mobx/6.7.0/mobx.umd.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mobx-react-lite/3.4.0/mobxreactlite.umd.development.js"></script>
¹ In theory. The useMemo documentation says it's only a performance optimiation, not a semantic guarantee, but that's all you need for this code.
Your understanding of useMemo is correct. It does cache the result of a calculation between re-renders.
Your code should be more like
return useMemo(() => {
const filteredTasks = TaskStore.tasks.filter(
(task: any) => !task.completed
);
setIncompleteTasks(filteredTasks);
}, [TaskStore.tasks]);
I have this error that says everytime I'll submit it:
TypeError: s.indexOf is not a function
import React, { useState } from "react";
const checkboxes = [
{ id: 1, text: "Checkbox 1" },
{ id: 2, text: "Checkbox 2" },
{ id: 3, text: "Checkbox 3" }
];
const SearchResults = () => {
const [selectedCheckbox, setSelectedCheckbox] = useState([]);
const handleChange = (id) => {
const findIdx = selectedCheckbox.indexOf(id);
if (findIdx > -1) {
selectedCheckbox.splice(findIdx, 1);
} else {
selectedCheckbox.push(id);
}
setSelectedCheckbox(selectedCheckbox);
};
const handleSubmit = (e) => {
e.preventDefault();
console.log(JSON.stringify(selectedCheckbox));
try {
const userRef = db.collection("users").doc(uid);
const ref = userRef.set({
selectedCheckbox
});
console.log("done")
} catch (err) {
console.log(err);
}
};
return (
<div>
<div className="App">
<form onSubmit={handleSubmit}>
{checkboxes.map((checkbox) => (
<label key={checkbox.id}>
{checkbox.text}
<input
type="checkbox"
onChange={() => handleChange(checkbox.text)}
selected={selectedCheckbox.includes(checkbox.id)}
/>
</label>
))}
<button type="submit">Submit</button>
</form>
</div>
<p>Selected checkboxes: {JSON.stringify(selectedCheckbox)}</p>
</div>
);
};
This is the codesandbox, though there's not Firestore data here:
https://codesandbox.io/s/handle-multiple-checkboxes-in-react-forked-xehxf?file=/src/App.js:0-2520
Any help would be appreciated. Thank you.
I suspect the error rises when your tracking array of selectedCheckboxes goes undefined even though this is not quite the case in the linked code sandbox.
In React as a general rule, you should never mutate the state even using React hooks which is you case in the handleChange method.
You should update you event handler method as follows (always making a copy of the state array to construct a the new one):
const handleChange = (id) => {
const findIdx = selectedCheckbox.indexOf(id);
let selected;
if (findIdx > -1) {
selected = selectedCheckbox.filter((checkboxId) => checkboxId !== id);
} else {
selected = [...selectedCheckbox, id];
}
setSelectedCheckbox(selected);
};
You click event handler takes the checkbox id as an argument and you should update you click handler accordingly:
<label key={checkbox.id}>
{checkbox.text}
<input
type="checkbox"
onChange={() => handleChange(checkbox.id)}
selected={selectedCheckbox.includes(checkbox.id)}
/>
</label>
I have a global state of the format like this:
{
type: "container",
items: [
{
type: "box"
},
{
type: "container",
items: [
type: "container",
items: [
{
type: "box",
color: "green"
},
{
type: "box",
color: "red"
},
{
type: "box"
}
]
]
}
]
}
The state above will produce the output below:
output
As you can see, we have only two elements: containers and boxes.
Containers have buttons and can contain either containers or boxes.
Boxes can only change their colors.
To understand better watch this demo.
I have been able to replicate this behavior, but I want to be able to extract the whole state of the app and be able to restore it.
There is no problem with restoring states. If I pass a json like above, it will work.
But when it comes to saving my current state, problems arise.
You see, when I try to restore my state, I pass nested object as a prop and then it becomes the local state of that child container. But when I change the state of that child container, it obviously doesn't change the global state (the json above).
So I tried 2 methods to change the global state:
Here is my code:
Create onChange method that will trigger when some local state changes. Then pass that method as a prop, and call that method by passing the changed state as a parameter. (You can see this in my code)
Pass path (list of indices) of the nested object as a prop to restore to the form state.items[path[0]].items[path[1]].items[path[n]] and then change it manually by setState hook callback.
In both cases, I get
Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.
and it just freezes.
Container.js
import React, { useState } from 'react'
import { v4 as uuidv4 } from 'uuid'
import Box from './Box'
function Container({ current, path, onChange }) {
const styles = ... some styles ...
const [popoverVisible, setPopoverVisibility] = useState(false)
const [state, setState] = useState(current || [])
React.useEffect(() => onChange(state, path), [state])
const handleButtonMouseOver = () => setPopoverVisibility(true)
const handleButtonMouseLeave = () => setPopoverVisibility(false)
const handlePopoverMouseOver = () => setPopoverVisibility(true)
const handlePopoverMouseLeave = () => setPopoverVisibility(false)
const props = {
container: { style: styles.container },
wrapper: { style: styles.wrapper },
popover: {
style: styles.popover,
onMouseOver: handlePopoverMouseOver,
onMouseLeave: handlePopoverMouseLeave
},
buttonAdd: {
style: styles.button,
onMouseOver: handleButtonMouseOver,
onMouseLeave: handleButtonMouseLeave
},
buttonBox: {
style: styles.button,
onClick: () => setState([...state, {type: 'box'}])
},
buttonContainer: {
style: styles.button,
onClick: () => setState([...state, {type: 'container', items: []}])
//onClick: () => setState({...state, items: [...state.items, {type: 'container', items: []}]})
}
}
return (
<>
<div {...props.container}>
{state.map((x, i) => x.type === 'container' ? <Container key={uuidv4()} current={x.items} onChange={ onChange } path={[...path, i]}></Container> : <Box key={uuidv4()}></Box>)}
<div {...props.wrapper}>
<div>
<div {...props.popover}>
{popoverVisible && <button {...props.buttonBox}>Box</button>}
{popoverVisible && <button {...props.buttonContainer}>Container</button>}
</div>
<button {...props.buttonAdd}>Add</button>
</div>
</div>
</div>
</>
)
}
export default Container
App.js
import { useState, useEffect } from 'react'
import Container from './Container.js'
function App() {
const styles = {
button: {
margin: ".5em",
width: "7em",
height: "3em",
border: "1px solid black",
borderRadius: ".25em"
}
}
const [state, setState] = useState({type: "container", items: []})
const [newState, setNewState] = useState("")
const [generatedJson, setGeneratedJson] = useState("")
const handleContainerChange = (s, p) => setState({...state, items: s})
const handleTextareaChange = e => setNewState(e.target.value)
const handleBuildButtonClick = () => setState(JSON.parse(newState))
const handleCreateJsonButtonClick = () => setGeneratedJson(JSON.stringify(state))
//useEffect(() => {alert(state)}, [state])
return (
<>
<div style={{ display: "flex" }}>
<Container key={state} current={state.items} path={[]} onChange={ handleContainerChange }>
</Container>
</div>
<textarea cols="30" rows="5" onChange={ handleTextareaChange }></textarea><br/>
<button style={styles.button} onClick={ handleBuildButtonClick }>Build</button><br/>
<button style={styles.button} onClick={ handleCreateJsonButtonClick }>Create JSON</button>
<label key={generatedJson}>{generatedJson}</label>
</>
)
}
export default App
So what I want is to update global state, when local state changes.
Example:
globalState = {
type: "container",
items: [
{ type: "container", items: [] }
]
}
// that inner container from the global state
localState = { type: "container", items: [] }
// so when it changes to
localState = {
type: "container",
items: [
{type: "box"}
]
}
// i want the global state to become
globalState = {
type: "container",
items: [
{
type: "container",
items: [
{type: "box"}
]
}
]
}
How can I do that?
Issue
I think the main issue is the usage of state as a React key on the Container component in App. Each time state updates you are specifying a new React key, and React will handle this by unmounting the previous version and mounting a new version of Container.
<Container
key={state} // <-- new key each state update
current={state.items}
path={[]}
onChange={ handleContainerChange }
>
</Container>
Each time Container mounts it will instantiate state and run the useEffect, which calls the passed onChange prop.
const [state, setState] = useState(current || []);
React.useEffect(() => onChange(state, path), [state]);
Solution
Remove the React key on Container in App.
<Container
current={state.items}
path={[]}
onChange={ handleContainerChange }
/>
I'm trying to make a search field with a dropdown list.For some reason, after mapping over an array and making a list of a few "li" the onClick handler sends to suggestionSelected (value) function 3 values without even clicking on any of them!
I'm enclosing the screenshot depicting my attempts to enter the name of a city starting with "s". See the output in the console.
The piece of code in question is commented.
import React from 'react';
import shortid from 'shortid';
import modules from './CityForm.module.css';
export default class ToDoForm extends React.Component {
state = {
text: '',
items: ["Moscow", "Saratov", "Singapore", "New York"],
suggestions: []
};
handleChange = (event) => {
this.setState({
text: event.target.value
});
const value = event.target.value;
let suggestions = [];
if (value.length > 0) {
const regex = new RegExp(`^${value}`, 'i');
suggestions = this.state.items.sort().filter(v => regex.test(v));
}
this.setState({
suggestions: suggestions
});
}
renderSuggestions() {
const { suggestions } = this.state;
if (suggestions.length === 0) {
return null;
} else {
return (
<ul className={modules.dropdown_list}>
{suggestions.map((item) => <li onClick={this.suggestionSelected(item)}> {item} </li>)}
</ul>
)
}
}
handleSubmit = (event) => {
event.preventDefault();
this.props.onSubmit({
id: shortid.generate(),
text: this.state.text
});
this.setState({
text: ''
});
}
suggestionSelected(value) {
console.log(value);
// this.setState({
// text: value,
// suggestions: []
// });
}
render() {
const { items } = this.state;
return (
<form
className={modules.search_row}
onSubmit={this.handleSubmit}>
<div>
<input
autoComplete="off"
className={modules.input}
name={modules.text}
value={this.state.text}
onChange={this.handleChange}
placeholder="E.g. Moscow..."
onKeyUp={this.filterOptions}
/>
{this.renderSuggestions()}
</div>
<button
className={modules.addcity_btn}
onClick={this.handleSubmit}>
Add city
</button>
</form>
)
}
}
try change your image function call to
{ suggestions.map((item) => <li onClick={() => this.suggestionSelected(item)}> {item} </li> ) }
by passing it as onClick={this.suggestionSelected(item)}, it is actually calling the function this.suggestionSelected(item) when rendering
if you wish to pass something back as a parameter in a callback assignment itself, pass it as a function reference
onClick={() => this.suggestionSelected(item)}
Wrong :<li onClick={this.suggestionSelected(item)}>
Correct: <li onclick={(evt)=>this.suggestionSelected(item) }