Using axios doesn't rerender my React Native component - javascript

I am using hooks in React Native. This is my code:
useEffect(() => {
if (var1.length > 0 ){
let sym = [];
var1.map((items) => {
let count = 0;
state.map((stateItems) => {
if(items === stateItems.name) {
count = count + 1;
}
})
if (count === 0) {
sym.push(items)
}
});
async function getAllStates (sym) {
let stateValues = [];
await Promise.all(sym.map(obj =>
axios.get(ServerURL + "/note?name=" + obj).then(response => {
stateValues.push(response.data[0]);
})
)).then(() =>{
setNewItem(stateValues);
});
}
getAllStates (sym);
}
}, [var1]);
useEffect(() => {
let stateValues = state;
for( let count = 0 ; count < newItem.length; count++ ){
stateValues.push(newItem[count]);
}
setState(stateValues);
}, [newItem]);
This runs successfully without any errors. However, when the state is displayed as below, I am not seeing the latest value added in the state. It shows all the previous values. When I refresh the whole application, I am able to see my value added.
return (
<View style={styles.container}>
<Text style = {{color:"white"}}>
{
state.map( (item, key) =>{
return(
<Text key = {key} style = {{color:"white"}}> {item.name} </Text>
)
})
}
</Text>
</View>
);
Can someone tell me why this is happening? I want to see the data render immediately after the axios call. I am using React Native.

when i force update using :stackoverflow.com/questions/53215285/... it works fine. However, i am looking for a better fix if anyone can provide?
This should do:
useEffect(() => {
var1.forEach(async (name) => {
if (state.some(item => item.name === name)) return;
const response = await axios.get(ServerURL + "/note?name=" + name);
setState(state => [...state, response.data[0]]);
});
}, [var1]);
I still see two issues in your approach:
this code may start the same ajax request multiple times before the result of the firstz ajax-request is added to state; this also means that the result for the same name may be added multiple times to state.
for each item of var1 times each item of state, this is an O(n*m) problem or in this case basically O(n²) as m is pretty fast catching up to n. You should find a better approach here.
And I'm almost certain that [var1] is wrong here as the dependency for useEffect. But you'd need to show where this value comes from to fix that, too.

Related

React Child Component Is Not Rerendering When Props Are Updated

My parent component takes input from a form and the state changes when the value goes out of focus via onBlur.
useEffect(() => {
let duplicate = false;
const findHierarchy = () => {
duplicationSearchParam
.filter(
(object, index) =>
index ===
duplicationSearchParam.findIndex(
(obj) => JSON.stringify(obj.name) === JSON.stringify(object.name)
)
)
.map((element) => {
DuplicateChecker(element.name).then((data) => {
if (data.status > 200) {
element.hierarchy = [];
} else {
element.hierarchy = data;
}
});
if (duplicate) {
} else {
duplicate = element?.hierarchy?.length !== 0;
}
});
return duplicate;
};
let dupe = findHierarchy();
if (dupe) {
setConfirmationProps({
retrievedData: formData,
duplicate: true,
responseHierarchy: [...duplicationSearchParam],
});
} else {
setConfirmationProps({
retrievedData: formData,
duplicate: false,
responseHierarchy: [],
});
}
}, [duplicationSearchParam]);
I have a child component also uses a useeffect hook to check for any state changes of the confirmationProps prop.
the issue is that the event gets triggered onblur, and if the user clicks on the next button. this function gets processes
const next = (data) => {
if (inProgress === true) {
return;
}
inProgress = true;
let countryLabels = [];
formData.addresses?.map((address) => {
fetch(`/api/ref/country/${address?.country}`)
.then((data) => {
countryLabels.push(data.label);
return countryLabels;
})
.then((countries) => {
let clean = MapCleanse(data, countries);
fetch("/api/v1/organization/cleanse", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(clean),
})
.then((data) => {
if (data.status > 200) {
console.log(data.message);
message.error(getErrorCode(data.message.toString()));
} else {
Promise.all([confirmationProps, duplicationSearchParam]).then(
(values) => {
console.log(values);
console.log(data);
setCleansed(data);
**setCurrent(current + 1);**
inProgress = false;
}
);
}
})
.catch((err) => {
console.log(err);
inProgress = false;
});
})
.catch((err) => {
console.log(err);
inProgress = false;
});
});
console.log(confirmationProps);
};
The important part in the above code snippet is the setCurrent(current + 1) as this is what directs our code to render the child component
in the child component, i have a use effect hook that is watching [props.duplicateData.responseHierarchy]
I do output the values of props.duplicateData.responsehierarchy to the console to see if the updated information gets passed to the child component and it does. the values are present.
I have a conditional render statement that looks like this
{cleansedTree?.length > 0 || treeDuplicate ? (...)}
so although the data is present and is processed and massaged in the child component. it still will not re render or display properly. unless the user goes back to the previous screen and proceeds to the next screen again... which forces a re-render of the child component.
I have boiled it down and am assuming that the conditional rendering of the HTML is to blame. Or maybe when the promise resolves and the state gets set for the confirmation props that the data somehow gets lost or the useefect doesn't pick it up.
I have tried the useefect dependency array to contain the props object itself and other properties that arent directly related
UPDATE: this is a code snippet of the processing that gets done in the childs useeffect
useEffect(() => {
console.log(props.duplicate);
console.log(props.duplicateData);
console.log(props.confirmationProps);
let newArray = props.duplicateData.filter((value) => value);
let duplicateCheck = newArray.map((checker) =>
checker?.hierarchy?.find((Qstring) =>
Qstring?.highlightedId?.includes(UUIDToString(props?.rawEdit?.id))
)
);
duplicateCheck = duplicateCheck.filter((value) => value);
console.log(newArray, "new array");
console.log(duplicateCheck, "duplicate check");
if (newArray?.length > 0 && duplicateCheck?.length === 0) {
let list = [];
newArray.map((dupeData) => {
if (dupeData !== []) {
let clean = dupeData.hierarchy?.filter(
(hierarchy) => !hierarchy.queryString
);
let queryParam = dupeData.hierarchy?.filter(
(hierarchy) => hierarchy.queryString
);
setSelectedKeys([queryParam?.[0]?.highlightedId]);
let treeNode = {};
if (clean?.length > 0) {
console.log("clean", clean);
Object.keys(clean).map(function (key) {
treeNode = buildDuplicate(clean[key]);
list.push(treeNode);
return list;
});
setCleansedTree([...list]);
setTreeDuplicate(true);
} else {
setTreeDuplicate(false);
}
}
});
}
}, [props.duplicateData.responseHierarchy]);
This is a decently complex bit of code to noodle through, but you did say that **setCurrent(current + 1);** is quite important. This pattern isn't effectively handling state the way you think it is...
setCurrent(prevCurrent => prevCurrent + 1)
if you did this
(count === 3)
setCount(count + 1) 4
setCount(count + 1) 4
setCount(count + 1) 4
You'd think you'd be manipulating count 3 times, but you wouldn't.
Not saying this is your answer, but this is a quick test to see if anything changes.
The issue with this problem was that the state was getting set before the promise was resolved. to solve this issue I added a promise.all function inside of my map and then proceeded to set the state.
What was confusing me was that in the console it was displaying the data as it should. but in fact, as I learned, the console isn't as reliable as you think. if someone runs into a similar issue make sure to console the object by getting the keys. this will return the true state of the object, and solve a lot of headache

REACT JS reset integer counter

I have this function that gets data from a service using a fetch api call and waits for the response using async and await. If the response isn't null, it loads a react component and passes the fetched data to the component, it uses react state to manage data content.
Because of the wait, i had to introduce an integer counter to help me manage the react page. So the integer counter is initialized to 0 and only increments if the response from fetch call isn't null. So i keep showing a progress bar as long as the counter is 0. Once the data state changes, the integer counter is incremented and the page loads the a new react component with the fetched data.
function CheckDeliveries(){
const [deliveries, setDeliveries] = useState(null);
const urlAPpend = "delivery/findByCustomerId/";
let userid = JSON.parse(localStorage.getItem("User"))["userId"];
const httpMethod = 'GET';
let token = localStorage.getItem("Token");
console.error('RELOAD >>>>>>', reload);
if(reload < 1){
makeApiAuthCallsWithVariable(userid,urlAPpend,httpMethod,token).then(
data => {
if (data !== null) {
//console.log("Api response: Data "+JSON.stringify(data));
setDeliveries(data);
reload++
}else{
console.error('Response not ok', data);
}
}
)
}
if(reload >= 1 && deliveries !== null){
reload = 0;
console.error('RELOAD AllDeliveryDiv >>>>>>', reload);
return (<AllDeliveryDiv obj = {deliveries} />);
}else if(reload >= 1 && deliveries === null){
reload = 0;
console.error('RELOAD MakeDeliveryDiv >>>>>>', reload);
return (<MakeDeliveryDiv />);
}else if(reload === 0){
return ( <ProgressBar striped variant="primary" now={value}/>);
}
}
My Question
I have tried using a boolean state instead of integer counter but the page gets into an infinite loop whenever i update the boolean state. Is there a way i can implement this boolean state in without experiencing the infinite loop ?
After i fetch the data, and reset the counter to 0. I log the value before reset and after reset and i get 1 and 0 respectively. But when i call this function again without reloading the entire page, counter value remains 1 even after i had reset it earlier. How do i resolve this?
Any better way to implement this, please share.
It's hard to really tell what you're going for with reload, so that's why I left the whole MakeDeliveryDiv stuff out, but this would load data from your endpoint when the component is first mounted using the useEffect side effect hook:
function CheckDeliveries() {
const [deliveries, setDeliveries] = useState(null);
const [loaded, setLoaded] = useState(false);
React.useEffect(() => {
const userid = JSON.parse(localStorage.getItem("User"))["userId"];
const token = localStorage.getItem("Token");
makeApiAuthCallsWithVariable(
userid,
"delivery/findByCustomerId/",
"GET",
token,
).then((data) => {
setDeliveries(data);
setLoaded(true);
});
}, []);
if (!loaded) {
return <ProgressBar striped variant="primary" now={value} />;
}
if (deliveries === null) {
return <>Data not OK.</>;
}
return <AllDeliveryDiv obj={deliveries} />;
}

React setTimeout with Loop

I am pulling documents from Firebase, running calculations on them and separating the results into an array. I have an event listener in place to update the array with new data as it is populated.
I am using setTimeout to loop through an array which works perfectly with the initial data load, but occasionally, when the array is updated with new information, the setTimeout glitches and either begins looping through from the beginning rather than continuing the loop, or creates a visual issue where the loop doubles.
Everything lives inside of a useEffect to ensure that data changes are only mapped when the listener finds new data. I am wondering if I need to find a way to get the setTimeout outside of this effect? Is there something I'm missing to avoid this issue?
const TeamDetails = (props) => {
const [teamState, setTeamState] = useState(props.pushData)
const [slide, setSlide] = useState(0)
useEffect(() => {
setTeamState(props.pushData)
}, [props.pushData])
useEffect(()=> {
const teams = teamState.filter(x => x.regData.onTeam !== "null" && x.regData.onTeam !== undefined)
const listTeams = [...new Set(teams.map((x) => x.regData.onTeam).sort())];
const allTeamData = () => {
let array = []
listTeams.forEach((doc) => {
//ALL CALCULATIONS HAPPEN HERE
}
array.push(state)
})
return array
}
function SetData() {
var data = allTeamData()[slide];
//THIS FUNCTION BREAKS DOWN THE ARRAY INTO INDIVIDUAL HTML ELEMENTS
}
SetData()
setTimeout(() => {
if (slide === (allTeamData().length - 1)) {
setSlide(0);
}
if (slide !== (allTeamData().length - 1)) {
setSlide(slide + 1);
}
SetData();
console.log(slide)
}, 8000)
}, [teamState, slide]);

Tracking changes in state?

The component code has several parameters, each of which has an initial value received from the server. How can I track that one of them (or several at once) has changed its state from the original one in order to suggest that the user save the changes or reset them?
Something similar can be seen in Discord when changing the profile / server.
The solution I found using useEffect () looks redundant, because there may be many times more options.
const [hiddenData, setHiddenData] = useState(server.hidden_data);
const [hiddenProfile, setHiddenProfile] = useState(server.hidden_profile);
const [isChanged, setIsChanged] = useState(false);
useEffect(() => {
if (hiddenData !== server.hidden_data
|| hiddenProfile !== server.hidden_profile) {
setIsChanged(true);
} else {
setIsChanged(false);
}
}, [hiddenData, server.hidden_data, hiddenProfile, server.hidden_profile]);
return (
<>
{isChanged && <div>You have unsaved changes!</div>}
</>
);
Maybe something like that?
const [draftState, setDraftState] = useState(server)
const [state, setState] = useState(server)
// a more complex object with the list of changed props is fine too
const isChanged = lodash.isEqual(state, draftState)
function changeProp (prop, value) {
setState({
...draftState,
[prop]: value
})
}
function saveState () {
setState(draftState)
// Persist state if needed
}

In Slate.js editor.apply(operation) is not applying "split_node" operations correctly

I am designing a google-doc like collaborative tool with latest React + Slate as Frontend and Flask in Backend. I am using socket-io in React and flask_socketio in Python to emit and listen content from other collaborators.
React app code:
const RichTextExample = props => {
const [value, setValue] = useState(props.currentEditor);
const editor = useMemo(() => withHistory(withReact(createEditor())), []);
const id = useRef(`${Date.now()}`);
const remote = useRef(false);
const socketchange = useRef(false);
useEffect(() => {
socket.on("new-remote-operations", ({ editorId, ops, doc_id }) => {
if (id.current !== editorId && doc_id === props.document.doc_id) {
remote.current = true;
JSON.parse(ops).forEach(op => {
console.log("LISTEN: applying op", op);
editor.apply(op);
});
remote.current = false;
console.log('value is ', value);
socketchange.current = true; //variable to track socket changes in editor via operations
}
});}, [])
return(
<Slate
editor={editor}
value={value}
onChange={value => {
setValue(value);
const ops = editor.operations
.filter(o => {
if (o) {
return o.type !== "set_selection" && o.type !== "set_value";
}
return false;
});
if (ops.length && !remote.current && !socketchange.current) {
console.log("EMIT: Editor operations are ", ops);
socket.emit("new-operations", {
editorId: id.current,
ops: JSON.stringify(ops),
doc_id: props.document.doc_id
});
}
socketchange.current = false;
}}
>
Python code for socket is simple:
app = Flask(__name__)
db_name = 'userdoc.db'
app.config['SECRET_KEY'] = 'secret-key'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///'+db_name
socketio = SocketIO(app, cors_allowed_origins="*")
#socketio.on('new-operations', namespace='/')
def operations(data):
print('operations listened...1/2/3..')
emit('new-remote-operations', data, broadcast=True, include_self=False)
Issue:
When split_node is passed as an type of operation in socket.on(),
editor.apply(op) doesn't apply it as it suppose to. Please help me on this.
Because of this, I get following two cases:
I think the issue you are facing is because you send a batch of operations that should not be applied one by one.
A split_node operation like the one you are generating by hitting enter will actually split all the nested nodes till it reaches the leaves, and move some nodes around.
Concretely, a split_node is actually 2-3 operations following each others, that can't be applied solely. If you apply the first one for example, that would split the text node, and end up with two Text sharing the same attributes. Slate will normalize them and re-merge them as soon as it can, which in your case, happen between each editor.apply(op).
I think the solution here, is simply to wrap your whole loop inside the withoutNormalizing method. It will prevent Slate to normalize the document in-between the operations.
For Slate <= 0.47
editor.withoutNormalizing(() => {
JSON.parse(ops).forEach(op => {
editor.apply(op);
});
})
For Slate >= 0.5
Editor.withoutNormalizing(editor, () => {
JSON.parse(ops).forEach(op => {
editor.apply(op);
});
})

Categories