no-unused-expressions on ternary - javascript

Here I have an error that says:
Expected an assignment or function call and instead saw an expression
no-unused-expressions
I want onClick put my const handleCompleted with ternary. I think it's just a problem with {} or (), I did lots of things but I always have this error message.
const Filters = ({ listTask, setListTask, filters, setFilters }) => {
const [completed, setCompleted] = useState(false);
const handleCompleted = () => {
const newFilters = { ...filters };
{
completed
? (setCompleted(false),
(newFilters.status.completed = ""),
setFilters(newFilters))
: (setCompleted(true),
(newFilters.status.completed = "Completed"),
setFilters(newFilters));
}
};
return(...);
};

There is some redundant logic (setFilters(newFilters) twice), in addition, you only made a shallow copy of filters ({...filters}), therefore you mutate state (newFilters.status.completed = ...).
I would rewrite it to something like:
const Filters = ({ listTask, setListTask, filters, setFilters }) => {
const [completed, setCompleted] = React.useState(false);
const handleCompleted = () => {
setFilters((filters) => ({
...filters,
status: { ...filters.status, completed: completed ? "" : "Completed" }
}
));
setCompleted(prev => !prev);
};
return <>...</>;
};

I'm pretty sure, it has to do with the weird ternary construction. It's probably just an error in ESLint.
BUT: A ternary operator should be used for conditionally assigning values to variables.
Although it CAN be used as a replacement for if (), it SHOULD NOT be used that way.
The reason is simple: It's unreadable and is prone to unwanted side effects that are hard to debug. Just don't ever use ternaries as a replacement for if ().
Try this instead. I think you find it even more readable.
const Filters = ({ listTask, setListTask, filters, setFilters }) => {
const [completed, setCompleted] = useState(false);
const handleCompleted = () => {
const newFilters = { ...filters };
// this is how you should use ternaries:
newFilters.status.completed = completed ? '' : 'Completed'
setCompleted(!completed)
setFilters(newFilters)
};
return(...);
};

Related

React-query setQueryData not re-rendering component

I've been dealing for a while with this problem and still can't tackle it.
I'm using React-query as a server state management library and I'm trying to get my UI state synchronized with my server state when a mutations occurs. Since I can use the mutation response to avoid a new API call, I'm using the setQueryData feature that React-query gives us.
The problem is that the old-data is being correctly modified (I can see it in the react-query DevTools) when a mutation is successful, but the component using it isn't being re-rendered, making my UI State not synchronized with my Server state (well, at least the user can't see the update).
Let me show some code and hope someone can give me some insights.
Component using the query:
const Detail = ({ orderId }) => {
const { workGroups } = useWorkGroups();
const navigate = useNavigate();
const queryClient = useQueryClient();
const orderQueries = queryClient.getQueryData(["orders"]);
const queryOrder = orderQueries?.find((ord) => ord.id === orderId);
// more code
Component mutating the query:
const Deliver = ({
setIsModalOpened,
artisan,
index,
queryQuantity,
queryOrder,
}) => {
const [quantity, setQuantity] = useState(() => queryQuantity);
const { mutate: confirmOrderDelivered } = useMutateOrderDeliveredByArtisan(
queryOrder.id
);
const onSubmit = () => {
confirmOrderDelivered(
{
id: queryOrder.artisan_production_orders[index].id,
artisan: artisan.user,
items: [
{
quantity_delivered: quantity,
},
],
},
{
onSuccess: setIsModalOpened(false),
}
);
};
// more code
Now the mutation function (ik it's a lot of logic but I dont' want to refetch the data using invalidateQueries since we're dealing with users with a really bad internet connection). Ofc you don't need to understand each step of the fn but what it basically does is update the old queried data. In the beginning I thought it was a mutation reference problem since React using a strict comparison under the hood but I also checked it and It doesn't look like it's the problem. :
{
onSuccess: (data) => {
queryClient.setQueryData(["orders"], (oldQueryData) => {
let oldQueryDataCopy = [...oldQueryData];
const index = oldQueryDataCopy.findIndex(
(oldData) => oldData.id === orderId
);
let artisanProdOrders =
oldQueryDataCopy[index].artisan_production_orders;
let artisanProductionOrderIdx = artisanProdOrders.findIndex(
(artProdOrd) => artProdOrd.id === data.id
);
artisanProdOrders[artisanProductionOrderIdx] = {
...artisanProdOrders[artisanProductionOrderIdx],
items: data.items,
};
const totalDelivered = artisanProdOrders.reduce((acc, el) => {
const delivered = el.items[0].quantity_delivered;
return acc + delivered;
}, 0);
oldQueryDataCopy[index] = {
...oldQueryDataCopy[index],
artisan_production_orders: artisanProdOrders,
items: [
{
...oldQueryDataCopy[index].items[0],
quantity_delivered: totalDelivered,
},
],
};
return oldQueryDataCopy;
});
},
onError: (err) => {
throw new Error(err);
},
}
And last but not least: I already checked that the oldQueryData is being correctly modified (console loging in the onSuccess fn in the mutation response) and, as I said before, the data is correctly modified in the React-query DevTools.
I know this is a lot of code and the problem seems to be complex but I really believe that it might be a really easy thing that I'm not pointing out because of how tired I already am.
Thanks!
Well, I fixed it in the worst possible way imho, so I will answer this question but I really would like to read your thoughts.
It looks like the new query data setted on the expected query is re-rendering the component only if the mutation function is located in the component that we actually want to re-render.
With that in mind what I did was just colocate my mutation function in the parent component and pass it down through the child component.
Something like this:
const Detail = ({ orderId }) => {
const { workGroups } = useWorkGroups();
const navigate = useNavigate();
const { mutate: confirmOrderDelivered } = useMutateOrderDeliveredByArtisan(
queryOrder.id
); ==============> THE MUTATION FN IS NOW IN THE PARENT COMPONENT
const queryClient = useQueryClient();
const orderQueries = queryClient.getQueryData(["orders"]);
const queryOrder = orderQueries?.find((ord) => ord.id === orderId);
// more code
First child:
const Assigned = ({ artisan, queryOrder, index, confirmOrderDelivered }) => {
// THE IMPORTANT PART HERE IS THE PROP BEING PASSED DOWN.
<Modal
isOpen={isModalOpened}
toggleModal={setIsModalOpened}
// className="w312"
width="80%"
height="fit-content"
justifyCont="unset"
alignItems="unset"
>
<Deliver
setIsModalOpened={setIsModalOpened}
artisan={artisan}
queryQuantity={quantity}
queryOrder={queryOrder}
index={index}
confirmOrderDelivered={confirmOrderDelivered} => HERE
/>
</Modal>
Component that actually needs the mutation fn:
const Deliver = ({
setIsModalOpened,
artisan,
index,
queryQuantity,
queryOrder,
confirmOrderDelivered,
}) => {
const [quantity, setQuantity] = useState(() => queryQuantity);
const onSubmit = () => {
confirmOrderDelivered( => HERE.
{
id: queryOrder.artisan_production_orders[index].id,
artisan: artisan.user,
items: [
{
quantity_delivered: quantity,
},
],
}
);
};
You can't mutate any prop.
You always need to create new versions of the objects and props and use destructuring.
Example
queryClient.setQueryData([QUERY_KEYS.MYKEY], (old) => {
const myArray = [...old.myArray];
return {
...old,
// WRONG
myArray[0].name: 'text1',
// CORRECT
myArray[0]: {
...myArray[0],
name: 'text1'
}
}})

JS spread operator workflow on React

React suggests not to mutate state. I have an array of objects which I am manipulating based on some events. My question is, is it okay to write it like this:
const makeCopy = (arr) => arr.map((item) => ({ ...item }));
function SomeComponenet() {
const [filters, setFilters] = useState(aemFilterData);
const handleFilterClick = (filter, c) => {
let copiedFilters = makeCopy(filters);
/**
* Apply toggle on the parent as well
*/
if (!("parentId" in filter)) {
copiedFilters[filter.id].open = !copiedFilters[filter.id].open;
}
setFilters(copiedFilters);
}
}
Am I mutating the original object by doing like above? Or does it make a difference if written like this:
const makeCopy = (arr) => arr.map((item) => ({ ...item }));
function SomeComponent() {
const [filters, setFilters] = useState(aemFilterData);
const handleFilterClick = (filter, c) => {
let copiedFilters = makeCopy(filters);
/**
* Apply toggle on the parent as well
*/
if (!("parentId" in filter)) {
copiedFilters = copiedFilters.map((f) => {
if (filter.id === f.id) {
return {
...f,
open: !f.open,
};
} else {
return { ...f };
}
});
}
setFilters(copiedFilters);
}
}
What's the preferred way to do this? Spread operators are getting a lot verbose and I am not liking it, but I prefer it if that's how I need to do it here. immutable.js and immer or not an option right now.
const makeCopy = (arr) => arr.map((item) => item );
With above code, it's mutating on the original object reference because we're not creating a deep clone.
copiedFilters[filter.id].open = !copiedFilters[filter.id].open;
Here reference of copiedFilters[filter.id] and filters[filter.id] is same.
With spread operator
const makeCopy = (arr) => arr.map((item) => ({ ...item }));
Here we create a new copy of the inner object too. So copiedFilters[filter.id] and filters[filter.id] will have different reference.
This is same as your second approach.
So either you use spread operator while making a copy or you can skip making a copy in the second approach and directly map on filters since you're using spread operator there. This looks better because, why run loop twice - first to create copy and then to update open.
// let copiedFilters = makeCopy(filters); Not needed in second approach
copiedFilters = copiedFilters.map((f) => {
if (filter.id === f.id) {
return {
...f,
open: !f.open,
};
} else {
return { ...f };
}
});
You can create a deep clone when you copy but that would be waste of computation and memory, I don't think it's needed here.
Deep clone is helpful when you have further nesting in the object.

converting class to hooks getting Property 'then' does not exist on type '(dispatch: any) => Promise<void>'.ts(2339)

I'm new to react, here I have two same codes, one is with classes that work, and another is converted from that same class into hooks.
in hooks version, my 'then' is giving an error
Property 'then' does not exist on type '(dispatch: any) =>
Promise'.ts(2339)
have I made some mistake with conversion?
why it is not giving the same error in class while both are the same?
also console.log("Fetched model", realGraph.model); should give an object but it is giving undefined(in-class version it works), but if I put this console outside of loadGraph function then it gives an object, why it's not giving an object inside loadGraph function?
any ideas and suggestions?
class:
import { getGraph, getFloorplan, changeActiveCamera } from '../redux/actions';
const mapStateToProps = (state) => {
return {
currentSite: state.selection.currentSite,
currentCamera: state.selection.currentCamera,
};
};
function mapDispatchToProps(dispatch) {
return {
getGraph: (site) => dispatch(getGraph(site)),
getFloorplan: (site) => dispatch(getFloorplan(site)),
changeActiveCamera: (site, id) => dispatch(changeActiveCamera(site, id)),
};
}
loadGraph() {
if (this.props.currentSite) {
this.props.getFloorplan(this.props.currentSite.identif).then(() => {
console.log('Fetched floorplan');
this.props.getGraph(this.props.currentSite.identif).then(() => {
console.log('Fetched model', this.props.realGraph.model);
// new camera-related node & link status
if (this.props.currentCamera) {
this.props.changeActiveCamera(
this.props.currentSite.identif,
this.props.currentCamera.identif
);
}
});
});
}
}
converted from class to hooks:
Hooks:
const dispatch = useDispatch();
const realGraph = useSelector((state) => state.graphArticles.graph);
const currentSite = useSelector((state) => state.selection.currentSite);
const currentCamera = useSelector((state) => state.selection.currentCamera);
const dispatchGetFloorplan = (site) => dispatch(getFloorplan(site));
const dispatchGetGraph = (site) => dispatch(getGraph(site));
const dispatchChangeActiveCamera = (site, id) =>
dispatch(changeActiveCamera(site, id));
const loadGraph = () => {
if (currentSite) {
dispatchGetFloorplan(currentSite.identif).then(() => {
console.log('Fetched floorplan');
dispatchGetGraph(currentSite.identif).then(() => {
console.log('Fetched model', realGraph.model);
// new camera-related node & link status
if (currentCamera) {
dispatchChangeActiveCamera(
currentSite.identif,
currentCamera.identif
);
}
});
});
}
};
my action related to those:
export function getGraph(site) {
return getData(`api/graph/${site}`, GET_GRAPHS);
}
export function getFloorplan(site) {
return getImage(`api/graph/${site}/floorplan`, GET_FLOORPLAN);
}
On first glance, there are several things I would change in the code you provided.
First, don't use any wrapper factories over your dispatch functions. Use dispatch(action()) directly where you need it component. You aren't gaining anything by creating wrapper functions.
Second, it would be advisable to use some sort of middleware, like Redux Thunk, to handle async Redux actions (like fetching something from the API).
The actions you provided are just "dumb" functions, which are not returning promises so you can't expect it to be "then"-able in your target component.
I also advise the async/await syntax since it is much more readable.
Third, you need to leverage the Hooks reactive API with the useEffect hook.
So first try to define getFloorPlan and getGraph as async actions using the redux-thunk syntax.
export const getGraphAsync = (site) => async (dispatch) => {
try {
const data = await getData(`api/graph/${site}`, GET_GRAPHS);
dispatch(saveGraphData(data)) // save data into Redux store with a normal, synchronous action (plain object)
} catch (error) {
console.log(error)
}
}
export const getFloorplanAsync = (site) => async (dispatch) => {
try {
const data = await getImage(`api/graph/${site}/floorplan`, GET_FLOORPLAN);
dispatch(saveImageData(data)) // save data into Redux store with a normal, synchronous action (plain object)
} catch (error) {
console.log(error)
}
}
I am making an assumption that you correctly configured your store.js to use the thunk middleware.
And then refactor the rest of the component (following some best practices):
const someHookComponent = () => {
// ...
const dispatch = useDispatch();
const currentSite = useSelector((state) =>
state.selection.currentSite);
const currentCamera = useSelector((state) =>
state.selection.currentCamera);
const loadGraph = async () => {
if (currentSite) {
await dispatch(getFloorPlanAsync(currentSite.identif));
console.log('Fetched floorplan');
await dispatch(getGraphAsync(currentSite.identif));
console.log('Fetched model', realGraph.model); /* where is
realGraph coming from? */
/* Why is it important that these 2 dispatches follow one
another when there is no data being passed from one to the
other, or being used later in the component... */
});
});
}
};
useEffect(() => {
// new camera-related node & link status
if (currentCamera) {
dispatch(changeActiveCamera(
currentSite.identif,
currentCamera.identif
));
}
}, [currentSite?.identif, currentCamera?.identif]) /* null chaining is optional here */
// ...
}
I am guessing that loadGraph gets called by some onClick event somewhere down the line like this:
onClick={loadGraph}
If it is called inside useEffect, define the deps (variables used inside loadGraph):
useDeepCompareEffect(() => {
// ... some logic
loadGraph()
}, [currentSite, realGraph])
If you put your currentSite and currentCamera objects directly into the useEffect list of deps then you need to do a deep comparison "by hand".
In that case it's best to create a custom hook like useDeepCompareEffect which will do the heavy lifting of running deep comparisons of reference types under the hood (with the help of some library like lodash for example).
If you want to use or console.log the latest value of realGraph (reference type), you need to use the useEffect hook with a deep comparison again (or just extract the target primitive directly into the deps list and use vanilla useEffect) :
useDeepCompareEffect(() => {
if (realGraph) {
console.log('Fetched model', realGraph.model);
}
}, [realGraph]) // reference type
// or
useEffect(() => {
if (realGraph) {
console.log('Fetched model', realGraph.model);
}
}, [realGraph.someProperty]) // primitive

State does not update in async function

Could you please help me understand why my state was not updated when I called two async functions in the first useEffect? and what is the best way to handle async data in the case that I don't know which one comes first (API1 or API2)?
Thank you!
const MyClass = () => {
const [myState, setMyState] = useState([]);
useEffect(() => {
callApi1();
callApi2();
}, []);
const callApi1 = () => {
fetch(...).then(result => {
// the result of API 1 always comes first and result is not empty
setMyState(result);
)};
}
const callApi2 = () => {
fetch(...).then(result => {
// the result of API 2 always comes 5 - 10 seconds after the API 1
console.log(myState) => [], WHY?
});
}
}
(1.) "... why my state was not updated ..."
Your state was updated, but the callback function captures the old state of myState (as a closure). That means myState inside the callback function will always stay the same as it was when the function was created. (And it is created only when callApi2() is invoked.)
You can not access the current up-to-date state inside an asynchronous callback.
(2.) "...best way to handle async data in the case that I don't know which one comes first"
It depends on your use case.
Generally, you would set some states from your callbacks (e.g. your setMyState(result)), and a different part of your program will do something else dependent on these states, e.g. useEffect(()=>{ /* do something */ }, [ myState ]).
e.g.:
const MyClass = () => {
const [myState, setMyState] = useState([]);
const [myState2, setMyState2] = useState([]);
const [allDone, setAllDone] = useState(false);
useEffect(() => {
callApi1();
callApi2();
}, []);
useEffect(() => {
console.log( 'myState/myState2:', myState, myState2);
if( myState.length && myState2.length ){
setAllDone(true);
}
}, [ myState, myState2 ]);
const callApi1 = () => {
fetch(...).then(result => {
setMyState(result);
)};
}
const callApi2 = () => {
fetch(...).then(result => {
setMyState2(result);
});
}
}

React hooks state undefined on handle function

I am creating a simple quiz with persisting response state and i am currently stuck trying to figure out why responseState is undefined in my handleAnswerClick function, this function is triggered much later if at all per click. By then the States should all be set.
const Question: React.FC<IProps> = (props) => {
const [responseState, setResponseState] = useState([]);
const [question, setQuestion] = useState<IStateProps>(props.id);
const [answers, setAnswers] = useState([]);
const [choice, setChoice] = useState();
const initialResponseState = () => {
const data = {
duration: '00:01:00',
examTakerProgress: 1,
question: props.id,
answer: '1',
}
axios.post(`${host}/api/responseState/`, data)
.then(() => {
console.log('Created the initial Response State');
})
.catch((err) => {
console.log(err);
})
}
const getData = async () => {
await axios.all([
axios.get(`${host}/api/question/${question}`),
axios.get(`${host}/api/responseState/examTakerProgress/1/question/${props.id}`)
])
.then(axios.spread((questionData, responseData) => {
if (!responseData.data.length > 0) {
initialResponseState();
}
setResponseState(responseData.data);
setQuestion(questionData.data);
setAnswers(questionData.data.answers);
setChoice(responseData.data.length > 0 ? responseData.data[0].answer : '');
setTimeout(() => {
props.toggleLoading();
}, 50);
}))
.catch(err => console.error(err));
};
useEffect(() => {
getData()
}, [])
const handleAnswerClick = async (number: number) => {
setChoice(number);
const data = {
duration: '00:01:00',
examTakerProgress: 1,
question: props.id,
answer: number,
}
await axios.put(`${host}/api/responseState/${responseState[0].id}/`, data)
.then((data) => {
console.log(data);
console.log('choice changed to :' + number);
})
.catch((err) => {
console.log(err);
})
}
if (props.loading) {
return <Loader/>
}
return (
<React.Fragment>
<h3>{question.label}</h3>
<p className='mb-3'>{question.description}</p>
<ListGroup as="ul">
{answers.map((answer, index) => {
if (answer.id === choice) {
return <ListGroup.Item key={answer.id} action active> <Answer key={index}
answer={answer}/></ListGroup.Item>
} else {
return <ListGroup.Item key={answer.id} action onClick={() => {
handleAnswerClick(answer.id)
}}><Answer key={index}
answer={answer}/></ListGroup.Item>
}
}
)}
</ListGroup>
</React.Fragment>
)};
Can someone explain me the reason why this is happening?
Based on this line
const [responseState, setResponseState] = useState({});,
the responseState is an object.
but in the handleAnswerClick function you are using it as an array ${responseState[0].id}.
Note that this actually works if the object has a 0 key but since you're getting undefined that is not the case.
Reasons why responseState is undefined.
The most obvious is that you set responseState to undefined
The only place you call setResponseState is in getData where you have setResponseState(responseData.data);, so please check if responseData.data isn't undefined.
You never get to call setResponseState and the initial state is an object but you try to get responseState[0].id
I'm not sure what data type you are handling, but if you defined const [responseState, setResponseState] = useState({}); with the default value of useState as {} and then try to access responseState[0].id, there is two things happening.
Either you have an object with number keys or you have an array, but if you have an array, you should declare the initial value as an array.
But this isn't the problem, the problem is that you might never get to the part where you call setResponseState(responseData.data); and then you will have responseState to be {} and when trying to do responseState[0] it will be undefined, and when calling responseState[0].id you will get an error saying that you cannot read id of undefined.
Ways to fix this
Debug your code, make sure that the api call returns what you are expecting and setResponseState(responseData.data); is called.
Check if responseState[0] exists before try to access responseState[0].id
I would be able to help more, but you didn't add more information, so it's hard to help, but above is what I think it's the problem
Basically i am creating a responseState when a question loads for the first time, if a question is afterwards reloaded the State is already there since it is persisted. This means i also have to make sure to call setResponseState for the first case where the ResponseState still does not exist.
const initialResponseState = () => {
const data = {
duration: '00:01:00',
examTakerProgress: 1,
question: props.id,
answer: '1',
}
axios.post(`${host}/api/responseState/`, data)
.then(() => {
setResponseState(res.data)
console.log('Created the initial Response State');
})
.catch((err) => {
console.log(err);
})}

Categories