find here my code whene I try to get firstName I get the issue “Cannot read properties of undefined” could some one give a solution or expalain what's the issue whith my code
let state = useSelector(state => {
// console.log('State: ', state.profile);
return state.profile;
});
const [profile, setProfile] = useState();
const [formState, setFormState] = useState({
isValid: false,
values: {},
touched: {},
errors: {}
});
useEffect(()=>{
console.log({accesstoken: token});
if(token)
dispatch(profileActions.getInfos({accesstoken: token}));
},[])
useEffect(()=>{
if (state)
setProfile(state)
console.log(profile.profile);
},[state, profile])
/// here the peace of code to display the profile.data.firstname
ListItemText primary="Date de creation" secondary{JSON.stringify(profile.data.firstName)} />
Did you console the variable state just before your useEffect.
I guess no need to use (useState & useEffect) you can directly use yout variable state.
try to add a console.log(state) before or after the useEffect and share it with as.
Related
I have a failing test but can't work out why. I have react-router links which link to the URL structure: /classes/${weekday}.
Classes component then sets the activeWeekday in context by React Router location which is displayed by the Classes as {activeWeekday} Classes
Functionality works i nthe browser, but for some reason in my tests it's not updating the header so the test is failing.
TestingLibraryElementError: Unable to find an element with the text: /friday classes/i
Can anyone see why? I can't figure it out.
Thks so much in advance.
Update - Here is a codepen replicating the issue.
// Snapshot of the state passed to Classes via context
export const ClassesProvider = ({ children }: ClassesProviderProps) => {
const [activeWeekdayNumber, setActiveWeekdayNumber] = useState<number>(
new Date().getDay()
);
// Classes component
const Classes = () => {
const { activeWeekdayNumber, setActiveWeekdayNumber } = useContext(ClassesContext);
const location = useLocation();
useEffect(() => {
const day = location.pathname.replace("/classes/", "");
const dayIndex = daysOfWeekArray.indexOf(capitaliseFirstLetter(day));
if (dayIndex !== -1) {
setActiveWeekdayNumber(
daysOfWeekArray.indexOf(capitaliseFirstLetter(day))
);
}
}, [location, setActiveWeekdayNumber]);
return (
<>
<h2>{daysOfWeekArray[activeWeekdayNumber]} Classes</h2>
</>
);
};
// failing test - TestingLibraryElementError: Unable to find an element with the text: /friday classes/i
const setup = (value: ClassesContextType) =>
render(
<ClassesContext.Provider value={value}>
<MemoryRouter>
<Classes />
</MemoryRouter>
</ClassesContext.Provider>
);
test("displays the relevant heading when a day of the week link is clicked", () => {
const value = {
activeWeekdayNumber: 3, // wednesday
};
setup(value);
const link = screen.getByRole("link", { name: "Friday" });
fireEvent.click(link);
expect(screen.getByText(/friday classes/i)).toBeInTheDocument();
});
});
The menu list is a styled link:
<li>
<HorizontalMenuLink $active={weekdayNumber === 1} to="/classes/monday">
Monday
</HorizontalMenuLink>
</li>
Im trying to make a shopping cart using react redux. i can add products to my shopping cart but have no idea how to remove a product from my cart.
i tried to remove by splice method but it doesnt seem to work.
Heres my cartRedux -
import {createSlice} from '#reduxjs/toolkit';
const cartSlice = createSlice({
name: "cart",
initialState: {
products:[],
quantity:0,
total:0
},
reducers:{
addProduct: (state, action) => {
state.quantity += 1;
state.products.push(action.payload);
state.total += action.payload.price * action.payload.quantity;
},
removeProduct: (state, action) => {
let index = state.products.indexOf(action.payload);
state.quantity -= action.payload
state.products.splice(index, 1)
}
},
});
export const {addProduct} = cartSlice.actions;
export default cartSlice.reducer;
Replace the array instead of its content.
Changing the line :
state.products.slice(index, 1)]
by
state.products.splice(index, 1)
state.products = [...state.products] // clone array
should allow redux to notice the change.
Receives the item ID as a payload which is then used to remove from the state using the filter method.
const removeItem = state.products.filter((item) => item.id !== action.payload);
state.products = removeItem;
removeProduct: (state, action) =>{state.products.indexOf(action.payload);
state.products.splice(action.payload, 1)};
Using TipTap, I'm trying to avoid adding a <br />, but create a <p></p> instead, with the focus inside that <p>|</p> when the user hit shift-Enter but I can't make it work.
Here's what I did so far:
new (class extends Extension {
keys () {
return {
'Shift-Enter' (state, dispatch, view) {
const { schema, tr } = view.state
const paragraph = schema.nodes.paragraph
console.log(tr.storedMarks)
const transaction = tr.deleteSelection().replaceSelectionWith(paragraph.create(), true).scrollIntoView()
view.dispatch(transaction)
return true
}
}
}
})()
How can I do this?
I don't know if this is still relevant but as I was looking for the same thing, I found two ways to make this work.
NOTE:
I'm using tiptap v2, if that's not a problem, then:
I overrode the HardBreak extension, since it's the one that use the Shift-Enter keybinding. It looks something like;
const CustomHardBreak = HardBreak.extend({
addKeyboardShortcuts() {
return {
"Mod-Enter": () => this.editor.commands.setHardBreak(),
"Shift-Enter": () => this.editor.commands.addNewline(),
};
},
});
And used it like so;
editor = new Editor({
extensions: [
customNewline,
CustomHardBreak,
]
});
Use the default editor command createParagraphNear. E.g this.editor.commands.createParagraphNear()
I tried creating a custom extension from your code and ended up with something similar to the command above, i.e;
export const customNewline = Extension.create({
name: "newline",
priority: 1000, // Optional
addCommands() {
return {
addNewline:
() =>
({ state, dispatch }) => {
const { schema, tr } = state;
const paragraph = schema.nodes.paragraph;
const transaction = tr
.deleteSelection()
.replaceSelectionWith(paragraph.create(), true)
.scrollIntoView();
if (dispatch) dispatch(transaction);
return true;
},
};
},
addKeyboardShortcuts() {
return {
"Shift-Enter": () => this.editor.commands.addNewline(),
};
},
});
And added this as an extension in my editor instance.
PS:
They both work, almost exactly the same, I haven't found a difference yet. But there's somewhat of a 'catch' if you would call it that; Both these methods don't work on empty lines/nodes, a character has to be added before the cursor for it to work, any character, even a space.
In TipTap 2.0 I am able to use this custom extension:
const ShiftEnterCreateExtension = Extension.create({
addKeyboardShortcuts() {
return {
"Shift-Enter": ({ editor }) => {
editor.commands.enter();
return true;
},
};
},
});
To make shift + enter behave like enter.
In my case I actually wanted enter to do something different. So I use prosemirror events to set a ref flag on whether shift was pressed. Than I check that flag under the "Enter" keyboard event -- which could be triggered normally or through the shift + enter extension.
I’m trying to use HeroCards along with a prompt choice in a carousel. So the options to be selected by the user are displayed as HeroCards. As soon as the user clicks in the button of a card it should goes to the next waterfall function.
Here is a working example in bot framework v3. It does work as expected.
const cards = (data || []).map(i => {
return new builder.HeroCard(session)
.title(`${i.productName} ${i.brandName}`)
.subtitle(‘product details’)
.text(‘Choose a product’)
.images([builder.CardImage.create(session, i.image)])
.buttons([builder.CardAction.postBack(session, `${i.id.toString()}`, ‘buy’)]);
});
const msg = new builder.Message(session);
msg.attachmentLayout(builder.AttachmentLayout.carousel);
msg.attachments(cards);
builder.Prompts.choice(session, msg, data.map(i => `${i.id.toString()}`), {
retryPrompt: msg,
});
Below I’m trying to do the same with bot framework v4 but it does not work. It never goes to the next function in my waterfall.
How can I do the same with v4?
…
this.addDialog(new ChoicePrompt(PRODUCTS_CAROUSEL));
…
const productOptions: Partial<Activity> = MessageFactory.carousel(
item.map((p: Product) =>
CardFactory.heroCard(
p.productName,
‘product details’,
[p.image || ''],
[
{
type: ActionTypes.PostBack,
title: ‘buy’,
value: p.id,
},
],
),
),
‘Choose a product’,
);
return await step.prompt(PRODUCTS_CAROUSEL, productOptions);
…
UPDATE:
Follow full code with the suggestion from #Drew Marsh
export class ProductSelectionDialog extends ComponentDialog {
private selectedProducts: Product[] = [];
private productResult: Product[][];
private stateAccessor: StatePropertyAccessor<State>;
static get Name() {
return PRODUCT_SELECTION_DIALOG;
}
constructor(stateAccessor: StatePropertyAccessor<State>) {
super(PRODUCT_SELECTION_DIALOG);
if (!stateAccessor) {
throw Error('Missing parameter. stateAccessor is required');
}
this.stateAccessor = stateAccessor;
const choicePrompt = new ChoicePrompt(PRODUCTS_CAROUSEL);
choicePrompt.style = ListStyle.none;
this.addDialog(
new WaterfallDialog<State>(REVIEW_PRODUCT_OPTIONS_LOOP, [
this.init.bind(this),
this.selectionStep.bind(this),
this.loopStep.bind(this),
]),
);
this.addDialog(choicePrompt);
}
private init = async (step: WaterfallStepContext<State>) => {
const state = await this.stateAccessor.get(step.context);
if (!this.productResult) this.productResult = state.search.productResult;
return await step.next();
};
private selectionStep = async (step: WaterfallStepContext<State>) => {
const item = this.productResult.shift();
const productOptions: Partial<Activity> = MessageFactory.carousel(
item.map((p: Product) =>
CardFactory.heroCard(
p.productName,
'some text',
[p.image || ''],
[
{
type: ActionTypes.ImBack,
title: 'buy',
value: p.id,
},
],
),
),
'Choose a product',
);
return await step.prompt(PRODUCTS_CAROUSEL, {
prompt: productOptions,
choices: item.map((p: Product) => p.id),
});
};
private loopStep = async (step: WaterfallStepContext<State>) => {
console.log('step.result: ', step.result);
};
}
PARENT DIALOG BELOW:
...
this.addDialog(new ProductSelectionDialog(stateAccessor));
...
if (search.hasIncompletedProducts) await step.beginDialog(ProductSelectionDialog.Name);
...
return await step.next();
...
MY BOT DIALOG STRUCTURE
onTurn()
>>> await this.dialogContext.beginDialog(MainSearchDialog.Name) (LUIS)
>>>>>> await step.beginDialog(QuoteDialog.Name)
>>>>>>>>> await step.beginDialog(ProductSelectionDialog.Name)
UPDATE
Replacing the ChoicePrompt with TextPromt (as suggested by Kyle Delaney) seems to have the same result (do not go to the next step) but I realised that if remove return from the prompt like this:
return await step.prompt(PRODUCTS_CAROUSEL, `What is your name, human?`); TO await step.prompt(PRODUCTS_CAROUSEL, `What is your name, human?`);
it does work but when I'm returning the original code with ChoicePrompt without return like this:
await step.prompt(PRODUCTS_CAROUSEL, {
prompt: productOptions,
choices: item.map((p: Product) => p.id),
});
I'm getting another error in the framework:
error: TypeError: Cannot read property 'length' of undefined
at values.sort (/xxx/Workspace/temp/13.basic-bot/node_modules/botbuilder-dialogs/lib/choices/findValues.js:84:48)
at Array.sort (native)
at Object.findValues (/xxx/Workspace/temp/13.basic-bot/node_modules/botbuilder-dialogs/lib/choices/findValues.js:84:25)
at Object.findChoices (/xxx/Workspace/temp/13.basic-bot/node_modules/botbuilder-dialogs/lib/choices/findChoices.js:58:25)
at Object.recognizeChoices (/xxx/Workspace/temp/13.basic-bot/node_modules/botbuilder-dialogs/lib/choices/recognizeChoices.js:75:33)
at ChoicePrompt.<anonymous> (/xxx/Workspace/temp/13.basic-bot/node_modules/botbuilder-dialogs/lib/prompts/choicePrompt.js:62:39)
at Generator.next (<anonymous>)
at /xxx/Workspace/temp/13.basic-bot/node_modules/botbuilder-dialogs/lib/prompts/choicePrompt.js:7:71
at new Promise (<anonymous>)
at __awaiter (/xxx/Workspace/temp/13.basic-bot/node_modules/botbuilder-dialogs/lib/prompts/choicePrompt.js:3:12)
this is the line:
// Sort values in descending order by length so that the longest value is searched over first.
const list = values.sort((a, b) => b.value.length - a.value.length);
I can see the data from my state is coming properly
prompt: <-- the data is ok
choices: <-- the data is ok too
Sometimes I'm getting this error too:
error: TypeError: Cannot read property 'status' of undefined
at ProductSelectionDialog.<anonymous> (/xxxx/Workspace/temp/13.basic-bot/node_modules/botbuilder-dialogs/lib/componentDialog.js:92:28)
at Generator.next (<anonymous>)
at fulfilled (/xxxx/Workspace/temp/13.basic-bot/node_modules/botbuilder-dialogs/lib/componentDialog.js:4:58)
at <anonymous>
at process._tickDomainCallback (internal/process/next_tick.js:228:7)
this line
// Check for end of inner dialog
if (turnResult.status !== dialog_1.DialogTurnStatus.waiting) {
You're using a ChoicePrompt, but when you call prompt you're only passing through an activity (the carousel). ChoicePrompt is going to try to validate the input against a set of choices that you should be passing in when you call prompt. Because you're not doing this, the prompt is not recognizing the post back value as valid and technically should be reprompting you with the carousel again to make a valid choice.
The fix here should be to call prompt with PromptOptions instead of just a raw Activity and set the choices of the PromptOptions to an array that contains all the values you expect back (e.g. the same value you set for the value of the post back button).
This should end up looking a little something like this:
Since you're providing the choices UX with your cards, you want to set the ListStyle on the ChoicePrompt to none
const productsPrompt = new ChoicePrompt(PRODUCTS_CAROUSEL);
productsPrompt.style = ListStyle.none;
this.addDialog(productsPrompt);
Then, set the available choices for the specific prompt:
return await step.prompt(PRODUCTS_CAROUSEL, {
prompt: productOptions,
choices: items.map((p: Product) => p.id),
});
Basically Drew Marsh was right.
I just would like to add some other details that I had to tweak to make it work. In case someone else is going crazy like I was. It could give some insights. It's all about how you handle the returns of nested dialogs.
First change. I had to transform the identifier of the Choice prompt into string:
{
type: ActionTypes.PostBack,
title: 'buy',
value: p.id.toString(),
},
and
return await step.prompt(PRODUCTS_CAROUSEL, {
prompt: productOptions,
choices: item.map((p: Product) => p.id.toString()),
});
Another problem that I found was in the parent dialog:
I was basically trying to do this:
if (search.hasIncompletedProducts) await step.beginDialog(ProductSelectionDialog.Name);
return await step.next();
Which makes no sense, then I changed it to:
if (search.hasIncompletedProducts) {
return await step.beginDialog(ProductSelectionDialog.Name);
} else {
return await step.next();
}
And then the final change in the parent of the parent dialog:
Before was like this:
switch (step.result) {
case ESearchOptions.OPT1:
await step.beginDialog(OPT1Dialog.Name);
break;
default:
break;
}
await step.endDialog();
Which again does not make sense since I should return the beginDialog or endDialog. It was changed to:
switch (step.result) {
case ESearchOptions.OPT1:
return await step.beginDialog(OPT1Dialog.Name);
default:
break;
}
I'm trying to encapsulate a TextInput such that when the value changes it does a serverside lookup and based on the result shows a notification to the user: "That group name already exists". I've been using this as my example to start from: https://marmelab.com/admin-on-rest/Actions.html#the-simple-way
My current error is
Error: The TextInput component wasn't called within a redux-form . Did you decorate it and forget to add the addField prop to your component? See https://marmelab.com/admin-on-rest/Inputs.html#writing-your-own-input-component for details.
but even if I add in
NameLookupTextInput.defaultProps = {
addField: true, // require a <Field> decoration
}
I still get the error. Heres my code
class NameLookupTextInput extends TextInput {
handleChange = eventOrValue => {
console.log("handleChange",eventOrValue);
this.props.onChange(eventOrValue);
this.props.input.onChange(eventOrValue);
if(this.timeoutHandle){
clearTimeout(this.timeoutHandle);
}
console.log(fetch);
this.timeoutHandle = setTimeout(function(){
let value = this.props.input.value;
fetchUtils.fetchJson(API_ENDPOINT+'/groupNameCheck/'+value, { method: 'GET'})
.then((data) => {
console.log(data);
let exists = data.json.exists;
let name = data.json.name;
if(exists){
console.log(this.props.showNotification('The group name "'+name+'" already exists.'));
}else{
console.log(this.props.showNotification('The group name "'+name+'" does not exist.'));
}
})
.catch((e) => {
console.error(e);
//showNotification('Error: comment not approved', 'warning')
});
}.bind(this),500);
};
}
export default connect(null, {
showNotification: showNotificationAction
})(NameLookupTextInput);