import React from 'react' import { useState } from 'react'
const App = () => {
const [tasks, setTasks] = useState([
{ id: 1, text: 'Task1' }, { id: 2, text: 'Task2' }, { id: 3, text: 'Task3' }
])
const showTasks = tasks.map((task) => <h2>{task.text}<button onClick={() => onDelete(task.id)}>X</button></h2>)
const onDelete = (id) => {
setTasks(tasks.filter((task) => task.id !== id))
}
const [text, setText] = useState('')
const onSubmit = (e) => {
e.preventDefault()
if (!text || tasks > text || text === setTasks) {
alert('Problem');
return
}
addTask({ text })
setText('')
}
const addTask = (task) => {
const id = Math.floor(Math.random() * 10000) + 1;
const newTask = { id, ...task };
setTasks([...tasks, newTask]);
}
return (
<div>
<div>{showTasks}</div>
<form onSubmit={onSubmit}>
<input type='text' value={text} onChange={(e) => setText(e.target.value)} />
<input type='submit' value='Save Task' />
</form>
</div>
) }
export default App
Following is the condition to avoid duplicates in the onSubmit function.
!text || tasks.some((item) => item.text === text)
const App = () => {
const [tasks, setTasks] = React.useState([
{ id: 1, text: "Task1" },
{ id: 2, text: "Task2" },
{ id: 3, text: "Task3" }
]);
const showTasks = tasks.map((task) => (
<h2 key={task.id}>
{task.text}
<button onClick={() => onDelete(task.id)}>X</button>
</h2>
));
const onDelete = (id) => {
setTasks(tasks.filter((task) => task.id !== id));
};
const [text, setText] = React.useState("");
const onSubmit = (e) => {
e.preventDefault();
if (!text || tasks.some((item) => item.text === text)) {
alert("Duplicate");
return;
}
addTask({ text });
setText("");
};
const addTask = (task) => {
const id = Math.floor(Math.random() * 10000) + 1;
const newTask = { id, ...task };
setTasks([...tasks, newTask]);
};
return (
<div>
<div>{showTasks}</div>
<form onSubmit={onSubmit}>
<input
type="text"
value={text}
onChange={(e) => setText(e.target.value)}
/>
<input type="submit" value="Save Task" />
</form>
</div>
);
};
ReactDOM.render(<App />, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div class='react'></div>
Related
I have the following issue with website where the settings state resets after running more searches. The settings component is show below in the picture, it usually works but if you uncheck a box and then run a few more searches at some point the showAllDividends setting will be set to false, the All dividends component won't be on the screen, but for some reason the checkbox itself is checked (true). This is my first time really working with checkboxes in React, and I think I'm using the onChange feature wrong. Right now I get the event.target.checked boolean, but only onChange.
If that isn't the issue then the most likely cause is the default statements being run again on another render:
const [showMainInfo, setShowMainInfo] = useState(true);
const [showYieldChange, setShowYieldChange] = useState(true);
const [showAllDividends, setShowAllDividends] = useState(true);
the thing is I don't see why the default statements would run more than once, the component isn't being destroyed there's no react router usage. I expected it to keep its current state after the page is first loaded. I think the settings defaults are being rerun, but just don't understand why they would.
I unchecked, checked, unchecked the 'All dividends' checkbox, and it was unchecked when I ran 2 more searches. After the second search the checkbox was checked but the component was gone, because showAllDividends was false
main component SearchPage.js:
import React, {useState, useEffect} from 'react';
import { connect } from 'react-redux';
import axios from 'axios';
import SearchBar from './SearchBar';
import AllDividendsDisplay from './dividend_results_display/AllDividendsDisplay';
import DividendResultsDisplay from './dividend_results_display/DividendResultsDisplay';
import SettingsView from './settings/SettingsView';
const HOST = process.env.REACT_APP_HOSTNAME
const PROTOCOL = process.env.REACT_APP_PROTOCOL
const PORT = process.env.REACT_APP_PORT
const BASE_URL = PROTOCOL + '://' + HOST + ':' + PORT
const SearchPage = ({userId}) => {
const DEFAULT_STOCK = 'ibm';
const [term, setTerm] = useState(DEFAULT_STOCK);
const [debouncedTerm, setDebouncedTerm] = useState(DEFAULT_STOCK);
const [loading, setLoading] = useState(false);
const [recentSearches, setRecentSearches] = useState([DEFAULT_STOCK]);
const [dividendsYearsBack, setDividendsYearsBack] = useState('3');
const [debouncedDividendYearsBack, setDebouncedDividendYearsBack] = useState('3');
const [errorMessage, setErrorMessage] = useState('');
const [dividendsData, setDividendsData] = useState(
{
current_price: '',
recent_dividend_rate: '',
current_yield: '',
dividend_change_1_year: '',
dividend_change_3_year: '',
dividend_change_5_year: '',
dividend_change_10_year: '',
all_dividends: [],
name: '',
description: '',
}
)
const [settingsViewVisible, setSettingsViewVisible] = useState(false);
const [showMainInfo, setShowMainInfo] = useState(true);
const [showYieldChange, setShowYieldChange] = useState(true);
const [showAllDividends, setShowAllDividends] = useState(true);
const onTermUpdate = (term) => {
setTerm(term)
}
// TODO: write a custom hook that debounces taking the term and the set debounced term callback
useEffect(() => {
const timerId = setTimeout(() => {
setDebouncedTerm(term);
}, 1500);
return () => {
clearTimeout(timerId);
};
}, [term]);
useEffect(() => {
const timerId = setTimeout(() => {
setDebouncedDividendYearsBack(dividendsYearsBack);
}, 1500);
return () => {
clearTimeout(timerId);
};
}, [dividendsYearsBack]);
useEffect(() => {runSearch()}, [debouncedTerm]);
useEffect(() => {
// alert(dividendsYearsBack)
if (dividendsYearsBack !== '') {
runSearch();
}
}, [debouncedDividendYearsBack])
useEffect(() => {
console.log("user id changed")
if (userId) {
const user_profile_api_url = BASE_URL + '/users/' + userId
axios.get(user_profile_api_url, {})
.then(response => {
const recent_searches_response = response.data.searches;
const new_recent_searches = [];
recent_searches_response.map(dict => {
new_recent_searches.push(dict.search_term)
})
setRecentSearches(new_recent_searches);
})
.catch((error) => {
console.log("error in getting user profile: ", error.message)
})
}
}, [userId])
useEffect(() => {
const user_profile_api_url = BASE_URL + '/users/' + userId
const request_data = {searches: recentSearches}
axios.post(user_profile_api_url, request_data)
// .then(response => {
// console.log(response)
// })
}, [recentSearches])
const makeSearchApiRequest = () => {
let dividends_api_url = BASE_URL + '/dividends/' + term + '/' + dividendsYearsBack
if (!recentSearches.includes(term)) {
setRecentSearches([...recentSearches, term])
}
axios.get(dividends_api_url, {})
.then(response => {
// console.log(response)
setLoading(false);
setDividendsData(response.data);
})
.catch((error) => {
console.log(error.message);
setLoading(false);
setErrorMessage(error.message);
})
}
const runSearch = () => {
console.log("running search: ", term);
setErrorMessage('');
if (term) {
setLoading(true);
if (!dividendsYearsBack) {
setDividendsYearsBack('3', () => {
makeSearchApiRequest()
});
} else {
makeSearchApiRequest()
}
}
}
const recentSearchOnClick = (term) => {
setTerm(term);
setDebouncedTerm(term);
}
const removeRecentSearchOnClick = (term) => {
const searchesWithoutThisOne = recentSearches.filter(search => search !== term)
setRecentSearches(searchesWithoutThisOne);
}
const dividendsYearsBackOnChange = (text) => {
setDividendsYearsBack(text);
}
const renderMainContent = () => {
if (!debouncedTerm) {
return (
<div className="ui active">
<div className="ui text">Search for info about a stock</div>
</div>
)
}
if (loading === true) {
return (
<div className="ui active dimmer">
<div className="ui big text loader">Loading</div>
</div>
)
}
if (errorMessage) {
return (
<div className="ui active">
<div className="ui text">{errorMessage}</div>
</div>
)
} else {
return (
<DividendResultsDisplay
data={dividendsData}
dividends_years_back={dividendsYearsBack}
dividendsYearsBackOnChange={dividendsYearsBackOnChange}
showMainInfo={showMainInfo}
showYieldChange={showYieldChange}
showAllDividends={showAllDividends}/>
)
}
}
// https://stackoverflow.com/questions/38619981/how-can-i-prevent-event-bubbling-in-nested-react-components-on-click
const renderRecentSearches = () => {
return recentSearches.map((term) => {
return (
<div key={term}>
<button
onClick={() => recentSearchOnClick(term)}
style={{marginRight: '10px'}}
>
<div>{term} </div>
</button>
<button
onClick={(event) => {event.stopPropagation(); removeRecentSearchOnClick(term)}}>
X
</button>
<br/><br/>
</div>
)
})
}
const renderSettingsView = (data) => {
if (settingsViewVisible) {
return (
<SettingsView data={data} />
)
} else {
return null;
}
}
const toggleSettingsView = () => {
setSettingsViewVisible(!settingsViewVisible);
}
const toggleDisplay = (e, setter) => {
setter(e.target.checked)
}
const SETTINGS_DATA = [
{
label: 'Main info',
id: 'main_info',
toggler: toggleDisplay,
setter: setShowMainInfo
},
{
label: 'Yield change',
id: 'yield_change',
toggler: toggleDisplay,
setter: setShowYieldChange
},
{
label: 'Dividends list',
id: 'all_dividends',
toggler: toggleDisplay,
setter: setShowAllDividends
},
]
console.log("showMainInfo: ", showMainInfo);
console.log("showYieldChange: ", showYieldChange);
console.log("showAllDividends: ", showAllDividends);
return (
<div className="ui container" style={{marginTop: '10px'}}>
<SearchBar term={term} onTermUpdate={onTermUpdate} />
{renderRecentSearches()}
<br/><br/>
<button onClick={toggleSettingsView}>Display settings</button>
{renderSettingsView(SETTINGS_DATA)}
<div className="ui segment">
{renderMainContent()}
</div>
</div>
)
}
const mapStateToProps = state => {
return { userId: state.auth.userId };
};
export default connect(
mapStateToProps
)(SearchPage);
// export default SearchPage;
the settingsView component:
import React from 'react';
import SettingsCheckbox from './SettingsCheckbox';
const SettingsView = ({data}) => {
const checkboxes = data.map((checkbox_info) => {
return (
<div key={checkbox_info.id}>
<SettingsCheckbox
id={checkbox_info.id}
label={checkbox_info.label}
toggler={checkbox_info.toggler}
setter={checkbox_info.setter}/>
<br/>
</div>
)
});
return (
<div className="ui segment">
{checkboxes}
</div>
);
}
export default SettingsView;
SettingsCheckbox.js:
import React, {useState} from 'react';
const SettingsCheckbox = ({id, label, toggler, setter}) => {
const [checked, setChecked] = useState(true)
return (
<div style={{width: '200px'}}>
<input
type="checkbox"
checked={checked}
id={id}
name={id}
value={id}
onChange={(e) => {
setChecked(!checked);
toggler(e, setter);
}} />
<label htmlFor="main_info">{label}</label><br/>
</div>
);
}
export default SettingsCheckbox;
On my site, I'm using TagsInput, which allows the user to enter data into an input field, hit the enter button, and see it displayed as tags.
But I have one problem: the user can enter data with the same value as many times as he wants. I would like to restrict this ability and not allow the same data to be entered.
I already have some validation that displays a message if the user has entered an invalid data format.
Thus, I would like to add the ability to not accept data if it is already in the tags and display the corresponding message.
export default function TagsInputRequestURL(props) {
const {tags, setTags} = props;
const [input, setInput] = useState("");
const [isValid, setIsValid] = useState(true);
const onChange = (e) => {
const { value } = e.target;
if (e.target.value) {
setIsValid(() => /^(ftp|https?):\/\/[^ "]+$/.test(e.target.value));
} else {
setIsValid(true);
}
setInput(value);
};
const onSubmit = (e) => {
e.preventDefault();
if (isValid) {
setTags((tags) => [...tags, input]);
setInput("");
}
};
const deleteTag = (index) => {
setTags((prevState) => prevState.filter((tag, i) => i !== index));
};
return (
<div className={classes.container}>
{tags.map((tag, index) =>
<div className={classes.tag}>
<ClearIcon
className={classes.del}
fontSize="big"
onClick={() => deleteTag(index)}
/>
{tag}
</div>
)}
<form onSubmit={onSubmit}>
<input
className={classes.input}
value={input}
placeholder={props.inputPlaceholder}
onChange={onChange}
/>
{!isValid && <small style={{ color: "red" }}>Invalid URL</small>}
</form>
</div>
);
}
export default function TagsInputRequestURL(props) {
const {tags, setTags} = props;
const [input, setInput] = useState("");
const [isValid, setIsValid] = useState(true);
const onChange = (e) => {
const { value } = e.target;
if (e.target.value) {
setIsValid(() => /^(ftp|https?):\/\/[^ "]+$/.test(e.target.value));
} else {
setIsValid(true);
}
setInput(value);
};
const containsString = (str) => {
if(!str || str === '') return false
const strLower = str.toLowerCase();
let isExist = false
for(let i=0; i<tags.length; i++){
let itemLower = tags[i].toLowerCase();
if(strLower === itemLower){
isExist = true;
break;
}
}
return isExist;
}
const onSubmit = (e) => {
e.preventDefault();
if (isValid && !containsString(input)) {
setTags((tags) => [...tags, input]);
setInput("");
}
else{
console.log("You already hame same value in the 'tags' array. Try with different string.")
}
};
const deleteTag = (index) => {
setTags((prevState) => prevState.filter((tag, i) => i !== index));
};
return (
<div className={classes.container}>
{tags.map((tag, index) =>
<div className={classes.tag}>
<ClearIcon
className={classes.del}
fontSize="big"
onClick={() => deleteTag(index)}
/>
{tag}
</div>
)}
<form onSubmit={onSubmit}>
<input
className={classes.input}
value={input}
placeholder={props.inputPlaceholder}
onChange={onChange}
/>
{!isValid && <small style={{ color: "red" }}>Invalid URL</small>}
</form>
</div>
);
}
I'm working on a React Notes Application and my App.js contains all the necessary functions props which are passed down to several components.
As a result I'm doing prop drilling a lot where I'm passing down around 10-20 props/functions in the components where it isn't needed.
I tried using useContext Hook but I guess it doesn't work with callback functions in the value parameter.
App.js
const App = () => {
const [ notes, setNotes ] = useState([]);
const [ category, setCategory ] = useState(['Notes']);
const [ searchText, setSearchText ] = useState('');
const [ alert, setAlert ] = useState({
show:false,
msg:'',
type:''
});
const [isEditing, setIsEditing] = useState(false);
const [editId, setEditId] = useState(null);
useEffect(()=>{
keepTheme();
})
// retreive saved notes
useEffect(()=>{
const savedNotes = JSON.parse(localStorage.getItem('react-notes-data'));
if (savedNotes) {
setNotes(savedNotes)
}
}, []);
// save notes to local storage
useEffect(() => {
localStorage.setItem('react-notes-data', JSON.stringify(notes))
setNotesCopy([...notes]);
}, [notes]);
// save button will add new note
const addNote = (text) => {
const date = new Date();
const newNote = {
id: nanoid(),
text: text,
date: date.toLocaleDateString(),
category: category,
}
const newNotes = [...notes, newNote]
setNotes(newNotes);
}
const deleteNote = (id) => {
showAlert(true, 'Note deleted', 'warning');
const newNotes = notes.filter(note => note.id !== id);
setNotes(newNotes);
}
// hardcoded values for categories
const allCategories = ['Notes', 'Misc', 'Todo', 'Lecture Notes', 'Recipe'];
// copy notes for filtering through
const [notesCopy, setNotesCopy] = useState([...notes]);
const handleSidebar = (category) => {
setNotesCopy(category==='Notes'?[...notes]:
notes.filter(note=>note.category===category));
}
// function to call alert
const showAlert = (show=false, msg='', type='') => {
setAlert({show, msg, type});
}
return (
<div>
<div className="container">
<Sidebar
allCategories={allCategories}
handleSidebar={handleSidebar}
notesCopy={notesCopy}
key={notes.id}
/>
<Header notes={notes} alert={alert} removeAlert={showAlert} />
<Search handleSearchNote={setSearchText} />
<NotesList
notesCopy={notesCopy.filter(note=>
note.text.toLowerCase().includes(searchText) ||
note.category.toString().toLowerCase().includes(searchText)
)}
handleAddNote={addNote}
deleteNote={deleteNote}
category={category}
setCategory={setCategory}
allCategories={allCategories}
showAlert={showAlert}
notes={notes}
setNotes={setNotes}
editId={editId}
setEditId={setEditId}
isEditing={isEditing}
setIsEditing={setIsEditing}
/>
</div>
</div>
)
}
NotesList.js
const NotesList = (
{ notesCopy, handleAddNote, deleteNote, category, setCategory, showHideClassName, allCategories, showAlert, isEditing, setIsEditing, notes, setNotes, editId, setEditId }
) => {
const [ noteText, setNoteText ] = useState('');
const textareaRef = useRef();
// function to set edit notes
const editItem = (id) => {
const specificItem = notes.find(note=>note.id === id);
setNoteText(specificItem.text);
setIsEditing(true);
setEditId(id);
textareaRef.current.focus();
}
return (
<div key={allCategories} className="notes-list">
{notesCopy.map(note => {
return (
<Note
key={note.id}
{...note}
deleteNote={deleteNote}
category={note.category}
isEditing={isEditing}
editId={editId}
editItem={editItem}
/>)
})}
<AddNote
handleAddNote={handleAddNote}
category={category}
setCategory={setCategory}
showHideClassName={showHideClassName}
allCategories={allCategories}
showAlert={showAlert}
isEditing={isEditing}
setIsEditing={setIsEditing}
notes={notes}
setNotes={setNotes}
editId={editId}
setEditId={setEditId}
noteText={noteText}
setNoteText={setNoteText}
textareaRef={textareaRef}
/>
</div>
)
}
AddNote.js
const AddNote = ({ notes, setNotes, handleAddNote, category, setCategory, showHideClassName, allCategories, showAlert, isEditing, setIsEditing, editId, setEditId, noteText, setNoteText, textareaRef }) => {
const [ show, setShow ] = useState(false);
const [ modalText, setModalText ] = useState('');
const charCount = 200;
const handleChange = (event) => {
if (charCount - event.target.value.length >= 0) {
setNoteText(event.target.value);
}
}
const handleSaveClick = () => {
if (noteText.trim().length === 0) {
setModalText('Text cannot be blank!');
setShow(true);
}
if (category === '') {
setModalText('Please select a label');
setShow(true);
}
if (noteText.trim().length > 0 && category!=='') {
showAlert(true, 'Note added', 'success');
handleAddNote(noteText);
setNoteText('');
setShow(false);
}
if (noteText.trim().length > 0 && category!=='' && isEditing) {
setNotes(notes.map(note=>{
if (note.id === editId) {
return ({...note, text:noteText, category:category})
}
return note
}));
setEditId(null);
setIsEditing(false);
showAlert(true, 'Note Changed', 'success');
}
}
const handleCategory = ( event ) => {
let { value } = event.target;
setCategory(value);
}
showHideClassName = show ? "modal display-block" : "modal display-none";
return (
<div className="note new">
<textarea
cols="10"
rows="8"
className='placeholder-dark'
placeholder="Type to add a note.."
onChange={handleChange}
value={noteText}
autoFocus
ref={textareaRef}
>
</textarea>
<div className="note-footer">
<small
className='remaining'
style={{color:(charCount - noteText.length == 0) && '#c60000'}}>
{charCount - noteText.length} remaining</small>
<div className='select'>
<select
name={category}
className="select"
onChange={(e)=>handleCategory(e)}
required
title='Select a label for your note'
defaultValue="Notes"
>
<option value="Notes" disabled selected>Categories</option>
{allCategories.map(item => {
return <option key={item} value={item}>{item}</option>
})}
</select>
</div>
<button className='save' onClick={handleSaveClick} title='Save note'>
<h4>{isEditing ? 'Edit':'Save'}</h4>
</button>
</div>
{/* Modal */}
<main>
<div className={showHideClassName}>
<section className="modal-main">
<p className='modal-text'>{modalText}</p>
<button type="button" className='modal-close-btn'
onClick={()=>setShow(false)}><p>Close</p>
</button>
</section>
</div>
</main>
</div>
)
}
I want the functions passed from App.js to NotesList.js to be in AddNote.js without them being passed in NotesList.js basically minimizing the props destructuring in NotesList.js
Context API does work with function. What you need to do is pass your function to Provider inside value :
<MyContext.Provider value={{notes: notesData, handler: myFunction}} >
For example:
// notes-context.js
import React, { useContext, createContext } from 'react';
const Context = createContext({});
export const NotesProvider = ({children}) => {
const [notes, setNote] = useState([]);
const addNote = setNote(...); // your logic
const removeNote = setNote(...); // your logic
return (
<Context.Provider value={{notes, addNote, removeNote}}>
{children}
</Context.Provider>
)
}
export const useNotes = () => useContext(Context);
Add Provider to your App.js like so:
// App.js
import NoteProvider from './notes-context';
export default App = () => {
return (
<NoteProvider>
<div>... Your App</div>
</NoteProvider>
)
}
Then call UseNote in your NoteList.js to use the function:
// NoteList.js
import {useNotes} from './note-context.js';
export const NoteList = () => {
const {notes, addNotes, removeNotes} = useNotes();
// do your stuff. You can now use functions addNotes and removeNotes without passing them down the props
}
I am learning react and I reached a problem I can't get past.
On the upmost component I have a lot of functions that depend on state and that modify state. These will get passed on to children components and get linked to event handlers.
The file gets really large and I would like to somehow separate the functions and not clutter all of them in one file.
I created a demo here,you can see the App component gets really cluttered with functions.
What options do I have to separate the functions?
const {
useState,
useEffect,
useRef,
useCallback
} = React;
const useStateWithCallback = initialState => {
const [state, setState] = useState({
value: initialState,
callback: undefined
});
useEffect(() => {
if (state.callback) {
state.callback();
}
}, [state]);
const setStateWithCallback = (newValue, callback) => {
const value =
typeof newValue === "function" ? newValue(state.value) : newValue;
setState({
value,
callback
});
};
return [state.value, setStateWithCallback];
};
const Day = ({
input1,
input2,
handleInputChange,
isLocked,
index,
className
}) => {
return (
<div className={className}>
<input
name="input1"
value={input1}
placeholder="lorem"
onChange={handleInputChange}
readOnly={isLocked}
data-index={index}
/>
<input
name="input2"
value={input2}
placeholder="ipsum"
onChange={handleInputChange}
readOnly={isLocked}
data-index={index}
/>
</div>
);
};
const Menu = ({
handleSelectChange,
clearInputs,
submitInputs,
handleLock,
isLocked,
handleDateChange
}) => {
return (
<React.Fragment>
<select name="date" onChange={handleDateChange}>
<option value="01">01</option>
<option value="02">02</option>
<option value="03">03</option>
<option value="04">04</option>
<option value="05">05</option>
<option value="06">06</option>
</select>
<select name="word" onChange={handleSelectChange}>
<option value="lorem">Lorem</option>
<option value="ipsum">Ipsum</option>
</select>
<button onClick={clearInputs}>Clear</button>
<button onClick={submitInputs}>Submit</button>
{isLocked ? (
<button onClick={handleLock}>Unlock</button>
) : (
<button onClick={handleLock}>Lock</button>
)}
</React.Fragment>
);
};
const Month = ({
inputs1,
inputs2,
handleInputChange,
isLocked,
mobile
}) => {
return ( < React.Fragment >
<
Day input1 = {
inputs1[0]
}
input2 = {
inputs2[0]
}
handleInputChange = {
handleInputChange
}
isLocked = {
isLocked
}
index = {
0
}
className = {
mobile ? "mobile" : "day"
}
/> <
Day input1 = {
inputs1[1]
}
input2 = {
inputs2[1]
}
handleInputChange = {
handleInputChange
}
isLocked = {
isLocked
}
index = {
1
}
className = {
mobile ? "mobile" : "day"
}
/> < /React.Fragment >
);
};
const App =()=>{
const [inputs1, setInputs1] = useStateWithCallback(
Array.from({ length: 2 }, () => "")
);
const [inputs2, setInputs2] = useStateWithCallback(
Array.from({ length: 2 }, () => "")
);
const [word, setWord] = useState("?");
const [isLocked, setIsLocked] = useStateWithCallback(false);
const [mobile, setMobile] = useState(true);
const [date, setDate] = useState(new Date().getDate());
const handleSelectChange = event => {
setWord(event.target.value);
};
const handleInputChange = event => {
if (event.target.name === "input1") {
const newInputs1 = [...inputs1];
newInputs1[event.target.dataset.index] =
event.target.value.length === 3
? event.target.value + word
: event.target.value;
setInputs1(newInputs1);
} else if (event.target.name === "input2") {
const newInputs2 = [...inputs2];
newInputs2[event.target.dataset.index] =
event.target.value.length === 4
? event.target.value + word + "%%"
: event.target.value;
setInputs2(newInputs2);
}
};
const clearInputsOnServer = () => {
return new Promise(resolve => setTimeout(resolve, 800))
.catch(() => console.log(`couldn't clear`))
.then(console.log("succesfully cleared inputs on server"));
};
const clearInputs = () => {
setInputs1(
Array.from({ length: 2 }, () => ""),
setInputs2(Array.from({ length: 2 }, () => ""), clearInputsOnServer)
);
};
const submitInputs = () => {
return new Promise(resolve => setTimeout(resolve, 800))
.catch(() => console.log(`couldn't update`))
.then(console.log("submitted inputs to server"));
};
const updateLockOnServer = () => {
return new Promise(resolve => setTimeout(resolve, 800))
.catch(() => console.log(`couldn't update`))
.then(console.log("updated lock status on server"));
};
const handleLock = () => {
setIsLocked(wasLocked => !wasLocked, updateLockOnServer);
};
let timeout = useRef();
const handleResize = useCallback(() => {
clearTimeout(timeout.current);
timeout.current = setTimeout(() => {
const newMobile = window.innerWidth <= 600 ? true : false;
if (mobile !== newMobile) setMobile(newMobile);
}, 300);
}, [mobile]);
useEffect(() => {
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, [handleResize]);
const handleDateChange = event => {
setDate(event.target.value);
};
const getValuesFromServer = useRef(() => {
return new Promise(resolve => setTimeout(resolve, 800))
.then(console.log("succesfully updated inputs from server"))
.catch(() => {});
});
useEffect(() => {
getValuesFromServer.current();
}, [date]);
return (
<React.Fragment>
<Menu
handleSelectChange={handleSelectChange}
clearInputs={clearInputs}
submitInputs={submitInputs}
handleLock={handleLock}
isLocked={isLocked}
handleDateChange={handleDateChange}
/>
<Month
inputs1={inputs1}
inputs2={inputs2}
handleInputChange={handleInputChange}
isLocked={isLocked}
mobile={mobile}
/>
</React.Fragment>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render( <
React.StrictMode >
<
App/ >
<
/React.StrictMode>,
rootElement
);
.App {
font-family: sans-serif;
text-align: center;
}
.mobile input {
background-color: yellow;
font-size: 20px;
}
<div id="root"></div>
<script crossorigin src="https://unpkg.com/react#16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.production.min.js"></script>
you can abstract scopes from your code into separate hooks folder than call into you App component extracting only what you need. for example all your lock state logic could be a hook like:
// useLockHandler.js at your hooks folder
const useLockHandler = () => {
const [isLocked, setIsLocked] = useStateWithCallback(false);
const updateLockOnServer = () => {
fetch(`server`, {
method: "put",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
isLocked
})
})
.catch(() => console.log(`couldn't update`))
.then(console.log("updated lock status on server"));
};
const handleLock = () => {
setIsLocked(wasLocked => !wasLocked, updateLockOnServer);
};
// expose what you need at your component.you can return as array or object
return { isLocked, handleLock }
}
export default useLockHandler
than at your App you would import useLockHandler and extract the variables you need:
export default function App() {
// all other states setting
const { isLocked, handleLock } = useLockHandler()
I am trying to use buttons to filter data from JSON. Whenever a button is clicked, it will update the state with filtered JSON data. It will then return the list with the updated data. Right now I have four buttons to filter the four different "types" but the code is not working at all.
import React, { useState, useEffect } from 'react';
import * as moment from 'moment';
import PollCard from './PollCard'
function PollList() {
const [polls, setPolls] = useState(null);
const [loading, setLoading] = useState(false);
const range = 30
var dateRange = moment().subtract(range, 'days').calendar();
async function fetchMyAPI() {
let response = await fetch(url)
const json = await response.json()
var data = json.filter(e => Date.parse(e.endDate) >= Date.parse(dateRange))
setPolls(data.reverse())
setLoading(true);
}
useEffect(() => {
fetchMyAPI();
}, [])
if (!loading) {
return ("Loading...")
}
var A = polls.filter(e => e.type === "A")
var B = polls.filter(e => e.type === "B")
var C = polls.filter(e => e.type === "C")
function showA() {
setPolls(A)
}
function showB() {
setPolls(B)
}
function showC() {
setPolls(C)
}
return (
<div className="polls">
<button onClick={showA()}>A</button>
<button onClick={showB()}>B</button>
<button onClick={showC()}>C</button>
{
polls && polls.map((poll) => (
<div key={poll.id}>
<PollCard poll={poll} />
<hr style={{ opacity: '.1' }} />
</div>
))
}
</div>
);
}
export default PollList;
You need two arrays to properly filter your data, when you click one of the buttons, it overwrites the original data. Change <button onClick={showA()}>A</button> to <button onClick={() => showA()}>A</button>
// get data from api, won't change
const [polls, setPolls] = useState(null);
// used for displaying polls and filtering
const [filteredPolls, setfilteredPolls] = useState(null)
Filtering data
var A = polls.filter(e => e.type === "A")
var B = polls.filter(e => e.type === "B")
var C = polls.filter(e => e.type === "C")
function showA() {
setfilteredPolls(A)
}
function showB() {
setfilteredPolls(B)
}
function showC() {
setfilteredPolls(C)
}
Displaying data
return (
<div className="polls">
<button onClick={() => showA()}>A</button>
<button onClick={() => showB()}>B</button>
<button onClick={() => showC()}>C</button>
{
filteredPolls && filteredPolls.map((poll) => (
<div key={poll.id}>
<PollCard poll={poll} />
<hr style={{ opacity: '.1' }} />
</div>
))
}
</div>
);
DEMO
<script crossorigin src="https://cdnjs.cloudflare.com/ajax/libs/react/16.7.0-alpha.2/umd/react.production.min.js"></script>
<script crossorigin src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.7.0-alpha.2/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.21.1/babel.min.js"></script>
<div id="root"></div>
<script type="text/babel">
class PollCard extends React.Component {
render() {
return (
<div>{`${this.props.poll.name} - ${this.props.poll.type}`}</div>
)
}
}
const dpolls = [
{
id: 4,
type: 'C',
name: 'Test 1'
},
{
id: 6,
type: 'B',
name: 'Test 2'
},
{
id: 7,
type: 'A',
name: 'Test 3'
},
{
id: 8,
type: 'A',
name: 'Test 9'
},
{
id: 17,
type: 'B',
name: 'Test 39'
}
]
function PollList() {
const [polls, setPolls] = React.useState(null);
const [filteredPolls, setfilteredPolls] = React.useState(null)
const [loading, setLoading] = React.useState(false);
const range = 30
// var dateRange = moment().subtract(range, 'days').calendar();
async function fetchMyAPI() {
let response = await fetch('https://api.themoviedb.org/3/movie/upcoming?api_key=81f382d33088c6d52099a62eab51d967&language=en-US&page=1')
const json = await response.json()
// var data = json.filter(e => Date.parse(e.endDate) >= Date.parse(dateRange))
setPolls(dpolls);
setfilteredPolls(dpolls.filter(e => e.type === "A"));
setLoading(true);
}
React.useEffect(() => {
fetchMyAPI();
}, [])
if (!loading) {
return ("Loading...")
}
var A = polls.filter(e => e.type === "A")
var B = polls.filter(e => e.type === "B")
var C = polls.filter(e => e.type === "C")
function showA() {
setfilteredPolls(A)
}
function showB() {
setfilteredPolls(B)
}
function showC() {
setfilteredPolls(C)
}
function removeFiter() {
setfilteredPolls(polls);
}
return (
<div className="polls">
<button onClick={() => showA()}>A</button>
<button onClick={() => showB()}>B</button>
<button onClick={() => showC()}>C</button>
<button onClick={() => removeFiter()}>Remove Filter</button>
{
filteredPolls && filteredPolls.map((poll) => (
<div key={poll.id}>
<PollCard poll={poll} />
<hr style={{ opacity: '.1' }} />
</div>
))
}
</div>
);
}
class App extends React.Component {
constructor() {
super();
this.state = {
name: 'React'
};
}
render() {
return (
<div>
<PollList />
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
</script>