How to select DOM elements from map function in react component? - javascript

In this simple components I want to select all inputs and listen to a change event, but I got just a null node list, but me I want the selected inputs.
How I can select inputs inside this map function ?
const Component = () => {
const [categories, setCategories] = useState([]);
const [loading, setLoading] = useState(false);
const myFun = () => {
// I want to get all inputs of type cheeckbox;
const inps = document.querySelectorAll("input[type=checkbox]");
console.log(inps); // but I get nodeList[]
}
useEffect(() => {
setCategories([...Fetched Categories from the server])
setLoading(true);
myFunc();
}, []);
return <div id="component">
{ loading && (categories.map((c,i)=>(
<div className="form-group" key={i}>
<label htmlFor={c.id}>
<input type="checkbox" value={c.id} id={c.id} />
{c.title}
</label>
</div>
)))
}
</div>
}
export default Component;

There is no inputs when you call myFunc(). Your setLoading(true) will affect html only on next render.
The best way to access dom nodes in react is to use thise hook https://reactjs.org/docs/hooks-reference.html#useref

Related

How to write value to localStorage and display it in input on reload?

I have an input on the page, initially it is empty. I need to implement the following functionality: on page load, the component App fetches from localStorage a value of key appData and puts it in the input. That is, so that in the localStorage I write the value to the input and when reloading it is displayed in the input. How can i do this?
I need to use useEffect
import { useEffect, useState } from "react";
export default function App() {
const [userData, setUserData] = useState("");
useEffect(() => {
localStorage.setItem("Userdata", JSON.stringify(userData));
}, [userData]);
return (
<div>
<input value={userData} onChange={(e) => setUserData(e.target.value)}></input>
</div>
);
}
Use the change event to write to the localStorage, then use an init function in the useState hook.
import { useState } from 'react';
const loadUserData = () => localStorage.getItem('UserData') || '';
const saveUserData = (userData) => localStorage.setItem('UserData', userData);
export default const Application = () => {
const [ userData, setUserData ] = useState(loadUserData);
const handleUserDataUpdate = e => {
const userData = e.target.value;
setUserData(userData);
saveUserData(userData);
};
return <div>
<label htmlFor="testInput">Test Input</label>
<input id="testInput" value={ userData } onChange={ handleUserDataUpdate } />
</div>;
}
If you need an example using uncontrolled inputs, here is one using useEffect :
import { useEffect } from 'react';
const loadUserData = () => localStorage.getItem('UserData') || '';
const saveUserData = (userData) => localStorage.setItem('UserData', userData);
export default const Application = () => {
const inputRef = useRef();
useEffect(() => {
inputRef.current.value = loadUserData();
}, []); // initial load
const handleUpdateUserData = () => {
saveUserData(inputRef.current.value);
};
return <div>
<label htmlFor="testInput">Test Input</label>
<input ref={ inputRef } id="testInput" onChange={ handleUpdateUserData } />
</div>;
}
You can set a default value for the input inside state.
const [userData, setUserData] =
useState(JSON.parse(localStorage.getItem('Userdata')) || '');
So when the component mounts (after reload), the initial userData value is taken directly from the localStorage. If it's empty, the fallback value will be set ('').
Note: Make sure to add also the onChange handler to the input.

Why the filter does not return the list on the initial render?

What I have is a list that was fetched from an api. This list will be filtered based on the input. But at the first render it will render nothing, unless I press space or add anything to the input. Another solution is set the fetched data to the filteredList. But I don't know if it is the right thing to set the fetched data to two arrays.
import React, { useState, useEffect } from "react";
const PersonDetail = ({ person }) => {
return (
<div>
Id: {person.id} <br />
Name: {person.name} <br />
Phone: {person.phone}
</div>
);
};
const App = () => {
const [personsList, setPersonsList] = useState([]);
const [personObj, setPersonObj] = useState({});
const [showPersonDetail, setShowPersonDetail] = useState(false);
const [newPerson, setNewPerson] = useState("");
const [filter, setFilter] = useState("");
const [filteredList, setFilteredList] = useState(personsList);
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/users")
.then((response) => response.json())
.then((data) => {
setPersonsList(data);
//setFilteredList(data) <-- I have to add this to work
console.log(data);
});
}, []);
const handleClick = ({ person }) => {
setPersonObj(person);
if (!showPersonDetail) {
setShowPersonDetail(!showPersonDetail);
}
};
const handleChange = (event) => {
setNewPerson(event.target.value);
};
const handleSubmit = (event) => {
event.preventDefault();
const tempPersonObj = {
name: newPerson,
phone: "123-456-7890",
id: personsList.length + 1,
};
setPersonsList((personsList) => [...personsList, tempPersonObj]);
//setFilteredList(personsList) <-- to render the list again when add new person
setNewPerson(" ");
};
const handleFilter = (event) => {
setFilter(event.target.value);
const filteredList =
event.target.value.length > 0
? personsList.filter((person) =>
person.name.toLowerCase().includes(event.target.value.toLowerCase())
)
: personsList;
setFilteredList(filteredList);
};
return (
<div>
<h2>List:</h2>
Filter{" "}
<input value={filter} onChange={handleFilter} placeholder="Enter" />
<ul>
{filteredList.map((person) => {
return (
<li key={person.id}>
{person.name} {""}
<button onClick={() => handleClick({ person })}>View</button>
</li>
);
})}
</ul>
<form onSubmit={handleSubmit}>
<input
placeholder="Add Person"
value={newPerson}
onChange={handleChange}
/>
<button type="submit">Add</button>
</form>
{showPersonDetail && <PersonDetail person={personObj} />}
</div>
);
};
export default App;
Your filtered list is actually something derived from the full persons list.
To express this, you should not create two apparently independent states in this situation.
When your asynchronous fetch completes, the filter is probably already set and you are just setting personsList which is not the list you are rendering. You are rendering filteredList which is still empty and you are not updating it anywhere, except when the filter gets changed.
To avoid all of this, you could create the filtered list on each rendering and — if you think this is not efficient enough — memoize the result.
const filteredList = useMemo(() =>
filter.length > 0
? personsList.filter((person) =>
person.name.toLowerCase().includes(filter.toLowerCase())
)
: personsList,
[filter, personsList]
);
When the filter input gets changed, you should just call setFilter(event.target.value).
This way, you will always have a filtered list, independent of when your asynchronous person list fetching completes or when filters get updated.
Side note: Writing const [filteredList, setFilteredList] = useState(personsList); looks nice but is the same as const [filteredList, setFilteredList] = useState([]); because the initial value will be written to the state only once, at that's when the component gets initialized. At that time personsList is just an empty array.

How to create an element in react and use aria-live as a custom hook on it

Is there a way to create a custom hook that will be able to create a component that will act as an announcer for aria-live to announce dynamic changes in applications?
Currently I'm using something like this:
export const useAnnouncer = () => {
const [message, setMessage] = useState<string | null>(null)
const setAnnouncer = (m: string) => setMessage(m)
const announcer = (
<div className="sr-only" aria-live="polite" aria-atomic="true" >
{message}
</div>
)
return [announcer, setAnnouncer] as const
Actually, its works. But it's not my problem. Because right now wrapper for aria-live element is creating in component where hook is called.
for ex:
const Controls = () => {
const [announcer, setAnnouncer] = useAnnouncer()
const handleClick = () => {
setAnnouncer('test')
}
return (
<div>
{announcer}
<nav><button onClick={(e: ButtonEvent) => handleClick(e, firstPage)}>test</button></nav>
</div>
)
}
I would like to avoid {announcer} in my jsx. Is it possible to create a wrapper for aria-live in useAnnouncer and only have setAnnouncer in the component to handle voice announcements?

How can I disable/enable the button based on multiple checkboxes state in React.js

I have a component on a page that renders a bunch of checkboxes and toggles.
I also have a button called confirm at the bottom to save the changes and make a request to update the back-end.
However I wanted to support a feature that when users haven't made any changes to any of these checkboxes or toggles, the confirm button should be disabled. and when users toggle or check any of these, then the confirm button is enabled and clickable.
so right now what I am doing is
const MyComponent = () => {
const dispatch = useDispatch();
// get the current state from the store
const state = useSelector((state: RootState) => state.settings);
const [isCheckbox1Checked, setCheckbox1] = useState(false);
const [isCheckbox2Checked, setCheckbox2] = useState(false);
const [isCheckbox3Checked, setCheckbox3] = useState(false);
const [isConfirmBtnEnabled, setConfirmBtn] = useState(false);
const [updateBtnEnabled, enableUpdateBtn] = useState(false);
useEffect(() => {
(async () => {
// `getSettingsConfig` is a async thunk action
await dispatch(getSettingsConfig());
setCheckbox1(state.isCheckbox1Checked);
setCheckbox2(state.isCheckbox2Checked);
setCheckbox3(state.isCheckbox3Checked);
})();
}, [
dispatch,
state.isCheckbox1Checked,
state.isCheckbox2Checked,
state.isCheckbox3Checked
// ..
]);
return (
<>
<div className="checkboxes">
<Checkbox1
onCheck={() => {
setCheckbox1(true);
setConfirmBtn(true);
}}
/>
<Checkbox2
onCheck={() => {
setCheckbox2(true);
setConfirmBtn(true);
}}
/>
<Checkbox3
onCheck={() => {
setCheckbox3(true);
setConfirmBtn(true);
}}
/>
</div>
<button disabled={!isConfirmBtnEnabled}>Confirm</button>
</>
);
};
right now it seems to be working out fine but it requires manually spamming setConfirmBtn to every checkbox and toggles I have on this page. I wonder if there is a better way to do it.
Also I thought about using useEffect to call isConfirmBtnEnabled every time any of these state changes. However since the initial state is derived from the store via dispatching an async thunk, the state of these checkboxes and toggles are going to be changed anyways after the page mounts, so that means I cannot use another useEffect to listen on the changes of these state.
You could use useEffect hook to watch the three check boxes and update the button state based on isConfirmBtnEnabled which is updated inside the useEffect hook:
useEffect(()=>{
setConfirmBtn(isCheckbox1Checked || isCheckbox2Checked || isCheckbox3Checked)
},[isCheckbox1Checked,isCheckbox2Checked,isCheckbox3Checked])
Edit :
const MyComponent = () => {
const dispatch = useDispatch();
// get the current state from the store
const state = useSelector((state: RootState) => state.settings);
const [checkboxes, setCheckboxes] = useState({c1:false,c2:false,c3:false});
const [isConfirmBtnEnabled, setConfirmBtn] = useState(false);
const [updateBtnEnabled, enableUpdateBtn] = useState(false);
useEffect(() => {
(async () => {
// `getSettingsConfig` is a async thunk action
await dispatch(getSettingsConfig());
[1,2,3].forEach(i=>{
setCheckboxes({...checkboxes,[`c${i}`]:state[`isCheckbox${1}Checked`]})
})
})();
}, [
dispatch,
state
// ..
]);
useEffect(()=>{
setConfirmBtn(Object.values(checkboxes).reduce((a,c)=>(a=a || c),false))
},[checkboxes])
const _onCheck=(i)=>{
setCheckboxes({...checkboxes,[`c${i}`]:tur})
}
return (
<>
<div className="checkboxes">
<Checkbox1
onCheck={() => _onCheck(1)}
/>
<Checkbox2
onCheck={() => _onCheck(2)}
/>
<Checkbox3
onCheck={() => _onCheck(3)}
/>
</div>
<button disabled={!isConfirmBtnEnabled}>Confirm</button>
</>
);
};

How use async return value from useEffect as default value in useState?

I've created a simple example https://codesandbox.io/s/4zq852m7j0.
As you can see I'm fetching some data from a remote source. I'd like to use the return value as the value inside my textfield.
const useFetch = () => {
const [value, setValue] = useState("");
useEffect(
async () => {
const response = await fetch("https://httpbin.org/get?foo=bar");
const data = await response.json();
setValue(data.args.foo);
},
[value]
);
return value;
};
However using the value inside the useState function does not work. I think useState uses the default value only on first render. When first rendering the value is obviously not set since it's async. The textfield should have the value bar but it is empty.
function App() {
const remoteName = useFetch();
// i want to see the remote value inside my textfield
const [name, setName] = useState(remoteName);
const onChange = event => {
setName(event.target.value);
};
return (
<div className="App">
<p>remote name: {remoteName}</p>
<p>local name: {name}</p>
<input onChange={onChange} value={name} />
</div>
);
}
After fetching the value from remote I'd like to be able to change it locally.
Any ideas?
Now that useFetch returns a value that is available asynchronously, what you need is to update localState when the remoteValue is available, for that you can write an effect
const remoteName = useFetch();
// i want to see the remote value inside my textfield
const [name, setName] = useState(remoteName);
useEffect(
() => {
console.log("inside effect");
setName(remoteName);
},
[remoteName] // run when remoteName changes
);
const onChange = event => {
setName(event.target.value);
};
Working demo
This is exactly same case as setting initial state asynchronously in class component:
state = {};
async componentDidMount() {
const response = await fetch(...);
...
this.setState(...);
}
Asynchronously retrieved state cannot be available during initial render. Function component should use same technique as class component, i.e. conditionally render children that depend on a state:
return name && <div className="App">...</div>;
This way there's no reason for useFetch to have its own state, it can maintain common state with the component (an example):
const useFetch = () => {
const [value, setValue] = useState("");
useEffect(
async () => {
const response = await fetch("https://httpbin.org/get?foo=bar");
const data = await response.json();
setValue(data.args.foo);
},
[] // executed on component mount
);
return [value, setValue];
};
function App() {
const [name, setName] = useFetch();
const onChange = event => {
setName(event.target.value);
};
return name && (
<div className="App">
<p>local name: {name}</p>
<input onChange={onChange} value={name} />
</div>
);
}
Is it not possible to pass the initial state as an async function?
i.e
const [value, setValue] = useState(async () => fetch("https://httpbin.org/get?foo=bar").json().args.foo);

Categories

Resources