Strapi onChange signature - reactjs

I am implementing some minor adjustments to a Strapi CMS, customized by my colleagues.
I need to extend the existing plugins with custom UI [done] and to be able to persist the configuration changes. Found the database config and lifecycle methods. Those functions from my model are never invoked.
I was told that the signature for the onChange method, which my component is receiving in props is onChange({value: newValue}) and it is [excuse my English] the new value for the variable named value from the component properties [props].
const Category = (props) => {
const { name, visible, value, onChange } = props;
...
return (
<div className="container" style={{ margin: '2rem auto', paddingLeft: 0 }}>
<table className="col-12">
{allSubCategories &&
Object.keys(allSubCategories).map((key) => (
<SubCategoryList
key={`sblist ${key}`}
title={key}
visible={visible}
data={allSubCategories[key]}
remainingData={data}
availableAttributes={value}
onChange={(value) => {
console.log('value:', value);
console.log('before onchange');
onChange({value});
console.log('finished onchange');
}}
/>
)
)}
</table>
</div>
);
}
This signature that I am using is not working for me. The call to onChange() crashes with an error: Uncaught TypeError: Cannot read properties of undefined (reading 'name').
What is the correct way of using onChange?

Discovered the correct signature
onChange({
target: {
name,
value,
},
});
Now, it is working.

Related

Styled components: cannot find name 'ref' when trying to pass a ref

I'm trying to pass a ref to be read inside of emotion component (similar to styled-components)
But receving the error Cannot find name 'ref'
What should I do in order to be able to access the ref inside the emotion component?
const Label = styled('p')<{ ref: React.MutableRefObject<null> }>({
fontsize: ref.current...,
});
export const NodeDisplayer = ({ data }) => {
const size = useRef(null);
return (
<>
<Label ref={size} id="title">
</Label>
</>
What are you trying to do? Why would a component need to access its own DOM node to determine its styling? I doubt it will work properly.
But to answer your question, the ref prop is special in React so it isn't accessible in the props, but nothing prevents you from also adding it as a different prop:
<Label ref={size} myRef={size} id="title">
Then you should be able to access myRef.
You cannot get a reference to a component function, because it does not exist in the DOM. You need to reference the HTML element, which you might as well do in the child. And since the ref will not be populated until after the component has rendered, you need to use useEffect to get the referenced node.
function Label() {
const ref = useRef<HTMLParagraphElement>(null);
const [style, setStyle] = useState<CSSProperties>({});
useEffect(() => setStyle({ fontSize: ref.current.clientWidth + 'px' }), []);
return (
<p ref={ref} style={style}>
Hi
</p>
);
}
export const NodeDisplayer = () => <Label></Label>

How to fix this error in react `onClick` listener to be a function, instead got a value of `string` type

I have this codes in react:
const [categoryId, setCategoryId] = useState("");
{
catName.map((singleCategory, index) => {
const { catName, _id: categoryId } = singleCategory;
return (
<>
<div
className="category-single-div flex-3 center-flex-align-display"
key={index}
>
<p className="text-general-small2 category-custom-text">{catName}</p>
<div className="category-icons-div ">
<FaEdit
className="category-icon-edit"
onClick={() => {
setEditCategory(true);
setCategoryId(categoryId);
}}
/>
<AiFillDelete className="category-icon-edit category-icon-delete" />
</div>
</div>
</>
);
});
}
I used map to get an array of objects, and I needed their individual _id when a user clicks the edit button. I also want to call another function on the same edit button via onClick. It is working but displays an error.
Warning: Expected onClick listener to be a function, instead got a
value of string type.
I need that _id so as to pass it to a state and have access to it globally within the component at the top level.
Is this workable?
Your problem comes from the FaEdit component.
<FaEdit
id={categoryId}
className="category-icon-edit"
onClick={(editCategory, id) => { // you need to have onClick as a prop defined inside the FaEdit component
setEditCategory(editCategory);
setCategoryId(id);
}}
/>
Example...
export default function FaEdit({className, onClick, categoryId}){
const handleChange() => {
onClick(true, categoryId)
}
return(
<div className={className} onClick={() => handleChange()}>Click</div>
)
}

React - Unhandled Rejection (TypeError): Cannot read property 'front_default' of undefined

I'm new to React and I learn best by building my own projects with things that's "fun".
I'm building a Pokedex and everything has been pretty neat, until today when building out a new function.
It's supposed to search every time the user passes in another letter with the "searchPokemon" function.
When this is assigned to the button it works like a charm, but when I try to add it to the "onChange" handler within the input it generates this:
How does that come?
If I assign an invalid pokemon name (string) and then search when the searchPokemon function is assigned to the button it doesn't generate an error message, but now it does?
I assume I need some sort of if statement, but I'm not sure how to go about it.
import Axios from "axios";
import React, { useState } from "react";
import "./SearchPokemon.css";
function PK() {
const api = Axios.create({
baseURL: "https://pokeapi.co/api/v2/",
});
const [pokemonSearched, setPokemonSearched] = useState(false);
const [search, setSearch] = useState("");
const [pokemon, setPokemon] = useState({});
const [pokemonDescription, fetchDescription] = useState({});
const [evolution, pokemonEvolution] = useState({});
const searchPokemon = () => {
api.get(`pokemon/${search}`.toLowerCase()).then((response) => {
setPokemon({
name: response.data.name,
height: response.data.height,
weight: response.data.weight,
img: response.data.sprites.front_default,
id: response.data.id,
type: response.data.types[0].type.name,
type2: response.data.types[1]?.type.name,
});
api.get(`pokemon-species/${response.data.id}/`).then((response) => {
fetchDescription({
entry: response.data.flavor_text_entries[0].flavor_text,
evolution: response.data.evolution_chain.url,
});
api.get(`${response.data.evolution_chain.url}`).then((response) => {
pokemonEvolution({
evolution: response.data.chain.evolves_to[0]?.species.name,
});
});
});
});
setPokemonSearched(true);
};
return (
<div className="page">
<div className="search-section">
<input
placeholder="Search..."
type="text"
onChange={(event) => {
setSearch(event.target.value);
searchPokemon();
}}
/>
<button onClick={searchPokemon}>Click me</button>
</div>
<div className="main">
{!pokemonSearched ? null : (
<>
<h1 style={{ textTransform: "capitalize" }}>{pokemon.name}</h1>
<h1>No. {pokemon.id}</h1>
<img src={pokemon.img} alt="" />
<div className="info-wrapper">
<div className="info">
<h3 style={{ textTransform: "capitalize" }}>
Type: {pokemon.type} {pokemon.type2}
</h3>
<h3>Height: {pokemon.height * 10} Cm</h3>
<h3>Weight: {pokemon.weight / 10} Kg</h3>
</div>
</div>
<div className="desc">
<div className="desc-info">
<h3 style={{ textTransform: "capitalize" }}>
{pokemonDescription.entry}
</h3>
</div>
</div>
</>
)}
</div>
</div>
);
}
export default PK;
The error tells exactly what the problem is!
You cannot read property type of undefined.
So what does that mean? You have on line 23 the following:
type2: response.data.types[1]?.type.name,
As you are using Typescript, so you have to understand what exactly the ? means on that line, which is just you making an assumption that the properties followed by the ? will be present everytime! Even though the typing says otherwise.
But, as you can see in the docs of the pokemon endpoint, types is a list, and some don't have more than 1 type in the array, so you have to figure out a way of checking weather that typing is present to set the state.
Edit: As you are triggering the searchPokemon function on every keyboard stroke (try doing the same here), you are using the hook setPokemon on every response, even when there's no response.data, so here's what causing you trouble, you just need to find a way to validate the response before updating the state.

Objects are not valid as a React child when trying to map antd Select options

I'm trying to ask my api for a list of ids and display them in Select component of antd.
<Select style={{ width: "100%" }}>
{myApi.getIds().then((result) => result.map((value) => {
return <Select.Option value={value}>{value}</Select.Option>
}))}
</Select>
value is string.
getIds returns Promise<string[]>
This returns an error Objects are not valid as a React child (found: [object Promise]). But the value is just a string with an id. What am I missing here?
EDIT: Don't know if it will help anyone, but my solution, based on the accepted answer is:
const [ids, setIds] = useState<string[]>(null);
useEffect(() => {
//some code
if (!ids) {
myApi.getIds().then((result) => setIds(result));
}
// some code
return () => { };
}, [ids]);
// some more code
return (
<>
//render other elements
<Select style={{ width: "100%" }}>
{ids?.map((value) => {
return <Select.Option value={value}>{value}</Select.Option>
})}
</Select>
//render other elements
</>
);
It's not complaining about value, it's complaining about the the return value of then.
<Select style={{ width: "100%" }}>
{myApi.getIds().then((result) => result.map((value) => {
// −^^^^^^^^^^^^^^^^^^^^
return <Select.Option value={value}>{value}</Select.Option>
}))}
</Select>
The return value of then is a promise.
You can't render the result of calling getIds like that. Instead, you'll need to either:
Do the getIds in the parent component and have that component pass the result (when it's available) to this component as a prop,
or
Have this component do getIds and wait for the result in a useEffect callback (functional component w/hooks) or componentDidMount (class component), save the result in state, and use that state when rendering. The component will have to handle rendering when it doesn't have the information yet.

can you set outer HTML in return function of a React component?

The default behavior of the MailchimpSubscribe component displays a status alert upon user signup. I want to move this status alert outside of it's position in the DOM so that it shows up at the top of the page.
My code looks like this:
import MailchimpSubscribe from "react-mailchimp-subscribe"
import SimpleForm from './SimpleForm.js'
function Info() {
return (
<div className="canary">
<MailchimpSubscribe url={process.env.REACT_APP_MAILCHIMP_URL}
render={({subscribe, status, message}) => <SimpleForm
status={status}
message={message}
className="form"
style={{}}
onSubmitted={formData => subscribe(formData)}
/>
}/>
</div>
);
}
export default Info;
To change the default button text, I create my own SimpleForm.js component:
import React from "react";
// a basic form
const SimpleForm = ({ status, message, className, style, onSubmitted }) => {
let input;
const submit = () =>
input &&
input.value.indexOf("#") > -1 &&
onSubmitted({
EMAIL: input.value
});
return (
<div className={className} style={style}>
{status === "sending" && <div style={{ color: "blue" }}>sending...</div>}
{status === "error" && (
<div
style={{ color: "red" }}
dangerouslySetInnerHTML={{ __html: message }}
/>
)}
{status === "success" && (
<div
style={{ color: "green" }}
dangerouslySetInnerHTML={{ __html: message }}
/>
)}
<input
ref={node => (input = node)}
type="email"
placeholder="Your email"
/>
<button onClick={submit}>Subscribe</button>
</div>
);
};
export default SimpleForm;
How would I modify this return function so that the dangerouslySetInnerHTML={{ __html: message }} part is set on <div className="canary">?
From what I see in component definition you can pass render props
MailchimpSubscribe.defaultProps = {
render: ({ subscribe, status, message }) => (
<SimpleForm
status={status}
message={message}
onSubmitted={formData => subscribe(formData)}
/>
)
};
with SimpleForm and include specific className style
<MailchimpSubscribe url={process.env.REACT_APP_MAILCHIMP_URL}
render={({subscribe, status, message}) => <SimpleForm
status={status}
message={message}
className="form"
style={{}}
onSubmitted={formData => subscribe(formData)}
/>
}/>
If you need more customization you can create your own component based on what's inside SimpleForm
This is how I understand the problem 😀:
Question
How do you render data passed as props to Component A within Component B?
MailchimpSubscribe holds message inside of its internal state and passes it as a prop to the results of its render function
Answer
React aims to only pass data from parents to children in unidirectional data flow,
so the data passed as props to MailchimpSubscribe cannot directly be rendered in another component which is not its child 😕
The best way I can think of to circumvent this is to leverage the Context API to create shared state between the SimpleForm rendered inside MailchimpSubscribe and a DisplayMessage rendered elsewhere in your app.
In a new file SharedState.js
Create SharedContext to allow components to access context
Create SharedProvider which is a parent that will make the context available to all its children
In SimpleForm
Read SharedContext
Set up an effect to push the status and message values up to the context. This effect will be called each time those values change.
return null when the status is not null, as you would like to render the message elsewhere and hide the form.
In DisplayMessage
Read SharedContext
Use status and message from SharedContext to render your message
Demo
I put together this CodeSandbox which illustrates this strategy.
References
React docs for useContext
React docs for Context
Hope that helps! Let me know if there's anything you'd like clarified further 👍

Resources