Cant get the selected value from input - javascript

I'm trying to get the task value from the button when pressed to show it in the side nav, but the value is always empty even though it's not. help appreciated.
See the comments in the code for more details:
import './index.css';
import React, {useState} from 'react';
function App() {
return (
<div>
<TodoListItem />
</div>
);
}
const TodoListItem = (props) => {
//for task list
const [tasks, setTasks] = useState(['']);
//toggling the side menu
const [toggle, setToggle]= useState(true);
//toggling grid layout
const [grid, setGrid]= useState('');
//getting the selected item !! not working
const [selectedTask, setSelectedTask]= useState('');
//brings out the side nav bar
const TodoItemDetails = () => {
setToggle(false)
setGrid("grid grid-cols-2")
}
const onFormSubmit = e => {
e.preventDefault();
}
return (
<div class="bg-gray-100 items-center justify-center min-h-screen min-w-screen p-4">
<div class={grid}>
{/* grid one */}
<div>
{/* task form */}
<form onSubmit={onFormSubmit} class="bg-white rounded py-4 px-2 my-4 h-16 shadow-sm">
<input
class="w-[92%] h-full float-left focus:outline-none placeholder-blue-500 ml-2 py-1 focus:placeholder-black"
type="text"
id="task"
placeholder="Add a task"
/>
{/* task add button*/}
<button
type="submit"
class="text-blue-500 float-right text-2xl -translate-y-0.5 -translate-x-1"
onClick={() => {
let taskdom = document.getElementById("task");
{/* creates individual task */}
let task = <button
onClick={() => {
{/* nav bar comes out whne the invidual task is pressed with the task value */}
TodoItemDetails();
{/* the below setSelectedTask should set */}
{/* the selected task and get the value from taskdom.value but its always empty (cont) */}
setSelectedTask(taskdom.value);
}}
{/* even though taskdom.value works properly right after that */}
class="bg-white w-full hover:bg-gray-200 p-4 my-1 rounded shadow-sm text-left">{taskdom.value}</button>
{/* adds the new task to the the task array */}
setTasks((oldTasks) => [ ...oldTasks, task ])
{/* empties the task text box */}
taskdom.value = "";
}}
>+</button>
</form>
{/* shows all the tasks */}
{tasks}
</div>
{/* grid two: side nav bar */}
<div hidden={toggle}>
{/* nav bar hides itself when this is pressed !!!! this value is supposed to be from the pressed value but its empty */}
<button onClick={() => {
setToggle(true)
setGrid("")
}}>{selectedTask}</button>
</div>
</div>
</div>
);
}
export default App;
sorry for bad formatting...this is the first time I'm posting a question even though I've been using stack overflow for 2 years and I don't know how to exactly ask this question...

The React way to do this is to use a controlled input element and store the value in local state on change. You can then get it from the state when the button is clicked.

You could use a useRef hook on your input
// logic
const [selectedTask, setSelectedTask]= useState('');
const inputValue = useRef('');
const handleInput = () => {
setSelectedTask(inputValue.current.focus());
}
// render
<input type="text" id="task" ref={inputValue} />
<button onClick={handleInput}>Click</button>
Then you should be able to use the state.

Related

UI Not updating using Formik setFieldValue

My formik form contains a custom textfield component. The text field component reads the formik context and populates based on the field value passed into it. When i invoke setFieldValue(), the value changes in the formik context but the UI element(textField) does not update. If i navigate to the next step in my form and then back the new values are reflected. So i'm assuming my textfield component is not rerendering when i invoke setFieldValue().
How can i get my textbox element to update when i invoke setFieldValue?
Custom Textfield
import { FieldHookConfig, useField } from 'formik'
import React from 'react'
const TextField = ({
label,
...props
}) => {
const [field, meta, helper] = useField(props)
return (
<div className="relative mb-2 flex w-full flex-col px-1 text-gray-800">
<input
onChange={(e) => helper.setValue(e.target.value.trim())}
onBlur={field.onBlur}
{...props}
id={props.name}
placeholder={label.split('-')[0]}
defaultValue={field.value}
/>
<label
className="absolute -top-2.5 left-3 mt-1 inline-block bg-white text-xs text-gray-400"
htmlFor={field.name}
>
{label}
</label>
{meta.error && meta.touched && (
<p className="px-2 text-sm text-red-500">{meta.error}</p>
)}
</div>
)
}
I then use a simple onclick to set the fieldvalue.
onClick Function
function handleAdaSelect(e) {
console.log(e)
formik.setFieldValue('name', e.value)
}
Here is a sample of how the Textfield is invoked.
<TextField
label="Name"
name="name"
type="text"
disabled
className="border-0 focus:outline-none"
/>
const TextField = ({
label,
name,
...props
}) => {
const [field, meta, helper] = useField(name)
You should pass only the name property to the useField()

React - List icons with different param

I'm trying to use the map function but I can't get it right.
I have a side-bar which I want to show some icons. Here is an example without the map.
const SideBar = () => {
return (
<div className="fixed top-0 left-0 h-screen w-20 m-0 flex flex-col bg-gray-100 text-white shadow-lg">
<SideBarIcon icon={<FaFire size="30" />} />
<SideBarIcon icon={<FaPoo size="30" />} />
</div>
);
};
const SideBarIcon = ({ icon, text = "tooltip 💡"}) => (
<div className="sidebar-icon group">
{icon}
<span class="sidebar-tooltip group-hover:scale-100">{text}</span>
</div>
);
Here is an example with the map function
const SideBar = () => {
const icons = [FaFire, FaPoo];
return (
<div className="fixed top-0 left-0 h-screen w-20 m-0 flex flex-col bg-gray-100 text-white shadow-lg">
{icons.map(function(icon) {
return <SideBarIcon icon={<icon size="30"/>}/>
})}
</div>
);
};
const SideBarIcon = ({ icon, text = "tooltip 💡"}) => (
<div className="sidebar-icon group">
{icon}
<span class="sidebar-tooltip group-hover:scale-100">{text}</span>
</div>
);
Can you tell me what I'm doing wrong?
Thank you for your time!
By simply putting icon inside the tags, it thinks you're rendering an HTML element called icon, therefore it's not rendering the mapped item. It also wouldn't work if you set it as <{icon}/>, because it would be trying to render an empty element.
Luckily, there's an easy fix -- Just capitalize Icon, and React will render it as a JSX Component.
{icons.map(function(Icon) {
return <SideBarIcon icon={<Icon size="30"/>}/>
})}
{icons.map(function (Icon) {
return <SideBarIcon icon={<Icon size="30"/>}/>
})}
Components start with capital letters, just change icon to Icon.
Please do not forget to use key prop for your mapped items.
https://reactjs.org/docs/jsx-in-depth.html#user-defined-components-must-be-capitalized
const SideBar = () => {
const icons = [<FaFire size="30" />, <FaPoo size="30" />];
return (
<div className="fixed top-0 left-0 h-screen w-20 m-0 flex flex-col bg-gray-100 text-white shadow-lg">
{icons.map(function(icon) {
return <SideBarIcon icon={icon}/>
})}
</div>
);
};
Since you are using icon in lowercase, it is not recognized as a jsx component. To fix this, change it to uppercase.
turn this
{icons.map(function(icon) {
return <SideBarIcon icon={<icon size="30"/>}/>
})}
to this
{icons.map(function(Icon) {
return <SideBarIcon icon={<Icon size="30"/>}/>
})}
Also, if the underlying components allow it, you could use the direct invocation, like so:
return <SideBarIcon icon={icon({size:"30"})/>
Keep in mind, that in most cases that is not what you want, and it might introduce hard-to-fix bugs.

How do I use setstate on multiple and one wont affect the other?

I am creating Accodion which will have multiple items with TITLE and BODY.
When a title is clicked, I want the setShow to true. But when Item A is true, I don't want item B to be true at same time. I will be fetching those data from the Server and I don't know how my items i will have in the future.
const Accordion = ({ title, text }) => {
const [show, setShow] = React.useState(false);
return (
<>
<div className="accordion-wrapper">
<div
onClick={() => setShow(!show)}
className="accordion-title cursor-pointer flex flex-row flex-1 justify-between items-center"
>
<div className="title-text">
<h2>{title}</h2>
</div>
<div className="title-icon"></div>
</div>
<div className="accordion-content">{text}</div>
</div>
</>
);
};
export default Accordion;

Pass query from Search bar to results page React

Ive made a search and filtering bar as part of an application im making in React. The current way it works is suggestions appear as you type. However there is no handler for if the user just clicks the submit button. At the moment clicking the submit button will take you to a results page with the query in the page URL.
I would like this to be passed as a state when you click the link. This link could then be displayed in the Results component.
Ive attempted this but im fairly new to React so any help would be appreciated.
Heres the search component:
import * as React from 'react';
import { useState } from "react";
import { Link } from "react-router-dom";
const content = [
{link: '/review/elden-ring', name: 'Elden\nRing'},
{link: '/review/', name: 'defg'},
{link: '/review/', name: 'ghij'},
{link: '/review/', name: 'jklm'},
]
export default function Search(props) {
//For storing and setting search input
const [query, setQuery] = useState("");
return (
//Search input
<div class="flex flex-col z-10">
<form class="text-black ml-5 py-0.5 lg:py-0 flex border-2 border-gray-400 rounded-md bg-white px-1">
<input id="searchInput" class="focus:outline-none" type="text" placeholder="Search" value={query} onChange={event => {setQuery(event.target.value)}}/>
<div class="flex mt-1.5"> {/* Flex container to align the icon and bar */}
<Link to={{pathname: "/results/" + query, state: {query}}}> {/* Error handler as search is strick */}
<button type="submit" onClick={() => setQuery(() => "")}>
<svg class="fill-current h-auto w-4 " xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"> {/* ! Font Awesome Pro 6.1.0 by #fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. */}
<path d="M500.3 443.7l-119.7-119.7c27.22-40.41 40.65-90.9 33.46-144.7C401.8 87.79 326.8 13.32 235.2 1.723C99.01-15.51-15.51 99.01 1.724 235.2c11.6 91.64 86.08 166.7 177.6 178.9c53.8 7.189 104.3-6.236 144.7-33.46l119.7 119.7c15.62 15.62 40.95 15.62 56.57 0C515.9 484.7 515.9 459.3 500.3 443.7zM79.1 208c0-70.58 57.42-128 128-128s128 57.42 128 128c0 70.58-57.42 128-128 128S79.1 278.6 79.1 208z" />
</svg>
</button>
</Link>
</div>
</form>
{/* Search Suggestions */}
<div class="ml-5 px-0.5">
{/* Query must have length to prevent mapping by default */}
{query.length > 0 && content.filter((content) => {
//If input return object
if (query == "") {
return content
}
//If any input characters much object characters return corresponding object
else if (content.name.toLowerCase().includes(query.toLocaleLowerCase())) {
return content
}
})
//Maps element based on the number of json objects
.map((content) => {
return(
<div class="bg-white rounded-sm">
<Link to={content.link} onClick={() => setQuery(() => "")}><p>{content.name}</p></Link>
</div>
);
})};
</div>
</div>
);
};
Heres the Results component
import * as React from 'react';
export default function Results(props) {
return (
<h1>{props.location.state.query}</h1>
);
};
Routes
import * as React from 'react';
import './app.css';
import { Routes, Route } from "react-router-dom";
import Header from './components/header/header';
import Footer from './components/footer';
import Error from './components/error';
import Results from './components/results';
import Index from './components/index/index';
import ReviewsPage from './components/reviews/reviewsPage';
import Review from './components/reviews/review';
export default function App() {
return (
<>
<Header />
<Routes>
<Route path="/" element={<Index />} />
<Route path="/reviews" element={<ReviewsPage />} />
{/* Render review with ID for switch statment */}
<Route path="/review/:id" element={<Review />} />
<Route path="/results/:id" element={<Results />} />
<Route path="*" element={<Error />} />
</Routes>
<Footer />
</>
);
};
Search component import line 30
import * as React from 'react';
import Search from './search';
import { useState } from 'react';
import { Link } from 'react-router-dom';
export default function Header() {
//State to toggle navlinks on small screens
const [state, setState] = useState(false)
return (
<nav className=" w-full bg-red-500 shadow-lg relative max-h-[4.1rem]"> {/* Max height set to avoid search suggestions increasing header size */}
<div className="flex justify-between py-3.5 w-full px-3 md:w-2/3 md:px-0 m-auto">
{/* Logo */}
<Link className="text-2xl font-semibold text-white hover:animate-pulse whitespace-nowrap" to="/">GAME REVIEWS</Link>
<div className="flex max-h-[3rem]"> {/* Container to prevent flex effecting both parents container */}
{/* Links */}
{!state && (
<ul id="links" className=" h-40 lg:h-auto flex-col flex lg:flex-row absolute lg:relative mt-10 lg:mt-0 right-0 lg:right-auto px-10 lg:px-0 bg-red-500 rounded-lg lg:rounded-none shadow-sm lg:shadow-none">
<li className="m-5 lg:my-0 lg:mx-5">
<Link className="text-2xl text-white border-none hover:border-solid border-b-2 border-white" to="/">Home</Link>
</li>
<li className="m-5 lg:my-0 lg:mx-5">
<Link className="text-2xl text-white border-none hover:border-solid border-b-2 border-white" to="/reviews">Reviews</Link>
</li>
</ul>
)}
{/* Search bar */}
<Search />
{/* Hamburger */}
<div id="hamburger" onClick={() => setState(!state)} className=" space-y-2 ml-5 mt-2 block cursor-pointer lg:hidden">
<div className="w-6 h-0.5 bg-white"></div>
<div className="w-6 h-0.5 bg-white"></div>
<div className="w-6 h-0.5 bg-white"></div>
</div>
</div>
</div>
</nav>
)
}
Heres an example of what I want to achieve
User searches 'game'
Upon clicking the icon on the right they should be redirected to my results page. This page should show what they just entered on submit.
You can use dynamic route in the Link component which passes query in the URL. And to parse it in Result component, you can use match props.
To navigate change your Link component to
<Link to={"/results/" + query} />
And to parse the query in Result component, use
<h1>{props.match.params.id}</>
If you want the page results to be shared, you must include on the url the search term something like: www.yourdomain.com/review/elden-ring
Take a look and you will see that I've defined that the review route now expects a parameter. You should use that parameter to check all the data you need to display on the page.
And had to edit the Search component because you're using class instead of className for styling.
On the Results component I use the useParams hook to get the url params and show it on the h1. You should use this param as a key to retrieve the actual details of the review of your API.
This is how I'd do (all the logic):
On the App component I define the routes:
<Routes>
<Route exact path="/" element={<Search />} />
<Route path="/review/:query" element={<Results />} />
</Routes>
On the Search component:
// Router v6 hook to navigate the user
const navigate = useNavigate();
const queryRef = useRef(null) // Reference to the input
// Navigates the user to reviews/what they've written
const queryHandler = () => navigate(`/reviews/${queryRef.current.value}`);
return (
<>
// This is where the user types the query
<input type='text' ref={queryRef} placeholder='Search' />
<Icon onClick={queryHandler} /> // This has the onClick to hndle the click
</>
)
And on the results component:
const params = useParams(); // This retrieves all the params defined for the url
<h1>{params.query}</h1>
The query you're sending in the history.push() method must be an object. Instead, you are sending a string. Change it to object like below
props.history.push({
pathname: '/results',
state: { query }
});
For the following route.
localhost:3000/search/?query=ABCD
The following code on load extract the ABCD from query=ABCD and set to the state.
export default function App() {
const [query, setQuery] = useState(() => {
const q = new URLSearchParams(window.location.search);
console.log(q.toString());
return q.get("query") || "";
});
return (
<div className="App">
<h1>Query</h1>
<h2>{query}</h2>
</div>
);
}
So this way you can extract the info from a route.
Now if you want to know how to move from one page to another
assuming you are using some routing library, look at how you can change the route
history.push(`/search/?query={query}`)
is a way to use with react router ( ensure you use the useHistory hook for it )
It was far simpler than I thought and something I had done on a different page
Creating a state for the input.
Setting input as the variable in that state (query).
Setting the value as the input using an onClick on the button.
The link then provided the state variable with the route.
const [query, setQuery] = useState("");
<form
className="text-black ml-5 py-0.5 lg:py-0 flex border-2 border-gray-400 rounded-md bg-white px-1"
>
<input
id="searchInput"
className="focus:outline-none"
type="text"
placeholder="Search"
value={query}
onChange={(event) => {
setQuery(event.target.value);
}}
/>
{/* Flex container to align the icon and bar */}
<div className="flex mt-1.5">
<Link to={{ pathname: "/results/" + query }}>
<button type="submit" onClick={() => setQuery(() => "")}>
<svg
className="fill-current h-auto w-4 "
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
>
{/* ! Font Awesome Pro 6.1.0 by #fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. */}
<path d="M500.3 443.7l-119.7-119.7c27.22-40.41 40.65-90.9 33.46-144.7C401.8 87.79 326.8 13.32 235.2 1.723C99.01-15.51-15.51 99.01 1.724 235.2c11.6 91.64 86.08 166.7 177.6 178.9c53.8 7.189 104.3-6.236 144.7-33.46l119.7 119.7c15.62 15.62 40.95 15.62 56.57 0C515.9 484.7 515.9 459.3 500.3 443.7zM79.1 208c0-70.58 57.42-128 128-128s128 57.42 128 128c0 70.58-57.42 128-128 128S79.1 278.6 79.1 208z" />
</svg>
</button>
</Link>
</div>
</form>
Then in the route the path is given a variable (id)
<Route path="/results/:id" element={<Results />} />
This could then be pulled in the results page by useParams.
And assigned to my h1 tag.
import { useParams } from "react-router-dom";
export default function Results(props) {
const {id} = useParams();
return (
<h1>{id}</h1>
);
};
Thank you for everyone help and guidance.

The placeholder is not showing up at first instance because of i put value={newTodo} and i know that in first instance "newTodo" is empty. How to fix?

The "newTodo" state variable is empty in the first instance when i load the page, that's why the placeholder is not displaying, it act as if there is zero value in newTodo variable so placeholder is not showing up in the first instance, but after i enter a text then placeholder shows up.
import React, {useState} from 'react'
const ToDo = () => {
const [todo, setTodo] = useState([]);
const [newTodo, setNewTodo] = useState(" ");
let globalID = 0;
const handleSubmit = (e) => {
e.preventDefault();
setNewTodo("");
setTodo((oldTodo) => {
return [...oldTodo, newTodo]
});
}
const handleInput = (e) => {
setNewTodo(e.target.value);
}
return (
<>
<h1 className="header">Building To Do App</h1>
<section className='flex justify-center mt-8'>
<div className='border border-indigo-800 w-1/2 flex flex-col text-center h-96'>
<h2 className=' h-16 flex justify-center items-center bg-pink-600 text-white' >To-Do List</h2>
<form onSubmit={handleSubmit} className="mt-6">
<input placeholder='Add a Item' type="text" onChange={handleInput} name="todo" id="todo" value={newTodo} className='w-1/2 h-12 rounded-sm border-b-4 border-indigo-500 text-xl bg-gray-300 focus:outline-none text-black' />
<button className='bg-pink-600 rounded-full w-12 h-12 mx-4 hover:bg-green-700'>+</button>
</form>
<ol className='todoList'>
{todo.map((items) => {
return <li key={globalID++} >{items}</li>
} )}
</ol>
</div>
</section>
</>
)
}
export default ToDo
You set the default value newTodo value " " that's issue !
const [newTodo, setNewTodo] = useState();
Remove " " from useState(); solve the issue !

Categories