I have one issue with fetching the data using the useQuery. Please have a look at the code below:
FetchUser hook:
const useFetchUsers = ({ selectedSchool, selectedYear }) => {
return useQuery(['Users', selectedSchool, selectedYear], async () => {
const URL = getSchoolURL({ selectedSchool, selectedYear })
const response = await fetch(URL)
const data = await response.json()
return {
count: data.count,
users: data.users
}
}, {
enabled: !!(selectedSchool && selectedYear),
onSuccess: () => {
console.log('success')
},
onError: () => {
console.log('errors')
}
})
}
Users component:
const Users = () => {
const {
isLoading,
data,
isError
} = useFetchUsers({ selectedSchool: '', selectedYear: '' })
const updateUsersData = ({ selectedSchool, selectedYear }) => {
// Here, I have to write logic to fetch Users data as per
// selected organization and selectedYear
}
return (
<div className='app'>
<Schools updateUsersData={updateUsersData}/>
{
/**
* Rendering components
* <Component-1/>
* <Component-2/>
* <Component-3/>
* <Component-4/>
* <Component-5/>
*
*/
}
</div>
)
}
School component:
const Schools = () => {
const [school, setSchool] = useState('')
const handleChange = (e) => {
const selectedSchool = e.target.value
setSchool(selectedSchool)
if (selectedSchool) {
// we have other logic to select selected Year
// but here sake for the example, I'm using this value
// hardcoded
updateUsersData({ selectedSchool, selectedYear: '2021' })
}
}
return (
<select
value={school}
onChange={handleChange}
name='school'
id='school'>
<option
value={''}
key={'Select School'}>
Select School
</option>
<option value={'school-1'}>school-1</option>
<option value={'school-2'}>school-2</option>
<option value={'school-3'}>school-3</option>
<option value={'school-4'}>school-4</option>
<option value={'school-5'}>school-5</option>
</select>
)
}
Some notes:
School component: Here, we are rendering the school names and when the user selects any school data, we are calling updateUsersData method and from this method, we have to call again the useFetchUsers hook with updated params but it is not working.
I don't want to take additional states i.e selectedSchool and selectedYear on Users component because of unnecessary component rendering.
Problem: How to again call useFetchUsers hook with updated params from updateUsersData method?
Related
Stackoverflow
problem
I have separate components that house Tiptap Editor tables. At first I had a save button for each Child Component which worked fine, but was not user friendly. I want to have a unified save button that will iterate through each child Table component and funnel all their editor.getJSON() data into an array of sections for the single doc object . Then finish it off by saving the whole object to PouchDB
What did I try?
link to the repo → wchorski/Next-Planner: a CRM for planning events built on NextJS (github.com)
Try #1
I tried to use the useRef hook and the useImperativeHandle to call and return the editor.getJSON(). But working with an Array Ref went over my head. I'll post some code of what I was going for
// Parent.jsx
const childrenRef = useRef([]);
childrenRef.current = []
const handleRef = (el) => {
if(el && !childrenRef.current.includes(el)){
childrenRef.current.push(el)
}
}
useEffect(() =>{
childrenRef.current[0].childFunction1() // I know this doesn't work, because this is where I gave up
})
// Child.jsx
useImperativeHandle(ref, () => ({
childFunction1() {
console.log('child function 1 called');
},
childFunction2() {
console.log('child function 2 called');
},
}))
Try #2
I set a state counter and passed it down as a prop to the Child Component . Then I update the counter to trigger a child function
// Parent.jsx
export const Planner = ({id, doc, rev, getById, handleSave, db, alive, error}) => {
const [saveCount, setSaveCount] = useState(0)
const handleUpdate = () =>{
setSaveCount(prev => prev + 1)
}
const isSections = () => {
if(sectionsState[0]) handleSave(sectionsState)
if(sectionsState[0] === undefined) console.log('sec 0 is undefined', sectionsState)
}
function updateSections(newSec) {
setsectionsState(prev => {
const newState = sectionsState.map(obj => {
if(!obj) return
if (obj.header === newSec.header) {
return {...obj, ...newSec}
}
// 👇️ otherwise return object as is
return obj;
});
console.log('newState', newState);
return newState;
});
}
useEffect(() => {
setsectionsState(doc.sections)
}, [doc])
return (<>
<button
title='save'
className='save'
onPointerUp={handleUpdate}>
Save to State <FiSave />
</button>
<button
style={{right: "0", width: 'auto'}}
title='save'
className='save'
onClick={isSections}>
Save to DB <FiSave />
</button>
{doc.sections.map((sec, i) => {
if(!sec) return
return (
<TiptapTable
key={i}
id={id}
rev={doc.rev}
getById={getById}
updateSections={updateSections}
saveCount={saveCount}
section={sec}
db={db}
alive={alive}
error={error}
/>
)
})}
</>)
// Child.jsx
export const TiptapTable = ((props, ref) => {
const {id, section, updateSections, saveCount} = props
const [currTimeStart, setTimeStart] = useState()
const [defTemplate, setdefTemplate] = useState('<p>loading<p>')
const [isLoaded, setIsLoaded] = useState(false)
const [notesState, setnotesState] = useState('')
const editor = useEditor({
extensions: [
History,
Document,
Paragraph,
Text,
Gapcursor,
Table.configure({
resizable: true,
}),
TableRow.extend({
content: '(tableCell | tableHeader)*',
}),
TableHeader,
TableCell,
],
// i wish it was this easy
content: (section.data) ? section.data : defTemplate,
}, [])
const pickTemplate = async (name) => {
try{
const res = await fetch(`/templates/${name}.json`,{
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
const data = await res.json()
setIsLoaded(true)
setdefTemplate(data)
console.log('defTemplate, ', defTemplate);
// return data
} catch (err){
console.warn('template error: ', err);
}
}
function saveData(){
console.log(' **** SAVE MEEEE ', section.header);
try{
const newSection = {
header: section.header,
timeStart: currTimeStart,
notes: notesState,
data: editor.getJSON(),
}
updateSections(newSection)
} catch (err){
console.warn('table update error: ', id, err);
}
}
useEffect(() => {
// 👇️ don't run on initial render
if (saveCount !== 0) saveData()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [saveCount])
useEffect(() => {
setTimeStart(section.timeStart)
setnotesState(section.notes)
if(!section.data) pickTemplate(section.header).catch(console.warn)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [id, section, isLoaded])
useEffect(() => {
if (editor && !editor.isDestroyed) {
if(section.data) editor.chain().focus().setContent(section.data).run()
if(!section.data) editor.chain().focus().setContent(defTemplate).run()
setIsLoaded(true)
}
}, [section, defTemplate, editor]);
if (!editor) {
return null
}
return isLoaded ? (<>
<StyledTableEditor>
<div className="title">
<input type="time" label='Start Time' className='time'
onChange={(e) => setTimeStart(e.target.value)}
defaultValue={currTimeStart}
/>
<h2>{section.header}</h2>
</div>
<EditorContent editor={editor} className="tiptap-table" ></EditorContent>
// ... non relavent editor controls
<button
title='save'
className='save2'
onPointerUp={() => saveData()}>
Save <FiSave />
</button>
</div>
</nav>
</StyledTableEditor>
</>)
: null
})
TiptapTable.displayName = 'MyTiptapTable';
What I Expected
What I expected was the parent state to update in place, but instead it overwrites the previous tables. Also, once it writes to PouchDB it doesn't write a single piece of new data, just resolved back to the previous, yet with an updated _rev revision number.
In theory I think i'd prefer the useRef hook with useImperativeHandle to pass up the data from child to parent.
It looks like this question is similar but doesn't programmatically comb through the children
I realize I could have asked a more refined question, but instead of starting a new question I'll just answer my own question from what I've learned.
The problem being
I wasn't utilizing React's setState hook as I iterated and updated the main Doc Object
Thanks to this article for helping me through this problem.
// Parent.jsx
import React, {useState} from 'react'
import { Child } from '../components/Child'
export const Parent = () => {
const masterDoc = {
_id: "123",
date: "2023-12-1",
sections: [
{header: 'green', status: 'old'},
{header: 'cyan', status: 'old'},
{header: 'purple', status: 'old'},
]
}
const [saveCount, setSaveCount] = useState(0)
const [sectionsState, setsectionsState] = useState(masterDoc.sections)
function updateSections(inputObj) {
setsectionsState(prev => {
const newState = prev.map(obj => {
// 👇️ if id equals 2, update country property
if (obj.header === inputObj.header)
return {...obj, ...inputObj}
return obj;
});
return newState;
});
}
return (<>
<h1>Parent</h1>
{sectionsState.map((sec, i) => {
if(!sec) return
return (
<Child
key={i}
section={sec}
updateSections={updateSections}
saveCount={saveCount}
/>
)
})}
<button
onClick={() => setSaveCount(prev => prev + 1)}
>State dependant update {saveCount}</button>
</>)
}
// Child.jsx
import React, {useEffect, useState, forwardRef, useImperativeHandle} from 'react'
export const Child = forwardRef((props, ref) => {
const {section, updateSections, saveCount} = props
const [statusState, setStatusState] = useState(section.status)
function modData() {
const obj = {
header: section.header,
status: statusState
}
updateSections(obj)
}
useEffect(() => {
// 👇️ don't run on initial render
if (saveCount !== 0) modData()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [saveCount])
return (<>
<span style={{color: section.header}}>
header: {section.header}
</span>
<span>status: {section.status}</span>
<input
defaultValue={section.status}
onChange={(e) => setStatusState(e.target.value)}
/>
________________________________________
</>)
})
Child.displayName = 'MyChild';
How to pass data to another select after clicking on the first one. I have a select of countries, when clicking on them, regions are loaded into the second select loadOptions.
import dummy from './dummy';
import AsyncSelect from "react-select/async";
//This is country LoadOptions
const loadOptions = (inputValue, callback) => {
dummy.get(`country?CountrySearch[query]=${inputValue}`)
.then((response) => {
const options = []
response.data.data.forEach((permission) => {
options.push({
label: permission.name,
value: permission.id
})
})
callback(options);
})
}
Hooks with options Array(regions)
const [a, b] = useState([''])
let loadOptions2 = async (a) => {
return a
};
After onChange country select, get new data for regions
const handleCountry = (value) =>{
dummy.get(`region?filter[country_id]=${value.value}`)
.then((response) => {
const options = [];
response.data.data.forEach((permission) => {
options.push({
label: permission.name,
value: permission.country_id
})
})
b(options)
loadOptions2 = () => {
return options
}
console.log(a)
return options
})
}
Country Select
<AsyncSelect
cacheOptions
defaultOptions
onChange={handleCountry}
loadOptions={loadOptions}
/>
Region Select
<AsyncSelect
cacheOptions
defaultOptions
value={selectedCity}
onChange={handleCity}
loadOptions={loadOptions2}
/>
Console.log is show regions array, but loadOptions is empty
I'm building an app using react, redux, and redux-saga.
The situation is that I'm getting information from an API. In this case, I'm getting the information about a movie, and I will update this information using a basic form.
What I would like to have in my text fields is the value from the object of the movie that I'm calling form the DB.
This is a brief part of my code:
Im using 'name' as an example.
Parent component:
const MovieForm = (props) => {
const {
movie,
} = props;
const [name, setName] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
onSubmit({
name,
});
};
const handleSetValues = () => {
console.log('hi');
console.log(movie, name);
setName(movie.name);
setValues(true);
};
useEffect(() => {
if (movie && values === false) {
handleSetValues();
}
});
return (
<Container>
<TextField
required
**defaultValue={() => {
console.log(movie, name);
return movie ? movie.name : name;
}}**
label='Movie Title'
onChange={(e) => setName(e.target.value)}
/>
</Container>
);
};
export default MovieForm;
....
child component
const MovieUpdate = (props) => {
const { history } = props;
const { id } = props.match.params;
const dispatch = useDispatch();
const loading = useSelector((state) => _.get(state, 'MovieUpdate.loading'));
const created = useSelector((state) => _.get(state, 'MovieUpdate.created'));
const loadingFetch = useSelector((state) =>
_.get(state, 'MovieById.loading')
);
const movie = useSelector((state) => _.get(state, 'MovieById.results'));
useEffect(() => {
if (loading === false && created === true) {
dispatch({
type: MOVIE_UPDATE_RESET,
});
}
if (loadingFetch === false && movie === null) {
dispatch({
type: MOVIE_GET_BY_ID_STARTED,
payload: id,
});
}
});
const updateMovie = (_movie) => {
const _id = id;
const obj = {
id: _id,
name: _movie.name,
}
console.log(obj);
dispatch({
type: MOVIE_UPDATE_STARTED,
payload: obj,
});
};
return (
<div>
<MovieForm
title='Update a movie'
buttonTitle='update'
movie={movie}
onCancel={() => history.push('/app/movies/list')}
onSubmit={updateMovie}
/>
</div>
);
};
export default MovieUpdate;
Then, the actual problem is that when I use the default prop on the text field the information appears without any problem, but if i use defaultValue it is empty.
Ok, I kind of got the answer, I read somewhere that the defaultValue can't be used int the rendering.
So I cheat in a way, I set the properties multiline and row={1} (according material-ui documentation) and I was able to edit this field an receive a value to display it in the textfield
I have the following code https://codesandbox.io/s/async-http-u9zsg
What I am trying to achieve is to reset the value of the second select box every time I switch movies.
I tried a bunch of different things without any luck. I'm not asking to write me the code, I just want a push in the right direction am I missing something, is my approach completely wrong?
thanks in advance
The code :
import React, { useState, useEffect } from "react";
import fetch from "node-fetch";
const query = `{
allFilms{
films{
title
characterConnection{
characters{
name
species{
name
}
homeworld{
name
}
}
}
}
}
}`;
function App() {
const [state, setState] = useState({
loading: true,
appliedFilters: {},
species: []
});
useEffect(() => {
const fetchMovieData = () => {
const options = {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query })
};
return fetch(
"https://swapi-graphql.netlify.com/.netlify/functions/index",
options
)
.then(res => res.json())
.then(res =>
setState({ ...state, data: res.data.allFilms.films, loading: false })
);
};
fetchMovieData();
}, []);
if (state.loading) {
return <div>Loading...</div>;
}
/*work from here */
const getSpeciesOptions = () => {
if (state.appliedFilters.movie === undefined) {
return <option>Select a movie first</option>;
}
const currentMovie = state.data.filter(
movie => movie.title === state.appliedFilters.movie
);
const characters = currentMovie[0].characterConnection.characters;
const speciesList = characters.map(char =>
char.species === null ? "unknown" : char.species.name
);
return [...new Set(speciesList)].map(specie => <option>{specie}</option>);
};
const handleFilterChange = e => {
setState({
...state,
appliedFilters: {
...state.appliedFilters,
[e.target.name]: e.target.value
}
});
};
console.log(state);
const removeFilters = () => setState({ ...state, appliedFilters: {} });
const movieOptions = () =>
state.data.map(movie => <option>{movie.title}</option>);
return (
<div className="App">
<form>
<select
name="movie"
onChange={e =>
e.target.value !== "default"
? handleFilterChange(e)
: removeFilters()
}
>
<option value="default">Please select movie</option>
{movieOptions()};
</select>
<select name="species">
<option value="default">Please select a species</option>
{getSpeciesOptions()}
</select>
</form>
</div>
);
}
export default App;
A better approach is to fully control the value of the second select as well. So we add the value of it to the state as well:
const [state, setState] = useState({
loading: true,
appliedFilters: {},
species: [],
selected: "default"
});
Update the second control to read the value from state and also manipulate the state on change:
<select
value={state.selected}
name="species"
onChange={e => {
setState({ ...state, selected: e.target.value });
}}
>
<option value="default">Please select a species</option>
{getSpeciesOptions()}
</select>
And finally also manipulate the callback of the first to also reset the value:
const handleFilterChange = e => {
setState({
...state,
appliedFilters: {
...state.appliedFilters,
[e.target.name]: e.target.value,
},
selected: "default"
});
};
https://codesandbox.io/s/hardcore-nash-vtyx7 working example
All you need to do is to create a ref for the second select control.
let ref = React.createRef();
...
<select ref={ref}name="species">
<option value="default">Please select a species</option>
{getSpeciesOptions()}
</select>
</form>
Then in the callback of the first select do this:
const handleFilterChange = e => {
setState(...);
ref.current.value = 'default';
};
With the ref to the elememt you can now manipulate the value.
Actually, you know ReactJS exactly, but your approach causes you are complicated now. For such cases just keep the state of second select and after changing the first select reset the second one.
<select name="species" onChange={handleSpeciesChanges}>
I mean to write a state handler for the second select and then in the state handler of first one just reset the second.
hello this is my first question, I wanted to create a dynamically changing link api based on e.target with select. I need the option value to be added to api as a number. I tried to use parseInt but unsuccessfully. the first time I get the message: Parameter 'matchday' is expected to be an integer in the range 1-46. After choosing another option, everything starts loading as it should.
Component
import React, { useEffect } from "react";
const Fixtures = ({ fixtures, getFixtures, loading }) => {
useEffect(() => {
getFixtures();
}, [getFixtures]);
const handleOnChange = e => {
getFixtures(e.target.value);
};
return (
<>
<select onChange={handleOnChange}>
<option value="1">Matchday 1</option>
<option value="2">Matchday 2</option>
</select>
</>
);
};
export const getFixtures = matchday => dispatch => {
dispatch(startFetchingFixtures());
const getFixturesUrl = matchday =>
`http://api.football-data.org//v2/competitions/2021/matches?matchday=${matchday}`;
fetch(getFixturesUrl(matchday), {
headers: {
"X-Auth-Token": "..."
}
}).then(response => response.json())
.then(response => response.matches)
.then(matches => dispatch(fetchedFixtures(matches)));
};