Pre and post hooks for factory - scope issue - javascript

I'm attempting to build a simple factory that will pass "props" to modifiers registered to the parent.
However, in the code below, the props object is undefined by the time posthooks() gets called.
const factory = function({ collection = [], modifiers = [] }) {
let props = { collection, timesRan: 0 };
const registerRun = () => {
props.timesRan = props.timesRan + 1;
}
const prehooks = function() {
modifiers.forEach((modifier) => {
modifier.prehook(props);
registerRun();
});
};
const posthooks = function(props) {
modifiers.forEach((modifier) => {
modifier.posthook(props);
registerRun();
});
};
prehooks();
posthooks();
return props;
};
// test case
const collection = [
{
"name": "Jimmy",
"id": 1
},
{
"name": "Johnny",
"id": 2
},
]
// modifier
const modifier = {
prehook: (props) => {
if (props && props.collection) {
console.log('pre hook ran');
props.collection = props.collection.map(item => Object.assign({}, { points: 100 }, item));
}
return props;
},
posthook: (props) => {
if (props && props.collection) {
console.log('post hook ran');
props.collection = props.collection.map(item => Object.assign({}, { id: String(item.id) }, item));
}
return props;
}
}
// test the factory
const modifiers = [ modifier ];
const returnValue = factory({ collection, modifiers } );
console.log('returnValue', returnValue);

Related

Routing is not triggerd with useEffect in Nextjs

I have a dynamic page in Nextjs -> steps -> [slug].tsx . Essentially, it is a steps page and routing is triggered whenever a step changes in the state. For example, if I am in the first step, its value is null but once I click next its value becomes "next". When I go back I basically null the step and useEffect should react to these changes and route. However, it doesn't seem to work. I am not sure why.
Here is my code
type Steps = {
title: string;
slug: string;
value: string | null;
};
const StepsPage: NextPage = () => {
const router = useRouter();
const { query } = router;
const [steps, setSteps] = useState<Steps[]>([
{
title: "Step 1",
slug: "step-1",
value: null,
},
{
title: "Step 2",
slug: "step-2",
value: null,
},
{
title: "Step 3",
slug: "step-3",
value: null,
},
]);
let step: Steps | undefined = undefined;
useEffect(() => {
if (!steps) {
return;
}
const nextStep = steps.find((step) => step.value === null);
if (nextStep && step && step.slug !== nextStep.slug) {
console.log("go to next step");
router.push(`/steps/${nextStep.slug}`);
return;
}
if (!nextStep) {
router.push("/result");
return;
}
}, [router, steps, step]);
if (!steps) {
<div>no steps found</div>;
}
const slug = Array.isArray(query.slug) ? query.slug[0] : query.slug;
if (!slug) {
return <div>no slug found</div>;
}
const currentStep = steps.find((step) => step.slug === slug);
if (!currentStep) {
return <div>no current step found</div>;
}
const handleStep = (value: string | null) => {
setSteps((prevSteps) => {
const updatedSteps = prevSteps.map((step) => {
if (step.slug === slug) {
return { ...step, value };
}
return step;
});
return updatedSteps;
});
};
return (
<div>
<h1>Step Page</h1>
<p>Slug: {slug}</p>
<button onClick={() => handleStep("next")}>Next</button>
<button onClick={() => handleStep(null)}>Back</button>
</div>
);
};
export default StepsPage;
To me, it looks like the logic might just be a little jumbled. Here's what I'd recommend.
First, it looks like the steps themselves are static. I would make your Step type simply
type StepSlug = "step1" | "step2" | "step3"; // Extra type safety
interface Step {
title: string;
slug: StepSlug;
};
Then, I would declare the following array outside of the component, since it is static.
const STEPS: Step[] = [
step1: {
title: "Step One",
slug: "step1",
},
// same for step2 and step3
];
Then, within your component, you have the following state:
const StepsPage = () => {
...
const [stepIx, setStepIx] = useState(0);
const [values, setValues] = useState<(string | null)[]>(STEPS.map(() => null)); // creates initial values of array, same length as STEPS, with all null values
...
// Not necessary but for readability
const currentStep = STEPS[stepIx];
const canGoForward = stepIx < STEPS.length - 1;
const canGoBackward = stepIx > 0;
const goForward = () => {
if (!canGoForward) return;
setStepIx((val) => val + 1);
}
const goBackward = () => {
if (!canGoBackward) return;
setStepIx((val) => val - 1);
}
// Helper functions
const updateCurrentValue = (newVal: string) => {
setValues((oldValues) => {
oldValues[stepIx] = newVal;
return oldValues;
}
};
...
// Here's how you listen to changes to the step
useEffect(() => {
... // logic for setting the slug
}, [stepIx]);
...
}
Obviously there's still a bit to fill in, but this is both safer (w.r.t types) and lets stepIx serve as the source of truth combined with the STEPS array so you don't have to keep searching for the slug.

How to update an array inside an array of objects with useState function component

How to update array inside array use state function component
the array is inside the setTask() and how to use setTask() and add new_photo in photos
const new_photo = "testing2.png"; <------------ insert this value in photos
const [task, setTask] = useState([
{
task_id: 123,
title: "sample",
photos: ["testing1.png"] <------------ here
}
])
Result should be like this:
[
{
task_id: 123,
title: "sample",
photos: ["testing1.png","testing2.png"]
}
]
setTask((oldTasks) => {
const myTask = oldTasks.find(t => t.task_id === 123)
return [
...oldTasks.filter(ot => ot.task_id != 123),
{
...myTask,
photos: [...myTask.photos, new_photo],
}
]
})
Ref: spread operator and asynchronous state update
const new_photo = "testing2.png"; // <------------ insert this value in photos
const [task, setTask] = useState([
{
task_id: 123,
title: "sample",
photos: ["testing1.png"] // <------------ here
}
])
const arrayPush = (ID) => {
setTask((element) => {
element.forEach((e) => {
if (e.task_id === ID) {
e.photos.push(new_photo);
}
});
});
console.log(task);
}
const arrayPush2 = (ID) => {
let taskCopy = Array.from(task)
taskCopy.forEach((element) => {
if (element.task_id === ID) {
element.photos.push(new_photo);
}
});
setTask(taskCopy)
};
arrayPush(123)
arrayPush2(123)

Trying to understand an object composition pattern which features a factory and a function based mixin technique

I'm trying to understand behavior of function based composition in JavaScript.
const Animal = (name) => {
let properties = { name };
return ({
get name() { return properties.name },
set name(newName) { properties.name = newName },
breathe: function() {console.log(`${this.name} breathes!`); }
})
}
const aquaticKind = (animal) => ({
swim: () => console.log(`${animal.name} swims`)
})
const walkingKind = (animal, noOfLegs) => {
const properties = { noOfLegs }
return ({
get noOfLegs() { return properties.noOfLegs },
set noOfLegs(n) { properties.noOfLegs = n; },
walk: () => console.log(`${animal.name} walks with ${properties.noOfLegs} legs`)
})
}
const egglayingKind = (animal) => ({
layEgg: () => console.log(`${animal.name} laid an egg`)
})
const Crocodile = (name) => {
const info = Animal(name);
return Object.assign(info,
walkingKind(info, 4),
aquaticKind(info),
egglayingKind(info)
);
}
const snooty = Crocodile('snooty');
snooty.breathe();
snooty.swim();
snooty.walk();
snooty.name = "coolie";
snooty.noOfLegs = 23 // I expected this to get update to 23
snooty.swim();
snooty.walk();
snooty.layEgg();
If you run the code above, you will see that noOfLegs never get updated, while name get updated. I can't seem to wrap my head around this. How do we make noOfLegs get updated too?
MDN Documentation for object.assign shows you how to copy "accessors"
Here's your code that works as expected - the completeAssign function is based entirely on the code in that link
const completeAssign = (target, ...sources) => {
sources.forEach(source => {
const descriptors = Object.keys(source).reduce((descriptors, key) => {
descriptors[key] = Object.getOwnPropertyDescriptor(source, key);
return descriptors;
}, {});
Object.getOwnPropertySymbols(source).forEach(sym => {
const descriptor = Object.getOwnPropertyDescriptor(source, sym);
if (descriptor.enumerable) {
descriptors[sym] = descriptor;
}
});
Object.defineProperties(target, descriptors);
});
return target;
};
const Animal = (name) => {
const properties = { name };
return ({
get name() { return properties.name },
set name(newName) { properties.name = newName },
breathe () { console.log(`${this.name} breathes!`); }
})
}
const aquaticKind = (animal) => ({
swim: () => console.log(`${animal.name} swims`)
});
const walkingKind = (animal, noOfLegs) => {
const properties = { noOfLegs };
return ({
get noOfLegs() { return properties.noOfLegs },
set noOfLegs(n) { properties.noOfLegs = n; },
walk: () => console.log(`${animal.name} walks with ${properties.noOfLegs} legs`)
})
}
const egglayingKind = (animal) => ({
layEgg: () => console.log(`${animal.name} laid an egg`)
})
const Crocodile = (name) => {
const info = Animal(name);
return completeAssign(info,
walkingKind(info, 4),
aquaticKind(info),
egglayingKind(info)
);
}
const snooty = Crocodile('snooty');
snooty.breathe();
snooty.swim();
snooty.walk();
snooty.name = "coolie";
snooty.noOfLegs = 23;
snooty.swim();
snooty.walk();
snooty.layEgg();

Trying to access state in oncompleted method

I have API query and getting the result and setting those in a state variable in Oncompleted method of API query, Now i am updating the same state variable in another api query "onCompleted method.
I am not able to access the result from state what i have set before in first api query and below is my code
Query 1:
const designHubQueryOnCompleted = designHubProject => {
if (designHubProject) {
const {
name,
spaceTypes
} = designHubProject;
updateState(draft => { // setting state here
draft.projectName = name;
draft.spaceTypes = (spaceTypes || []).map(po => {
const obj = getTargetObject(po);
return {
id: po.id,
name: obj.name,
category: obj.librarySpaceTypeCategory?.name,
description: obj.description,
warning: null // trying to modify this variable result in another query
};
});
});
}
};
const { projectDataLoading, projectDataError } = useProjectDataQuery(
projectNumber,
DESIGNHUB_PROJECT_SPACE_TYPES_MIN,
({ designHubProjects }) => designHubQueryOnCompleted(designHubProjects[0])
);
Query 2:
const {
// data: designhubProjectSpaceTypeWarnings,
loading: designhubProjectSpaceTypeWarningsLoading,
error: designhubProjectSpaceTypeWarningsError
} = useQuery(DESIGNHUB_PROJECT_LINKED_SPACETYPE_WARNINGS, {
variables: {
where: {
projectNumber: { eq: projectNumber }
}
},
onCompleted: data => {
const projectSpaceTypeWarnings = data.designHubProjectLinkedSpaceTypeWarnings[0];
const warnings = projectSpaceTypeWarnings.spaceTypeWarnings.reduce((acc, item) => {
const spaceTypeIdWithWarningState = {
spaceTypeId: item.spaceTypeProjectObjectId,
isInWarningState: item.isInWarningState
};
acc.push(spaceTypeIdWithWarningState);
return acc;
}, []);
console.log(state.spaceTypes); // trying to access the state here but getting empty array
if (state.spaceTypes.length > 0) {
const updatedSpaceTypes = state.spaceTypes;
updatedSpaceTypes.forEach(item => {
const spaceTypeWarning = { ...item };
spaceTypeWarning.warning = warnings?.filter(
w => w.spaceTypeId === spaceTypeWarning.id
).isInWarningState;
return spaceTypeWarning;
});
updateState(draft => {
draft.spaceTypes = updatedSpaceTypes;
});
}
}
});
Could any one please let me know where I am doing wrong with above code Or any other approach to modify the state, Many thanks in advance!!

Only last item is added to array inside map function

I'm mapping an array inside a map function and I want to add the id of every element inside the array to a state. I'm facing an issue where just the last item is added to the array even though console log shows that the function iterates to correct number of times.
This is the code I have written
const { evc } = this.props;
evc.chargingStationGroups && evc.chargingStationGroups.map((item, key) => {
item.stations.map((stationItem, key) => {
console.log("stationID ",stationItem.stationID);
var stationId = {};
stationId = {
stationId: stationItem.stationID
}
var allIdArray = this.state.stationIdArray.concat(stationId);
this.setState({ stationIdArray: allIdArray })
})
})
Here evc.chargingStationGroups is something like this
[
{
groupId: "16",
groupName: "Sia",
stations: [{stationId: "8", name: "Test"},{stationId: "9", name: "Test2"},{stationId: "10", name: "Test3"}]
},
{
groupId: "17",
groupName: "Maroon5",
stations: [{stationId: "10", name: "Test"},{stationId: "11", name: "Test2"},{stationId: "10", name: "Test3"}]
}
],
How can i add all stationItem.stationID to my array, not just the last one.
Only call setState once inside all your rendering (because setState is asynchronous)
Assuming you don't have dupes of station between chargingStationGroups, just concat everybody
const { evc } = this.props;
if (evc.chargingStationGroups) {
const ids = evc.chargingStationGroups.flatMap((item, key) => {
return item.stations.map((stationItem, key) => {
return {
stationId: stationItem.stationID
}
})
})
const stationIdArray = this.state.stationIdArray.concat(ids)
this.setState({ stationIdArray })
})
Else just avoid the dupes...
const { evc } = this.props;
if (evc.chargingStationGroups) {
const ids = evc.chargingStationGroups.flatMap((item, key) => {
return item.stations.map((stationItem, key) => {
return {
stationId: stationItem.stationID
}
})
})
const arr = this.state.stationIdArray.concat(ids)
const s = new Set(arr.map(x => x.stationID))
const stationIdArray = [...s].map(stationId => ({ stationId }))
this.setState({ stationIdArray })
})
Not tested because no minimal reproducible example given, but you get the idea...
Original answer: What happens when using this.setState multiple times in React component?
In brief, this.setState is batched and called only once at the end of the loop, leaving this.state.stationIdArray empty all the time. Hence only the result at the final iteration of this statement is kept:
var allIdArray = this.state.stationIdArray.concat(stationId);
Avoid calling setState multiple time in this case:
const { evc } = this.props;
if (evc.chargingStationGroups) {
let allIdArray = [];
evc.chargingStationGroups.forEach(item => {
allIdArray = [
...allIdArray,
...item.stations.map(stationItem => ({
stationId: stationItem.stationId
}))
];
});
this.setState({ stationIdArray: allIdArray });
}
A simple example: https://codesandbox.io/s/bold-swartz-leto5
You should just use forEach if you want to do operations during iteration.
const { evc } = this.props;
evc.chargingStationGroups && evc.chargingStationGroups.forEach((item, key) => {
item.stations.forEach((stationItem, key) => {
console.log("stationID ",stationItem.stationID);
var stationId = {};
stationId = {
stationId: stationItem.stationID
}
var allIdArray = this.state.stationIdArray.concat(stationId);
this.setState({ stationIdArray: allIdArray })
})
})

Categories

Resources