React DropdownMultiselect component not updating with new options - reactjs

import React, {useState, useEffect} from 'react';
import DropdownMultiselect from "react-multiselect-dropdown-bootstrap";
function App() {
const [data, setData] = useState(['test'])
const fetchApi = () => {
fetch("../api/" + ticket)
.then((response) => response.json())
.then((json) => {
let names = [];
for(var entry in json){
names.push(json[entry].print_name);
}
setData(names);
})
}
useEffect( () => {
fetchApi();},
[])
console.log('I ran')
return (
<div>
<button> {data[0]} </button>
<DropdownMultiselect options={data} />
</div>
);
}
export default App;
When I run the code above, the button updates with the name that was fetched via the fetch call, but the DropdownMultiSelect does not update its options. Am I doing it wrong, or is there something strange about the DropdownMultiSelect component that is somehow breaking things?
edit: brought the names declaration and the setData call into the second .then statement, still no joy.

I tested your code here. Looks like the problem lies within react-multiselect-dropdown-bootstrap itself and not your code.
I would suggest using a different library instead like react-select. In general it's always wiser to use third-party libraries which are being maintained frequently and have many weekly downloads.
If yet you still want to stick with react-multiselect-dropdown-bootstrap I would suggest creating an issue on their github repository.

You can try with react-multiselect-dropdown-bootstrapv1.0.4
Higher versions have some issues with the data population.
codesandbox - https://codesandbox.io/s/pensive-curran-zryxj?file=/src/App.js

Related

How to extract data from axios GET request object in React?

Currently I've been pulling data from an API using axios and the useEffect Hook in React. This is done by having the user enter a value into the code field which will be used to make a request to the API. However, when it comes to extracting the data and getting it to display on the page I've been having issues. Normally, I would use data.map() to map the array data to different fields, but when pulling from this API I am getting a project object which I've been having issues mapping to fields for displaying on the application. In addition, I've also noticed that the useEffect hook is constantly being called over and over in the console, and I dunno how to make it be called only once as well. If anyone could help with either of these issues that would be greatly appreciated!
home.js
import React, { useState, useEffect } from 'react';
import axios from "axios";
import './Home.css'; // Import styling
function Home() {
const [data, setData] = useState([])
const [state, setState] = useState({
code: ""
})
const handleChange = e => setState(prevState => ({ ...prevState, [e.target.name]: e.target.value }))
useEffect(() => {
axios.get(baseURL)
.then((response) => {
setData(response.data);
console.log(data)
});
});
return (
<div className="form-container">
<h1>axios-test</h1>
<div className="form-group">
<label>Code</label>
<input type="text" className="form-control" name="code" value={state.code} onChange={handleChange}/>
</div>
<div className="form-group">
<label>Lead</label>
<input type="text" className="form-control" name="Lead" value={data.map(data => data.project.primaryLeaderName)} onChange={handleChange} disabled/>
</div>
</div>
)
}
export default Home;
console output
I would suggest adding the dependency array [] in the useEffect and also use useEffect hook to call a function =>
const fetchData = async ()=>{
const {data} = await axios.get('url')
// we await the response and destruct it to get only the data
setState(data)
}
useEffect(()=>{
fetchData()
},[])
There are two issues with your useEffect call.
You are missing the dependency array. Check out the documentation . You should add [] as the second argument to useEffect if you only want this to run the first time the component mounts.
console.log(data). You are expecting data to have the new value, but this is not guaranteed by React. The call to setData updates data, but you won't be able to access the new value inside of the same useEffect call. In this case, I don't think you need to though. It's worth pointing out as that's a common gotcha with learning useEffect
Finally, there's a slight issue in this section, based on the screenshot of the data:
value={data.map(data => data.project.primaryLeaderName)}
Check out the documentation on Array.prototype.map. data does not have a project key. In fact, you're mapping projects! The best naming convention is to name the element in the function after what it represents. Each element in data is a project. So:
value={data.map(project => project.primaryLeaderName)}
EDIT:
Seems I misread the console output. Looks like your response looks like this:
{project: [//...Array of projects]
So try this:
value={data.project.map(proj => proj.primaryLeaderName)}

ReactJS: Pass object into fetch promise

I'm kind of new react, and this quill plugin is really confusing me. I'm using react-quilljs to display an editable rich text field, which is supposed to be pre-populated with a value retrieved using fetch from my API. Seems pretty simple, right? But I'm getting the error 'quill is undefined' in the fetch callback.
import React, { useState, useEffect } from "react";
import { useQuill } from "react-quilljs";
import "quill/dist/quill.snow.css";
import "quill/dist/quill.bubble.css";
// see https://www.npmjs.com/package/react-quilljs
export default function View(props) {
const [group, setGroup] = useState([]);
const { quill, quillRef } = useQuill({});
useEffect(() => {
fetch('/api/groups/' + props.id , {
method: 'GET'
})
.then(res => res.json())
.then((data) => {
setGroup(data);
quill.setContents(JSON.parse(data));
})
.catch(console.log);
}, [quill]);
return(
<div >
<div id="descriptionInput" ref={quillRef} />
</div>
);
}
Of course I've omitted a lot of the code, but I think it should be enough to illustrate the problem. I think, basically the question is, how do I pass the quill object into the fetch promise?
I have searched for the answer but for some reason can't find anything on this.
I looked through the documents and found this:
quill.clipboard.dangerouslyPasteHTML();
I have made a working sample for you:
https://codesandbox.io/s/epic-stonebraker-itt06?file=/src/App.js:401-469
After some more inspection, it turns out useEffect is being called multiple times, and quill is not available right away (as Asher Lim notes). So adding a check if (quill) inside the fetch promise solves the problem.
Of course this means that the fetch is being done more times than necessary, which can be solved with some more refactoring.

Using React hooks to fire events with side effects, like onClick() calling fetch()

I use React with hooks, using useState() for the internal state and useEffect() for external side effects like calling a web service, as it is described in React's documentation.
In this example I have a button, and a click on the button should call a web service. This works with useEffect() the first time on initialization but further button clicks will not call the web service.
Now my hacky solution is to introduce an additional state called hackIndex which is incremented each time the button is clicked, but that does not seem particular elegant:
import React, { useState, useEffect } from "react";
function App() {
const [answers, setAnswers] = useState([]);
// very ugly hack
const [hackIndex, setHackIndex] = useState(0);
// when hackIndex changes, gets data from web service and adds it to answers array
// also called when the page is initialized, but we can ignore that for this question
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/todos/1")
.then(response => response.json())
.then(json => setAnswers([...answers, json.title]));
}, [hackIndex]);
// returning React JSX
return (
<div>
<div>
<button
onClick={e => {
setHackIndex(hackIndex + 1);
}}
>
Call web service
</button>
</div>
<div>Collected answers:</div>
{answers.map(answer => (
<div>{answer}</div>
))}
</div>
);
}
export default App;
I haven't used reducers yet, as coming from Java and Angular they strike me a bit odd. But useReducer() should only use pure functions, so they don't seem to fit.
This is somewhat basic functionality, but I don't find any articles about this problem or anyone asking it. Or I'm missing something completely.
There is a similar question, but it involves Apollo for GraphQL queries using useQuery(), whereas I'm using a simple VanillaJS fetch() call:
How to fire React Hooks after event
setX functions returned from useState have a functional update ability that takes a function as an argument and calls that function with the previous state value. That enables your fetchAnswers to not need access to the previous value of answers.
import React, { useState, useEffect } from "react";
function App() {
const [answers, setAnswers] = useState([]);
function fetchAnswers() {
fetch("https://jsonplaceholder.typicode.com/todos/1")
.then(response => response.json())
.then(json => setAnswers(prevAnswers => [...prevAnswers, json.title]));
}
useEffect(() => {
fetchAnswers();
}, []);
// returning React JSX
return (
<div>
<div>
<button
onClick={fetchAnswers}
>
Call web service
</button>
</div>
<div>Collected answers:</div>
{answers.map(answer => (
<div>{answer}</div>
))}
</div>
);
}
export default App;

Get state from display component

I have one fetch and one display .js file. However I am trying to figure out how to read the state. Of course as it's done now it's returned from the other .js file. But I would like to use the state that was set instead. How would I refactor to do this?
I would like to use the stateURL prop in the DataLoader.js
DataLoader.js
import React, { useState, useEffect } from 'react';
import useFetch from "./useFetch";
export default function DataLoader({stateURL}) {
const data = useFetch("/api");
// Should not be used
console.log("data", data);
const data2 = Object.keys(data).map(data => data);
console.log("data2", data2);
const data3 = data2.map(key => {
console.log("inside data3", key );
return data[key];
});
//This is empty
console.log("state", stateURL);
return (
<div>
<h1>Testing</h1>
<ul>
{Object.keys(data3).map(key => {
return <li>{data3[key].href}</li>;
})}
</ul>
</div>
);
}
useFetch.js
import { useState, useEffect } from "react";
export default function useFetch(url) {
const [stateURL, setStateURL] = useState([]);
console.log("url", url);
useEffect(() => {
fetch(url)
.then(response => response.json())
.then(data => setStateURL(data._links));
}, []);
console.log("stateURL", stateURL);
return stateURL;
}
That is not possible. The hooks can only be referred from the original creating component.
Why do you just use the fetch hook within the display file?
If you want to keep these two components separated:
To access the data, you have to share the data somehow to be accessible to your other components. There are several ways to do it:
Pass the data up into the parent component via a callback and pass that into the other child component.
Using a state management library like Redux or Mobx.
Using the context API to share data between components but that might not be the best way for this kind of data.
It depends on your setup and your needs. If only these two components ever need that data, pushing it into the parent works fine.
If there are other components, which maybe need that data, you might want to use a state management lib.

How to do fetch with React Hooks; ESLint enforcing `exhaustive-deps` rule, which causes infinite loop

I'm pretty new to React hooks in general, and very new to useSelector and useDispatch in react-redux, but I'm having trouble executing a simple get request when my component loads. I want the get to happen only once (when the component initially loads). I thought I knew how to do that, but I'm running into an ESLint issue that's preventing me from doing what I understand to be legal code.
I have this hook where I'm trying to abstract my state code:
export const useState = () => {
const dispatch = useDispatch();
const data = useSelector((state) => state.data);
return {
data: data,
get: (props) => dispatch(actionCreators.get(props))
};
};
Behind the above function, there's a network request that happens via redux-saga and axios, and has been running in production code for some time. So far, so good. Now I want to use it in a functional component, so I wrote this:
import * as React from 'react';
import { useState } from './my-state-file';
export default () => {
const myState = useState();
React.useEffect(
() => {
myState.get();
return () => {};
},
[]
);
return <div>hello, world</div>;
};
What I expected to happen was that because my useEffect has an empty array as the second argument, it would only execute once, so the get would happen when the component loaded, and that's it.
However, I have ESLint running on save in Atom, and every time I save, it changes that second [] argument to be [myState], the result of which is:
import * as React from 'react';
import { useState } from './my-state-file';
export default () => {
const myState = useState();
React.useEffect(
() => {
myState.get();
return () => {};
},
[myState]
);
return <div>hello, world</div>;
};
If I load this component, then the get runs every single render, which of course is the exact opposite of what I want to have happen. I opened this file in a text editor that does not have ESLint running on save, so when I was able to save useEffect with a blank [], it worked.
So I'm befuddled. My guess is the pattern I'm using above is not correct, but I have no idea what the "right" pattern is.
Any help is appreciated.
Thanks!
UPDATE:
Based on Robert Cooper's answer, and the linked article from Dan Abramov, I did some more experimenting. I'm not all the way there yet, but I managed to get things working.
The big change was that I needed to add a useCallback around my dispatch functions, like so:
export const useState = () => {
const dispatch = useDispatch();
const data = useSelector((state) => state.data);
const get = React.useCallback((props) => dispatch({type: 'MY_ACTION', payload:props}), [
dispatch
]);
return {
data: data,
get: get,
};
};
I must admit, I don't fully understand why I need useCallback there, but it works.
Anyway, then my component looks like this:
import * as React from 'react';
import { useState } from './my-state-file';
export default () => {
const {get, data} = useState();
React.useEffect(
() => {
get();
return () => {};
},
[get]
);
return <div>{do something with data...}</div>;
};
The real code is a bit more complex, and I'm hoping to abstract the useEffect call out of the component altogether and put it into either the useState custom hook, or another hook imported from the same my-state-file file.
I believe the problem you're encountering is that the value of myState in your dependency array isn't the same value or has a different JavaScript object reference on every render. The way to get around this would be to pass a memoized or cached version of myState as a dependency to your useEffect.
You could try using useMemo to return a memoized version of your state return by your custom useState. This might look something like this:
export const useState = () => {
const dispatch = useDispatch();
const data = useSelector((state) => state.data);
return useMemo(() => ({
data: data,
get: (props) => dispatch(actionCreators.get(props))
}), [props]);
};
Here's what Dan Abramov has to say regarding infinite loops in useEffect methods:
Question: Why do I sometimes get an infinite refetching loop?
This can happen if you’re doing data fetching in an effect without the second dependencies argument. Without it, effects run after every render — and setting the state will trigger the effects again. An infinite loop may also happen if you specify a value that always changes in the dependency array. You can tell which one by removing them one by one. However, removing a dependency you use (or blindly specifying []) is usually the wrong fix. Instead, fix the problem at its source. For example, functions can cause this problem, and putting them inside effects, hoisting them out, or wrapping them with useCallback helps. To avoid recreating objects, useMemo can serve a similar purpose.
Full article here: https://overreacted.io/a-complete-guide-to-useeffect/

Resources