I have the following state and function
const [obj, setObj] = useState([])
const getData = async (e) => {
e.preventDefault();
const text = e.target.input.value;
const payload = {
input: text,
};
const response = await fetch(process.env.API_LINK, {
body: JSON.stringify(payload),
headers: {
Authorization: `Bearer ${process.env.API_KEY}`,
"Content-Type": "application/json",
},
method: "POST",
});
if (response.ok) {
const data = await response.json();
const output = data.choices[0].text;
setObj(obj => [...obj, {input: text, output: output}])
}
};
return (
<form onSubmit={(e) => getData(e)}>
<input name="input"/>
</form>
)
The issue is that when the state obj is empty it doesn't get updated when the function first runs, it has to be run twice for obj to be updated. The response from the API and everything else is fine.
Can you try changing setObj call to :
setObj([...obj, {input: text, output: output}])
What you pass to setObj is a function rather than a state obj. It needs to be called hence the changes shows the second time onwards.
Related
On my page I have an onSubmit attribute to handle my submit event. The function of the submit and the page element are listed below.
On submit I first want to post a ruleset, and when I get the response with the ID of the ruleset, I post all rules individually.
Now, after everything has been submitted, I want to redirect to another page, called rulesets_modify.
I've tried Passing a useNavigate from the NewRuleset page to the handleSubmit function, and calling this after all rule posts. I get navigated over correctly, but on the rulesets_modify page, it isn't able to get the data. I can retrieve the ruleset data, but the rule data is empty. This makes me believe that the navigation is happening before everything is done being submitted. After refreshing the rulesets_modify page, everything is loaded correctly.
How could I solve this?
Page element rulesets_new:
export default function NewRuleset() {
// Add table columns
// Add useStates
// Function to add new rule
// useEffects to get data from API
const navigate = useNavigate();
return (
<>
<Header text='Regelset aanmaken' />
<Form onSubmit={(event) => handleSubmit(event, RulesetName, EngineOid, ProcessOid, Rules, navigate)}>
<RnRulesetsTopRow
// Params
/>
<Row>
<Col>
<RnTable columns={columns} data={Rules} Rules={Rules} setRules={setRules} />
</Col>
</Row>
<Row>
<Col>
<Button type="submit" variant='success'>Save</Button>
</Col>
<Col>
<Button className='float-right' onClick={(e) => addNewRule(e, setRules)}>+</Button>
</Col>
</Row>
</Form>
</>
)
}
function handleSubmit:
export function handleSubmit(e, RulesetName, EngineOid, ProcessOid, Rules, nav) {
e.preventDefault();
var newRulesetJson = `{...}`;
const RulesetPostOptions = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: newRulesetJson
};
postRuleset(RulesetPostOptions)
.then(response => {
Rules.forEach(Rule => {
let seq = (Rule.SequenceNumber) ? Rule.SequenceNumber : "";
let name = (Rule.Name) ? Rule.Name : "";
let desc = (Rule.Description) ? Rule.Description : "";
let exp = (Rule.Expression) ? Rule.Expression : "";
let RuleJson = `{...}`;
let RulesPostOptions = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: RuleJson
};
postRule(RulesPostOptions)
});
nav('/rulesets_modify?rulesetOid='+response.Oid);
})
};
function postRuleset:
export const postRuleset = async (RulesetPostOptions) => {
return fetch('api-url.com', RulesetPostOptions)
.then(response => response.json())
.catch(error => console.log('error', error))
}
You are correct, the handleSubmit isn't waiting for all the network requests to complete prior to navigating. The Rules.forEach is completely synchronous.
Assuming postRule is also an asynchronous function like postRuleset I'd suggest mapping the Rules array to an array of Promise objects and using Promise.all on them to wait for them all to be fulfilled, and then issuing the navigation action.
Example:
export async function handleSubmit(
e,
rulesetName,
engineOid,
processOid,
rules,
navigate
) {
e.preventDefault();
const newRulesetJson = `{...}`;
const rulesetPostOptions = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: newRulesetJson
};
const response = await postRuleset(rulesetPostOptions);
// Create array of postRule requests
const ruleRequests = rules.map(Rule => {
const seq = rule.SequenceNumber || "";
const name = rule.Name || "";
const desc = rule.Description || "";
const exp = rule.Expression || "";
const ruleJson = `{...}`;
const rulesPostOptions = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: RuleJson
};
return postRule(rulesPostOptions); // <-- return Promise!
});
// Wait for all postRule requests to resolve
await Promise.all(ruleRequests);
navigate('/rulesets_modify?rulesetOid=' + response.Oid);
};
I'm using antd with react to upload an image to each of my facilities.
The uploading is working, but I can't seem to get the preview from the previous image to show up where it should, by pulling the existing image from the database.
What happens is that it will only show the new image, after it has been uploaded via drag and drop but not the previous one stored in the database. I'm pulling the url of the previous image with the const "testing" and I can log it and it will show inside the console but it will not show when I use it in the setFileList const. What am I missing here?
export function FacilityUpdate() {
const navigate = useNavigate()
const { id } = useParams()
const [facility, setFacility] = useState(null)
const accessToken = useSelector((state) => state.tokens.value.accessToken);
const [loadingFacility, setLoadingFacility] = useState(false)
const [loading, setLoading] = useState(false)
const dispatch = useDispatch();
useEffect(() => {
if (facility && !facility.is_owner) {
navigate('/facilities')
}
return () => null
})
useEffect(() => {
setLoadingFacility(true)
function fetchFacility() {
axios.get(API.facilities.retrieve(id), {
headers: {
"Authorization": `Bearer ${accessToken}`
},
withCredentials: true,
})
.then(res => {
setFacility(res.data)
})
.finally(() => {
setLoadingFacility(false)
})
}
fetchFacility()
return () => null
}, [id, accessToken])
const testing = facility && (facility.mainimage)
console.log(testing)
const [fileList, setFileList] = useState([
{
uid: '-1',
name: testing,
status: 'done',
//This is where the preview image gets loaded from the database:
url: testing,
},
]);
const onUploadDraggerChange = ({ fileList: newFileList, file: resp }) => {
setFileList(newFileList);
if (!resp || !resp.response || !resp.response.mainimage) {
return;
}
message.success("Updated facility picture")
};
const uploadDraggerrops = {
name: 'mainimage',
action: API.facilities.update(id),
method: 'PATCH',
listType: "picture-card",
maxCount: 1,
onChange: onUploadDraggerChange,
fileList: fileList,
headers: {
"Authorization": `Bearer ${accessToken}`
},
withCredentials: true,
};
The problem with your code might come from this line on onUploadDraggerChange:
setFileList(newFileList);
Before updating the state of fileList, fileList is an array with an element that contains that previous picture. When you call onUploadDraggerChange you are erasing the stored content and replacing it with the new one. Maybe you want to push it to add to the array?
setFileList([...fileList, newFileList]);
This way, the first element is the one fetched and the consequent ones from the draggerUpload.
Any case, it looks like the structure of your state should look like
const [facility, setFacility] = useState(null)
const [fileList, setFileList] = useState([]);
useEffect(() => {
if (facility && !facility.is_owner) {
navigate('/facilities')
}
return () => null
})
useEffect(() => {
setLoadingFacility(true)
function fetchFacility() {
axios.get(API.facilities.retrieve(id), {
headers: {"Authorization": `Bearer ${accessToken}`},
withCredentials: true,
})
.then(res => {
setFacility(res.data)
res && (res.mainimage) {
setFileList([...filelist, {
uid: 'some-random-id',
name: testing,
status: 'done',Ï
url: res.mainimage
}])
})
.finally(() => {
setLoadingFacility(false)
})
}
fetchFacility()
return () => null
}, [id, accessToken])
here is my question how to call api recursively untill nested data stack keys are finished
here is my full explaination in image
i found this relatable code for recursion api call at this post recursive api call
function callFW() {
d3.json(url, async function(data) {
Tree["uid"] = data.request.uid
Tree["hid"] = data.firmware.meta_data.hid
Tree["size"] = data.firmware.meta_data.size
Tree["children"] = [];
await BuildTree(data.firmware.meta_data.included_files,Tree["children"]);
console.log(Tree)
})
}
async function BuildTree(included_files, fatherNode) {
if (included_files.length > 0) {
let promises = included_files.map( item => {
let url = endpoint + "file_object/" + item + "?summary=true";
return axios.get(url)
});
const results = await Promise.all(promises);
for(let response of results){
var node = {}
node["uid"] = response.data.request.uid
node["hid"] = response.data.file_object.meta_data.hid
node["size"] = response.data.file_object.meta_data.size
node["children"] = []
fatherNode.push(node)
await BuildTree(response.data.file_object.meta_data.included_files, node["children"]);
};
}
};
this is im using custom useRecurseFetch for getting post api result
but i have no idea how to change this code for recursive api call
import React from 'react';
import qs from 'qs';
import axios from 'axios';
const useRecurseFetch = query => {
const [status, setStatus] = React.useState('idle');
const [result, setResult] = React.useState([]);
const [findClass, setFindClass] = React.useState([]);
// console.log(passVariable);
var data;
data = qs.stringify({
query: `http://www.blabla{ ${query}/category}`,
});
// eslint-disable-next-line no-undef
var Grant = process.env.REACT_APP_GRANT;
// eslint-disable-next-line no-undef
var Client = process.env.REACT_APP_CLIENT;
// eslint-disable-next-line no-undef
var Key = process.env.REACT_APP_KEY;
// eslint-disable-next-line no-undef
var Auth = process.env.REACT_APP_AUTH;
// eslint-disable-next-line no-undef
var Query = process.env.REACT_APP_QUERY;
const queryCall = React.useCallback(
async token => {
if (!token) {
return;
} else {
setStatus('Loading');
var config = {
method: 'POST',
url: `${Query}`,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization:
token.data.token_type + ' ' + token.data.access_token,
},
data: data,
};
axios(config)
.then(response => {
setResult(response.data.results.bindings);
let originalResult = response.data.results.bindings
.filter(ek => ek.class !== undefined && ek)
.map(el => el.obj.value);
setFindClass(originalResult);
setStatus('success');
})
.catch(function (error) {
setStatus('error');
});
}
},
[data]
);
React.useEffect(() => {
const authInitiate = () => {
var data = qs.stringify({
grant_type: `${Grant}`,
client_id: `${Client}`,
client_secret: `${Key}`,
});
var config = {
method: 'POST',
url: `${Auth}`,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
data: data,
};
axios(config)
.then(response => {
queryCall(response);
})
.catch(function (error) {
console.log(error);
});
};
authInitiate();
}, [queryCall]);
return [status, result, findClass];
};
export default useRecurseFetch;
please any one can help me out with this code, im unable to figure out whats going in this code
anyhelp is really saved my day and really so appreciatable
here i called useRecurseFetch custom function in app.js
const [loadingStatus, mainData, addDataToPostItemArray] = useRecurseFetch(
`<kk:cat>`
);
please any one can help me out please im stuck with this process of calling api
So I'm trying out basic todo app with edit and delete feature. I'm having problems with my edit feature. I have two main components in my app namely InputTodo for adding todo items and ListTodo which contains two additional subcomponents (TodoItem for each todo and EditTodo which shows an editor for a selected todo). Whenever the Edit Button inside a certain TodoItem is clicked, the EditTodo component is showed. When the Confirm button in EditTodo component is clicked, a PUT request will be sent to update the database (PostgreSQL in this case) through Node. After successfully sending this send request, I would like to re-render the list of TodoItem components. I'm doing this by fetching the updated list of values from the database through a different GET request then calling setState given the response from the GET request. However, the GET request's response doesn't reflect the PUT request done earlier. Thus, the app still renders the un-updated list of todos from the database.
Here are some code snippets
const ListTodo = (props) => {
const [todos, setTodos] = useState([]);
const [editorOpen, setEditorOpen] = useState(false);
const [selectedId, setSelectedId] = useState();
const getTodos = async () => {
console.log('getTodos() called');
try {
const response = await fetch("http://localhost:5000/todos");
const jsonData = await response.json();
setTodos(jsonData);
console.log(todos);
} catch (err) {
console.error(err.message);
}
console.log('getTodos() finished');
};
const editTodo = async description_string => {
console.log('editTodo() called');
try {
const body = { description: description_string };
const response = await fetch(
`http://localhost:5000/todos/${selectedId}`,
{
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body)
}
);
console.log(response);
await getTodos();
props.handleListModified();
} catch (err) {
console.error(err.message);
}
console.log('editTodo() finised');
}
const handleItemButtonClick = (button, row_key) => {
if (button === 'delete') {
deleteTodo(row_key);
setEditorOpen(false);
} else if (button === 'edit') {
setEditorOpen(true);
setSelectedId(row_key);
console.log(todos.filter(todo => { return todo.todo_id === row_key})[0].description);
}
};
const handleEditorButtonClick = async (button, description_string) => {
if (button === 'cancel') {
setSelectedId(null);
} else if (button === 'confirm') {
await editTodo(description_string);
}
setEditorOpen(false);
};
useEffect(() => {
console.log('ListTodo useEffect() trigerred');
getTodos();
}, [props.listModified]);
return(
<Fragment>
<table>
<tbody>
{todos.map( todo => (
<TodoItem
key={todo.todo_id}
todo_id={todo.todo_id}
description={todo.description}
handleClick={handleItemButtonClick} />
))}
</tbody>
</table>
{ editorOpen &&
<EditTodo
handleEditorButtonClick={handleEditorButtonClick}
description={todos.filter(todo => { return todo.todo_id === selectedId})[0].description}
selectedId={selectedId} /> }
</Fragment>
);
};
I guess that the problem is - In editTodo function, you are calling getTodos() function. But, you are not updating the state with the response you get. See if this helps.
const response = await fetch(
`http://localhost:5000/todos/${selectedId}`,
{
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body)
}
);
console.log(response);
setTodo(await getTodos()); // Update the state with the values from fetch
I'm trying to use Spotify API and to move into the Arrays.
const App = () => {
const [isLoading, setIsLoading] = useState(true);
const [dataSource, setDataSource] = useState();
useEffect(() => {
return fetch("https://api.spotify.com/v1/me/albums?offset=0&limit=5", {
method: "GET",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
Authorization:
"Bearer AUTH CODE "
}
})
.then(response => response.json())
.then(responseJson => {
setIsLoading(false);
// console.log(responseJson);
// let result = Object.values(responseJson); // if I want to convert it in array
let result = responseJson
setDataSource(result);
});
}, []);
console.log(dataSource);
and I get an object
{href: "https://api.spotify.com/v1/me/albums?o`enter code here`ffset=0&limit=5", items: Array(5) ...}
I would like to go into items but when i do
console.log(dataSource.items);
or
console.log(dataSource.items[1]);
I get
Cannot read property 'items' of undefined
Any idea?
Where is my mistake?
The dataSource state is by default undefined, you need to change the default value to have items property. The fetch is handling the then asynchronously so the data will arrive few milliseconds later and in that time the code tries to access items property which is missing.
You can try to initialize differently, like the following:
const [dataSource, setDataSource] = useState({ items: [] });
I hope this helps!